From c3e38e713d27e1a07b4f15f62cc1637afec6791c Mon Sep 17 00:00:00 2001 From: Michael Langmayr Date: Tue, 6 Aug 2024 11:02:30 -0700 Subject: [PATCH 1/6] run clang-tidy on codebase and update code --- CMakeLists.txt | 44 +- camerad/CMakeLists.txt | 184 +- camerad/archon.cpp | 13533 +++++++++++++++++---------------- camerad/archon.h | 643 +- camerad/camera.cpp | 1538 ++-- camerad/camera.h | 385 +- camerad/camerad.cpp | 1216 +-- camerad/camerad.h | 319 +- camerad/fits.h | 789 +- camerad/generic.cpp | 29 +- common/CMakeLists.txt | 8 +- common/common.cpp | 490 +- common/common.h | 299 +- emulator/CMakeLists.txt | 84 +- emulator/emulator-archon.cpp | 1519 ++-- emulator/emulator-archon.h | 392 +- emulator/emulator-server.cpp | 532 +- emulator/emulator-server.h | 312 +- tests/utility_tests.cpp | 2 +- utils/CMakeLists.txt | 20 +- utils/config.h | 248 +- utils/daemonize.h | 248 +- utils/listener.c | 38 +- utils/logentry.cpp | 211 +- utils/logentry.h | 12 +- utils/md5.c | 291 +- utils/utilities.cpp | 1466 ++-- utils/utilities.h | 227 +- 28 files changed, 12871 insertions(+), 12208 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 048c0ac4..75c9e1a1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,45 +10,45 @@ # Interface type can be set via command line ( Archon | AstroCam ) -IF(NOT DEFINED INTERFACE_TYPE) +IF (NOT DEFINED INTERFACE_TYPE) set(INTERFACE_TYPE "Archon") -endif() +endif () # Instrument can be set via command line generic is the default value -IF(NOT DEFINED INSTR) +IF (NOT DEFINED INSTR) set(INSTR "generic") -endif() +endif () # Detector type can be set via command line ( Hxrg | Ccd ) -if(NOT DEFINED DETECTOR_TYPE) +if (NOT DEFINED DETECTOR_TYPE) set(DETECTOR_TYPE "Ccd") -endif() +endif () # ---------------------------------------------------------------------------- # un-comment the following to log verbose debug messages #add_definitions(-DLOGLEVEL_DEBUG) -cmake_minimum_required( VERSION 3.12 ) +cmake_minimum_required(VERSION 3.12) -set( CMAKE_CXX_STANDARD 17 ) -set( CMAKE_CXX_STANDARD_REQUIRED ON ) -set( CMAKE_CXX_EXTENSIONS OFF ) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) -project( camera ) +project(camera) # Run "cmake .." from the project's build/ directory! # -set( PROJECT_BASE_DIR $ENV{PWD}/../ ) +set(PROJECT_BASE_DIR $ENV{PWD}/../) -set( EXECUTABLE_OUTPUT_PATH ${PROJECT_BASE_DIR}/bin ) -set( LIBRARY_OUTPUT_PATH ${PROJECT_BASE_DIR}/lib ) -set( CMAKE_C_COMPILER /usr/bin/g++ ) -set( CMAKE_CXX_COMPILER /usr/bin/g++ ) +set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BASE_DIR}/bin) +set(LIBRARY_OUTPUT_PATH ${PROJECT_BASE_DIR}/lib) +set(CMAKE_C_COMPILER /usr/bin/g++) +set(CMAKE_CXX_COMPILER /usr/bin/g++) -find_package( Threads ) +find_package(Threads) -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}/tests ) +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}/tests) diff --git a/camerad/CMakeLists.txt b/camerad/CMakeLists.txt index 7827f028..cbd40e25 100644 --- a/camerad/CMakeLists.txt +++ b/camerad/CMakeLists.txt @@ -2,13 +2,13 @@ # camerad/CMakeLists.txt # ---------------------------------------------------------------------------- -cmake_minimum_required( VERSION 3.12 ) +cmake_minimum_required(VERSION 3.12) -set( CAMERA_DIR ${PROJECT_BASE_DIR}/camerad ) +set(CAMERA_DIR ${PROJECT_BASE_DIR}/camerad) -include_directories( ${PROJECT_BASE_DIR}/utils ) -include_directories( ${PROJECT_BASE_DIR}/common ) -link_directories( ${PROJECT_BASE_DIR}/lib ) +include_directories(${PROJECT_BASE_DIR}/utils) +include_directories(${PROJECT_BASE_DIR}/common) +link_directories(${PROJECT_BASE_DIR}/lib) # ---------------------------------------------------------------------------- # Setup for appropriate hardware interface... @@ -24,65 +24,65 @@ link_directories( ${PROJECT_BASE_DIR}/lib ) # AstroCam ARC-64/66 PCI/e interfaces # ---------------------------------------------------------------------------- if (${INTERFACE_TYPE} STREQUAL "AstroCam") - message( STATUS "compiling for AstroCam GenIII PCI/PCIe") - set( INTERFACE_TARGET astrocam) - set( ARCAPI_DIR "/opt/ARC_API/3.6") - add_definitions( -Wall -ansi -O1 -Wno-variadic-macros -std=c++17 -ggdb ) - add_definitions( -DASTROCAM) - add_definitions( -DARC66_PCIE) - find_path( ARCAPI_BASE "CArcBase.h" PATHS ${ARCAPI_DIR}/CArcBase/inc) - find_path( ARCAPI_FITS "CArcFitsFile.h" PATHS ${ARCAPI_DIR}/CArcFitsFile/inc) - find_path( ARCAPI_CFITS "CArcFitsFile.h" PATHS ${ARCAPI_DIR}/CArcFitsFile/inc) - find_path( ARCAPI_DEVICE "CArcDevice.h" PATHS ${ARCAPI_DIR}/CArcDevice/inc) - find_path( ARCAPI_PCI "CArcPCI.h" PATHS ${ARCAPI_DIR}/CArcDevice/inc) - find_path( ARCAPI_EXPIFACE "CExpIFace.h" PATHS ${ARCAPI_DIR}/CArcDevice/inc) - find_path( ARCAPI_CONIFACE "CConIFace.h" PATHS ${ARCAPI_DIR}/CArcDevice/inc) - find_path( ARCAPI_DEFS "ArcDefs.h" PATHS ${ARCAPI_DIR}/CArcDevice/inc) - - set(INTERFACE_SOURCE - "${CAMERA_DIR}/astrocam.cpp" - ) - - set(INTERFACE_INCLUDES - "${ARC_INTERFACE}" - "${ARCAPI_BASE}" - "${ARCAPI_FITS}" - "${ARCAPI_CFITS}" - "${ARCAPI_DEVICE}" - "${ARCAPI_PCI}" - "${ARCAPI_EXPIFACE}" - "${ARCAPI_CONIFACE}" - "${ARCAPI_DEFS}" - ) - find_library(CARC_BASE "CArcBase3.6" NAMES "libCArcBase3.6" PATHS ${ARCAPI_DIR}/Release) - find_library(CARC_DEVICE "CArcDevice3.6" NAMES "libCArcDevice3.6.so" PATHS ${ARCAPI_DIR}/Release) - find_library(CARC_FITS "CArcFitsFile3.6" NAMES "libCArcFitsFile3.6.so" PATHS ${ARCAPI_DIR}/Release) - -# ---------------------------------------------------------------------------- -# STA Archon interfaces -# ---------------------------------------------------------------------------- - -elseif(${INTERFACE_TYPE} STREQUAL "Archon") - message(STATUS "compiling for STA Archon") - set(INTERFACE_TARGET archon) - add_definitions( -Wall -ansi -O1 -Wno-variadic-macros -std=c++17 -ggdb ) - add_definitions(-DSTA_ARCHON) - list(APPEND INTERFACE_SOURCE - "${CAMERA_DIR}/archon.cpp" - ) - list(APPEND INTERFACE_INCLUDES - "${ARCHON_INCLUDE}" - ) - add_library( interface STATIC "${INTERFACE_SOURCE}" ) - if (${DETECTOR_TYPE} STREQUAL "Hxrg") - message(STATUS "compiling for HXRG detector") - add_definitions(-DDET_HXRG) - else() - message(STATUS "compiling for CCD detector") - endif() -else() - message(FATAL_ERROR " unknown interface type: " ${INTERFACE_TYPE}) -endif() + message(STATUS "compiling for AstroCam GenIII PCI/PCIe") + set(INTERFACE_TARGET astrocam) + set(ARCAPI_DIR "/opt/ARC_API/3.6") + add_definitions(-Wall -ansi -O1 -Wno-variadic-macros -std=c++17 -ggdb) + add_definitions(-DASTROCAM) + add_definitions(-DARC66_PCIE) + find_path(ARCAPI_BASE "CArcBase.h" PATHS ${ARCAPI_DIR}/CArcBase/inc) + find_path(ARCAPI_FITS "CArcFitsFile.h" PATHS ${ARCAPI_DIR}/CArcFitsFile/inc) + find_path(ARCAPI_CFITS "CArcFitsFile.h" PATHS ${ARCAPI_DIR}/CArcFitsFile/inc) + find_path(ARCAPI_DEVICE "CArcDevice.h" PATHS ${ARCAPI_DIR}/CArcDevice/inc) + find_path(ARCAPI_PCI "CArcPCI.h" PATHS ${ARCAPI_DIR}/CArcDevice/inc) + find_path(ARCAPI_EXPIFACE "CExpIFace.h" PATHS ${ARCAPI_DIR}/CArcDevice/inc) + find_path(ARCAPI_CONIFACE "CConIFace.h" PATHS ${ARCAPI_DIR}/CArcDevice/inc) + find_path(ARCAPI_DEFS "ArcDefs.h" PATHS ${ARCAPI_DIR}/CArcDevice/inc) + + set(INTERFACE_SOURCE + "${CAMERA_DIR}/astrocam.cpp" + ) + + set(INTERFACE_INCLUDES + "${ARC_INTERFACE}" + "${ARCAPI_BASE}" + "${ARCAPI_FITS}" + "${ARCAPI_CFITS}" + "${ARCAPI_DEVICE}" + "${ARCAPI_PCI}" + "${ARCAPI_EXPIFACE}" + "${ARCAPI_CONIFACE}" + "${ARCAPI_DEFS}" + ) + find_library(CARC_BASE "CArcBase3.6" NAMES "libCArcBase3.6" PATHS ${ARCAPI_DIR}/Release) + find_library(CARC_DEVICE "CArcDevice3.6" NAMES "libCArcDevice3.6.so" PATHS ${ARCAPI_DIR}/Release) + find_library(CARC_FITS "CArcFitsFile3.6" NAMES "libCArcFitsFile3.6.so" PATHS ${ARCAPI_DIR}/Release) + + # ---------------------------------------------------------------------------- + # STA Archon interfaces + # ---------------------------------------------------------------------------- + +elseif (${INTERFACE_TYPE} STREQUAL "Archon") + message(STATUS "compiling for STA Archon") + set(INTERFACE_TARGET archon) + add_definitions(-Wall -ansi -O1 -Wno-variadic-macros -std=c++17 -ggdb) + add_definitions(-DSTA_ARCHON) + list(APPEND INTERFACE_SOURCE + "${CAMERA_DIR}/archon.cpp" + ) + list(APPEND INTERFACE_INCLUDES + "${ARCHON_INCLUDE}" + ) + add_library(interface STATIC "${INTERFACE_SOURCE}") + if (${DETECTOR_TYPE} STREQUAL "Hxrg") + message(STATUS "compiling for HXRG detector") + add_definitions(-DDET_HXRG) + else () + message(STATUS "compiling for CCD detector") + endif () +else () + message(FATAL_ERROR " unknown interface type: " ${INTERFACE_TYPE}) +endif () # ---------------------------------------------------------------------------- # Add appropriate instrument library here. @@ -90,24 +90,24 @@ endif() # Currently supported names are: nirc2 generic # ---------------------------------------------------------------------------- -if ( NOT DEFINED INSTR ) - set( INSTR "undefined" ) -else() - if ( ${INSTR} STREQUAL "nirc2" ) - message( STATUS "building for nirc2 instrument" ) - list( APPEND INTERFACE_SOURCE "${CAMERA_DIR}/nirc2.cpp" ) - elseif( ${INSTR} STREQUAL "generic" ) - message( STATUS "building for generic instrument" ) - list( APPEND INTERFACE_SOURCE "${CAMERA_DIR}/generic.cpp" ) - elseif( ${INSTR} STREQUAL "undefined" ) - message( STATUS "no INSTR defined. using generic but other options are:" ) - message( STATUS "cmake -DINSTR=nirc2,generic .." ) - list( APPEND INTERFACE_SOURCE "${CAMERA_DIR}/generic.cpp" ) - else() - message( STATUS "unknown instrument " ${INSTR} ": using generic" ) - list( APPEND INTERFACE_SOURCE "${CAMERA_DIR}/generic.cpp" ) - endif() -endif() +if (NOT DEFINED INSTR) + set(INSTR "undefined") +else () + if (${INSTR} STREQUAL "nirc2") + message(STATUS "building for nirc2 instrument") + list(APPEND INTERFACE_SOURCE "${CAMERA_DIR}/nirc2.cpp") + elseif (${INSTR} STREQUAL "generic") + message(STATUS "building for generic instrument") + list(APPEND INTERFACE_SOURCE "${CAMERA_DIR}/generic.cpp") + elseif (${INSTR} STREQUAL "undefined") + message(STATUS "no INSTR defined. using generic but other options are:") + message(STATUS "cmake -DINSTR=nirc2,generic ..") + list(APPEND INTERFACE_SOURCE "${CAMERA_DIR}/generic.cpp") + else () + message(STATUS "unknown instrument " ${INSTR} ": using generic") + list(APPEND INTERFACE_SOURCE "${CAMERA_DIR}/generic.cpp") + endif () +endif () # ---------------------------------------------------------------------------- # End interface setup @@ -121,21 +121,21 @@ target_include_directories(${INTERFACE_TARGET} PUBLIC ${INTERFACE_INCLUDES}) add_library(camera STATIC ${CAMERA_DIR}/camera.cpp - ) +) # ---------------------------------------------------------------------------- # External libraries, such as FITS, etc. # ---------------------------------------------------------------------------- find_library(CCFITS_LIB CCfits NAMES libCCfits PATHS /usr/local/lib) -find_library(CFITS_LIB cfitsio NAMES libcfitsio PATHS /usr/local/lib) +find_library(CFITS_LIB cfitsio NAMES libcfitsio PATHS /usr/local/lib) find_package(Threads) -add_executable(camerad - ${CAMERA_DIR}/camerad.cpp +add_executable(camerad + ${CAMERA_DIR}/camerad.cpp ${INTERFACE_INCLUDES} - ) +) # ---------------------------------------------------------------------------- # Everyone gets this: @@ -151,7 +151,7 @@ target_link_libraries(camerad ${CMAKE_THREAD_LIBS_INIT} ${CCFITS_LIB} ${CFITS_LIB} - ) +) target_link_libraries(camerad ${CARC_BASE}) target_link_libraries(camerad ${CARC_DEVICE}) @@ -166,8 +166,8 @@ target_link_libraries(camerad ${CARC_FITS}) include(FindCURL) if (CURL_FOUND STREQUAL "FALSE") - message(STATUS "cURL was not found but may be needed by some systems if built into cfitsio.\n If you experience undefined curl references during linking \n then please your cURL installation.") -else() - target_link_libraries(camerad ${CURL_LIBRARIES}) -endif() + message(STATUS "cURL was not found but may be needed by some systems if built into cfitsio.\n If you experience undefined curl references during linking \n then please your cURL installation.") +else () + target_link_libraries(camerad ${CURL_LIBRARIES}) +endif () diff --git a/camerad/archon.cpp b/camerad/archon.cpp index 8e6cef22..1aa8cb64 100644 --- a/camerad/archon.cpp +++ b/camerad/archon.cpp @@ -10,7 +10,7 @@ #include // for std::stringstream #include // for setfil, setw, etc. #include // for hex, uppercase, etc. -#include +#include #include #include #include @@ -18,1731 +18,1812 @@ #include namespace Archon { - - // Archon::Interface constructor - // - Interface::Interface() { - this->archon_busy = false; - this->modeselected = false; - this->firmwareloaded = false; - this->msgref = 0; - this->lastframe = 0; - this->frame.index = 0; - this->frame.next_index = 0; - this->abort = false; - this->taplines = 0; - this->image_data = nullptr; - this->image_data_bytes = 0; - this->image_data_allocated = 0; - this->is_longexposure = false; - this->is_window = false; - this->is_autofetch = false; - this->win_hstart = 0; - this->win_hstop = 2047; - this->win_vstart = 0; - this->win_vstop = 2047; - this->tapline0_store = ""; - this->taplines_store = 0; - - this->n_hdrshift = 16; - this->backplaneversion=""; - - this->trigin_state="disabled"; - this->trigin_expose = 0; - this->trigin_untimed = 0; - this->trigin_readout = 0; - - this->lastcubeamps = this->camera.cubeamps(); - - this->trigin_expose_enable = DEF_TRIGIN_EXPOSE_ENABLE; - this->trigin_expose_disable = DEF_TRIGIN_EXPOSE_DISABLE; - this->trigin_untimed_enable = DEF_TRIGIN_UNTIMED_ENABLE; - this->trigin_untimed_disable = DEF_TRIGIN_UNTIMED_DISABLE; - this->trigin_readout_enable = DEF_TRIGIN_READOUT_ENABLE; - this->trigin_readout_disable = DEF_TRIGIN_READOUT_DISABLE; - - this->shutenable_enable = DEF_SHUTENABLE_ENABLE; - this->shutenable_disable = DEF_SHUTENABLE_DISABLE; - - // pre-size the modtype and modversion vectors to hold the max number of modules + // Archon::Interface constructor // - this->modtype.resize( nmods ); - this->modversion.resize( nmods ); + Interface::Interface() { + this->archon_busy = false; + this->modeselected = false; + this->firmwareloaded = false; + this->msgref = 0; + this->lastframe = 0; + this->frame.index = 0; + this->frame.next_index = 0; + this->abort = false; + this->taplines = 0; + this->image_data = nullptr; + this->image_data_bytes = 0; + this->image_data_allocated = 0; + this->is_longexposure = false; + this->is_window = false; + this->is_autofetch = false; + this->win_hstart = 0; + this->win_hstop = 2047; + this->win_vstart = 0; + this->win_vstop = 2047; + this->tapline0_store = ""; + this->taplines_store = 0; + + this->n_hdrshift = 16; + this->backplaneversion = ""; + + this->trigin_state = "disabled"; + this->trigin_expose = 0; + this->trigin_untimed = 0; + this->trigin_readout = 0; + + this->lastcubeamps = this->camera.cubeamps(); + + this->trigin_expose_enable = DEF_TRIGIN_EXPOSE_ENABLE; + this->trigin_expose_disable = DEF_TRIGIN_EXPOSE_DISABLE; + this->trigin_untimed_enable = DEF_TRIGIN_UNTIMED_ENABLE; + this->trigin_untimed_disable = DEF_TRIGIN_UNTIMED_DISABLE; + this->trigin_readout_enable = DEF_TRIGIN_READOUT_ENABLE; + this->trigin_readout_disable = DEF_TRIGIN_READOUT_DISABLE; + + this->shutenable_enable = DEF_SHUTENABLE_ENABLE; + this->shutenable_disable = DEF_SHUTENABLE_DISABLE; + + // pre-size the modtype and modversion vectors to hold the max number of modules + // + this->modtype.resize(nmods); + this->modversion.resize(nmods); - // TODO I should change these to STL maps instead - // - this->frame.bufsample.resize( Archon::nbufs ); - this->frame.bufcomplete.resize( Archon::nbufs ); - this->frame.bufmode.resize( Archon::nbufs ); - this->frame.bufbase.resize( Archon::nbufs ); - this->frame.bufframen.resize( Archon::nbufs ); - this->frame.bufwidth.resize( Archon::nbufs ); - this->frame.bufheight.resize( Archon::nbufs ); - this->frame.bufpixels.resize( Archon::nbufs ); - this->frame.buflines.resize( Archon::nbufs ); - this->frame.bufrawblocks.resize( Archon::nbufs ); - this->frame.bufrawlines.resize( Archon::nbufs ); - this->frame.bufrawoffset.resize( Archon::nbufs ); - this->frame.buftimestamp.resize( Archon::nbufs ); - this->frame.bufretimestamp.resize( Archon::nbufs ); - this->frame.buffetimestamp.resize( Archon::nbufs ); - } - - // Archon::Interface deconstructor - // - Interface::~Interface() = default; - - - /**************** Archon::Interface::interface ******************************/ - long Interface::interface(std::string &iface) { - std::string function = "Archon::Interface::interface"; - iface = "STA-Archon"; - logwrite(function, iface); - return 0; - } - /**************** Archon::Interface::interface ******************************/ - - - /***** Archon::Interface::configure_controller ******************************/ - /** - * @brief parse controller-related keys from the configuration file - * @details the config file was read by server.config.read_config() in main() - * @return ERROR or NO_ERROR - * - */ - long Interface::configure_controller() { - std::string function = "Archon::Interface::configure_controller"; - std::stringstream message; - int applied=0; - long error; - - // Must re-init all values to start-up defaults in case this function is - // called again to re-load the config file (such as if a HUP is received) - // and the new config file may not have everything defined. - // This ensures nothing is carried over from any previous config. + // TODO I should change these to STL maps instead + // + this->frame.bufsample.resize(Archon::nbufs); + this->frame.bufcomplete.resize(Archon::nbufs); + this->frame.bufmode.resize(Archon::nbufs); + this->frame.bufbase.resize(Archon::nbufs); + this->frame.bufframen.resize(Archon::nbufs); + this->frame.bufwidth.resize(Archon::nbufs); + this->frame.bufheight.resize(Archon::nbufs); + this->frame.bufpixels.resize(Archon::nbufs); + this->frame.buflines.resize(Archon::nbufs); + this->frame.bufrawblocks.resize(Archon::nbufs); + this->frame.bufrawlines.resize(Archon::nbufs); + this->frame.bufrawoffset.resize(Archon::nbufs); + this->frame.buftimestamp.resize(Archon::nbufs); + this->frame.bufretimestamp.resize(Archon::nbufs); + this->frame.buffetimestamp.resize(Archon::nbufs); + } + + // Archon::Interface deconstructor // - this->camera_info.hostname = ""; - this->archon.sethost( "" ); - this->camera_info.port = -1; - this->archon.setport( -1 ); + Interface::~Interface() = default; - this->is_longexposure = false; - this->n_hdrshift = 16; - this->camera.firmware[0] = ""; + /**************** Archon::Interface::interface ******************************/ + long Interface::interface(std::string &iface) { + std::string function = "Archon::Interface::interface"; + iface = "STA-Archon"; + logwrite(function, iface); + return 0; + } - this->exposeparam = ""; - this->trigin_exposeparam = ""; - this->trigin_untimedparam = ""; - this->trigin_readoutparam = ""; + /**************** Archon::Interface::interface ******************************/ - this->trigin_expose_enable = DEF_TRIGIN_EXPOSE_ENABLE; - this->trigin_expose_disable = DEF_TRIGIN_EXPOSE_DISABLE; - this->trigin_untimed_enable = DEF_TRIGIN_UNTIMED_ENABLE; - this->trigin_untimed_disable = DEF_TRIGIN_UNTIMED_DISABLE; - this->trigin_readout_enable = DEF_TRIGIN_READOUT_ENABLE; - this->trigin_readout_disable = DEF_TRIGIN_READOUT_DISABLE; - this->shutenable_enable = DEF_SHUTENABLE_ENABLE; - this->shutenable_disable = DEF_SHUTENABLE_DISABLE; + /***** Archon::Interface::configure_controller ******************************/ + /** + * @brief parse controller-related keys from the configuration file + * @details the config file was read by server.config.read_config() in main() + * @return ERROR or NO_ERROR + * + */ + long Interface::configure_controller() { + std::string function = "Archon::Interface::configure_controller"; + std::stringstream message; + int applied = 0; + long error; - // loop through the entries in the configuration file, stored in config class - // - for (int entry=0; entry < this->config.n_entries; entry++) { + // Must re-init all values to start-up defaults in case this function is + // called again to re-load the config file (such as if a HUP is received) + // and the new config file may not have everything defined. + // This ensures nothing is carried over from any previous config. + // + this->camera_info.hostname = ""; + this->archon.sethost(""); + this->camera_info.port = -1; + this->archon.setport(-1); - if (config.param[entry].compare(0, 9, "ARCHON_IP")==0) { - this->camera_info.hostname = config.arg[entry]; - this->archon.sethost( config.arg[entry] ); - message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite( function, message.str() ); - this->camera.async.enqueue( message.str() ); - applied++; - } + this->is_longexposure = false; + this->n_hdrshift = 16; - if (config.param[entry].compare(0, 11, "ARCHON_PORT")==0) { // ARCHON_PORT - int port; - try { - port = std::stoi( config.arg[entry] ); + this->camera.firmware[0] = ""; - } catch (std::invalid_argument &) { - this->camera.log_error( function, "unable to convert port number to integer" ); - return ERROR; + this->exposeparam = ""; + this->trigin_exposeparam = ""; + this->trigin_untimedparam = ""; + this->trigin_readoutparam = ""; - } catch (std::out_of_range &) { - this->camera.log_error( function, "port number out of integer range" ); - return ERROR; - } - this->camera_info.port = port; - this->archon.setport(port); - message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite( function, message.str() ); - this->camera.async.enqueue( message.str() ); - applied++; - } + this->trigin_expose_enable = DEF_TRIGIN_EXPOSE_ENABLE; + this->trigin_expose_disable = DEF_TRIGIN_EXPOSE_DISABLE; + this->trigin_untimed_enable = DEF_TRIGIN_UNTIMED_ENABLE; + this->trigin_untimed_disable = DEF_TRIGIN_UNTIMED_DISABLE; + this->trigin_readout_enable = DEF_TRIGIN_READOUT_ENABLE; + this->trigin_readout_disable = DEF_TRIGIN_READOUT_DISABLE; - if (config.param[entry].compare(0, 12, "AMPS_AS_CUBE")==0) { - std::string dontcare; - if ( this->camera.cubeamps( config.arg[entry], dontcare ) == ERROR ) { - this->camera.log_error( function, "setting cubeamps" ); - return ERROR; - } - } + this->shutenable_enable = DEF_SHUTENABLE_ENABLE; + this->shutenable_disable = DEF_SHUTENABLE_DISABLE; - if (config.param[entry].compare(0, 12, "EXPOSE_PARAM")==0) { // EXPOSE_PARAM - this->exposeparam = config.arg[entry]; - message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite( function, message.str() ); - this->camera.async.enqueue( message.str() ); - applied++; - } + // loop through the entries in the configuration file, stored in config class + // + for (int entry = 0; entry < this->config.n_entries; entry++) { + if (config.param[entry].compare(0, 9, "ARCHON_IP") == 0) { + this->camera_info.hostname = config.arg[entry]; + this->archon.sethost(config.arg[entry]); + message.str(""); + message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite(function, message.str()); + this->camera.async.enqueue(message.str()); + applied++; + } - if (config.param[entry].compare(0, 19, "TRIGIN_EXPOSE_PARAM")==0) { // TRIGIN_EXPOSE_PARAM - this->trigin_exposeparam = config.arg[entry]; - message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite( function, message.str() ); - this->camera.async.enqueue( message.str() ); - applied++; - } + if (config.param[entry].compare(0, 11, "ARCHON_PORT") == 0) { + // ARCHON_PORT + int port; + try { + port = std::stoi(config.arg[entry]); + } catch (std::invalid_argument &) { + this->camera.log_error(function, "unable to convert port number to integer"); + return ERROR; + } catch (std::out_of_range &) { + this->camera.log_error(function, "port number out of integer range"); + return ERROR; + } + this->camera_info.port = port; + this->archon.setport(port); + message.str(""); + message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite(function, message.str()); + this->camera.async.enqueue(message.str()); + applied++; + } - if (config.param[entry].compare(0, 20, "TRIGIN_UNTIMED_PARAM")==0) { // TRIGIN_UNTIMED_PARAM - this->trigin_untimedparam = config.arg[entry]; - message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite( function, message.str() ); - this->camera.async.enqueue( message.str() ); - applied++; - } + if (config.param[entry].compare(0, 12, "AMPS_AS_CUBE") == 0) { + std::string dontcare; + if (this->camera.cubeamps(config.arg[entry], dontcare) == ERROR) { + this->camera.log_error(function, "setting cubeamps"); + return ERROR; + } + } - if (config.param[entry].compare(0, 20, "TRIGIN_READOUT_PARAM")==0) { // TRIGIN_READOUT_PARAM - this->trigin_readoutparam = config.arg[entry]; - message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite( function, message.str() ); - this->camera.async.enqueue( message.str() ); - applied++; - } + if (config.param[entry].compare(0, 12, "EXPOSE_PARAM") == 0) { + // EXPOSE_PARAM + this->exposeparam = config.arg[entry]; + message.str(""); + message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite(function, message.str()); + this->camera.async.enqueue(message.str()); + applied++; + } - if (config.param[entry].compare(0, 20, "TRIGIN_EXPOSE_ENABLE")==0) { // TRIGIN_EXPOSE_ENABLE - int enable; - try { - enable = std::stoi( config.arg[entry] ); + if (config.param[entry].compare(0, 19, "TRIGIN_EXPOSE_PARAM") == 0) { + // TRIGIN_EXPOSE_PARAM + this->trigin_exposeparam = config.arg[entry]; + message.str(""); + message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite(function, message.str()); + this->camera.async.enqueue(message.str()); + applied++; + } - } catch (std::invalid_argument &) { - this->camera.log_error( function, "unable to convert TRIGIN_EXPOSE_ENABLE to integer" ); - return ERROR; + if (config.param[entry].compare(0, 20, "TRIGIN_UNTIMED_PARAM") == 0) { + // TRIGIN_UNTIMED_PARAM + this->trigin_untimedparam = config.arg[entry]; + message.str(""); + message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite(function, message.str()); + this->camera.async.enqueue(message.str()); + applied++; + } - } catch (std::out_of_range &) { - this->camera.log_error( function, "TRIGIN_EXPOSE_ENABLE out of integer range" ); - return ERROR; - } - this->trigin_expose_enable = enable; - message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite( function, message.str() ); - this->camera.async.enqueue( message.str() ); - applied++; - } + if (config.param[entry].compare(0, 20, "TRIGIN_READOUT_PARAM") == 0) { + // TRIGIN_READOUT_PARAM + this->trigin_readoutparam = config.arg[entry]; + message.str(""); + message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite(function, message.str()); + this->camera.async.enqueue(message.str()); + applied++; + } - if (config.param[entry].compare(0, 21, "TRIGIN_EXPOSE_DISABLE")==0) { // TRIGIN_EXPOSE_DISABLE - int disable; - try { - disable = std::stoi( config.arg[entry] ); + if (config.param[entry].compare(0, 20, "TRIGIN_EXPOSE_ENABLE") == 0) { + // TRIGIN_EXPOSE_ENABLE + int enable; + try { + enable = std::stoi(config.arg[entry]); + } catch (std::invalid_argument &) { + this->camera.log_error(function, "unable to convert TRIGIN_EXPOSE_ENABLE to integer"); + return ERROR; + } catch (std::out_of_range &) { + this->camera.log_error(function, "TRIGIN_EXPOSE_ENABLE out of integer range"); + return ERROR; + } + this->trigin_expose_enable = enable; + message.str(""); + message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite(function, message.str()); + this->camera.async.enqueue(message.str()); + applied++; + } - } catch (std::invalid_argument &) { - this->camera.log_error( function, "unable to convert TRIGIN_EXPOSE_DISABLE to integer" ); - return ERROR; + if (config.param[entry].compare(0, 21, "TRIGIN_EXPOSE_DISABLE") == 0) { + // TRIGIN_EXPOSE_DISABLE + int disable; + try { + disable = std::stoi(config.arg[entry]); + } catch (std::invalid_argument &) { + this->camera.log_error(function, "unable to convert TRIGIN_EXPOSE_DISABLE to integer"); + return ERROR; + } catch (std::out_of_range &) { + this->camera.log_error(function, "TRIGIN_EXPOSE_DISABLE out of integer range"); + return ERROR; + } + this->trigin_expose_disable = disable; + message.str(""); + message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite(function, message.str()); + this->camera.async.enqueue(message.str()); + applied++; + } - } catch (std::out_of_range &) { - this->camera.log_error( function, "TRIGIN_EXPOSE_DISABLE out of integer range" ); - return ERROR; - } - this->trigin_expose_disable = disable; - message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite( function, message.str() ); - this->camera.async.enqueue( message.str() ); - applied++; - } + if (config.param[entry].compare(0, 21, "TRIGIN_UNTIMED_ENABLE") == 0) { + // TRIGIN_UNTIMED_ENABLE + int enable; + try { + enable = std::stoi(config.arg[entry]); + } catch (std::invalid_argument &) { + this->camera.log_error(function, "unable to convert TRIGIN_UNTIMED_ENABLE to integer"); + return ERROR; + } catch (std::out_of_range &) { + this->camera.log_error(function, "TRIGIN_UNTIMED_ENABLE out of integer range"); + return ERROR; + } + this->trigin_untimed_enable = enable; + message.str(""); + message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite(function, message.str()); + this->camera.async.enqueue(message.str()); + applied++; + } - if (config.param[entry].compare(0, 21, "TRIGIN_UNTIMED_ENABLE")==0) { // TRIGIN_UNTIMED_ENABLE - int enable; - try { - enable = std::stoi( config.arg[entry] ); + if (config.param[entry].compare(0, 22, "TRIGIN_UNTIMED_DISABLE") == 0) { + // TRIGIN_UNTIMED_DISABLE + int disable; + try { + disable = std::stoi(config.arg[entry]); + } catch (std::invalid_argument &) { + this->camera.log_error(function, "unable to convert TRIGIN_UNTIMED_DISABLE to integer"); + return ERROR; + } catch (std::out_of_range &) { + this->camera.log_error(function, "TRIGIN_UNTIMED_DISABLE out of integer range"); + return ERROR; + } + this->trigin_untimed_disable = disable; + message.str(""); + message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite(function, message.str()); + this->camera.async.enqueue(message.str()); + applied++; + } - } catch (std::invalid_argument &) { - this->camera.log_error( function, "unable to convert TRIGIN_UNTIMED_ENABLE to integer" ); - return ERROR; + if (config.param[entry].compare(0, 21, "TRIGIN_READOUT_ENABLE") == 0) { + // TRIGIN_READOUT_ENABLE + int enable; + try { + enable = std::stoi(config.arg[entry]); + } catch (std::invalid_argument &) { + this->camera.log_error(function, "unable to convert TRIGIN_READOUT_ENABLE to integer"); + return ERROR; + } catch (std::out_of_range &) { + this->camera.log_error(function, "TRIGIN_READOUT_ENABLE out of integer range"); + return ERROR; + } + this->trigin_readout_enable = enable; + message.str(""); + message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite(function, message.str()); + this->camera.async.enqueue(message.str()); + applied++; + } - } catch (std::out_of_range &) { - this->camera.log_error( function, "TRIGIN_UNTIMED_ENABLE out of integer range" ); - return ERROR; - } - this->trigin_untimed_enable = enable; - message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite( function, message.str() ); - this->camera.async.enqueue( message.str() ); - applied++; - } + if (config.param[entry].compare(0, 22, "TRIGIN_READOUT_DISABLE") == 0) { + // TRIGIN_READOUT_DISABLE + int disable; + try { + disable = std::stoi(config.arg[entry]); + } catch (std::invalid_argument &) { + this->camera.log_error(function, "unable to convert TRIGIN_READOUT_DISABLE to integer"); + return ERROR; + } catch (std::out_of_range &) { + this->camera.log_error(function, "TRIGIN_READOUT_DISABLE out of integer range"); + return ERROR; + } + this->trigin_readout_disable = disable; + message.str(""); + message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite(function, message.str()); + this->camera.async.enqueue(message.str()); + applied++; + } - if (config.param[entry].compare(0, 22, "TRIGIN_UNTIMED_DISABLE")==0) { // TRIGIN_UNTIMED_DISABLE - int disable; - try { - disable = std::stoi( config.arg[entry] ); + if (config.param[entry].compare(0, 16, "SHUTENABLE_PARAM") == 0) { + // SHUTENABLE_PARAM + this->shutenableparam = config.arg[entry]; + message.str(""); + message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite(function, message.str()); + this->camera.async.enqueue(message.str()); + applied++; + } - } catch (std::invalid_argument &) { - this->camera.log_error( function, "unable to convert TRIGIN_UNTIMED_DISABLE to integer" ); - return ERROR; + if (config.param[entry].compare(0, 17, "SHUTENABLE_ENABLE") == 0) { + // SHUTENABLE_ENABLE + int enable; + try { + enable = std::stoi(config.arg[entry]); + } catch (std::invalid_argument &) { + this->camera.log_error(function, "unable to convert SHUTENABLE_ENABLE to integer"); + return ERROR; + } catch (std::out_of_range &) { + this->camera.log_error(function, "SHUTENABLE_ENABLE out of integer range"); + return ERROR; + } + this->shutenable_enable = enable; + message.str(""); + message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite(function, message.str()); + this->camera.async.enqueue(message.str()); + applied++; + } - } catch (std::out_of_range &) { - this->camera.log_error( function, "TRIGIN_UNTIMED_DISABLE out of integer range" ); - return ERROR; - } - this->trigin_untimed_disable = disable; - message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite( function, message.str() ); - this->camera.async.enqueue( message.str() ); - applied++; - } + if (config.param[entry].compare(0, 18, "SHUTENABLE_DISABLE") == 0) { + // SHUTENABLE_DISABLE + int disable; + try { + disable = std::stoi(config.arg[entry]); + } catch (std::invalid_argument &) { + this->camera.log_error(function, "unable to convert SHUTENABLE_DISABLE to integer"); + return ERROR; + } catch (std::out_of_range &) { + this->camera.log_error(function, "SHUTENABLE_DISABLE out of integer range"); + return ERROR; + } + this->shutenable_disable = disable; + message.str(""); + message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite(function, message.str()); + this->camera.async.enqueue(message.str()); + applied++; + } - if (config.param[entry].compare(0, 21, "TRIGIN_READOUT_ENABLE")==0) { // TRIGIN_READOUT_ENABLE - int enable; - try { - enable = std::stoi( config.arg[entry] ); + // .firmware and .readout_time are STL maps but (for now) only one Archon per computer + // so map always to 0 + // + if (config.param[entry].compare(0, 16, "DEFAULT_FIRMWARE") == 0) { + this->camera.firmware[0] = config.arg[entry]; + message.str(""); + message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite(function, message.str()); + this->camera.async.enqueue(message.str()); + applied++; + } - } catch (std::invalid_argument &) { - this->camera.log_error( function, "unable to convert TRIGIN_READOUT_ENABLE to integer" ); - return ERROR; + if (config.param[entry].compare(0, 16, "HDR_SHIFT") == 0) { + std::string dontcare; + this->hdrshift(config.arg[entry], dontcare); + message.str(""); + message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite(function, message.str()); + this->camera.async.enqueue(message.str()); + applied++; + } - } catch (std::out_of_range &) { - this->camera.log_error( function, "TRIGIN_READOUT_ENABLE out of integer range" ); - return ERROR; - } - this->trigin_readout_enable = enable; - message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite( function, message.str() ); - this->camera.async.enqueue( message.str() ); - applied++; - } + if (config.param[entry].compare(0, 12, "READOUT_TIME") == 0) { + int readtime; + try { + readtime = std::stoi(config.arg[entry]); + } catch (std::invalid_argument &) { + this->camera.log_error(function, "unable to convert readout time to integer"); + return ERROR; + } catch (std::out_of_range &) { + this->camera.log_error(function, "readout time out of integer range"); + return ERROR; + } + this->camera.readout_time[0] = readtime; + message.str(""); + message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite(function, message.str()); + this->camera.async.enqueue(message.str()); + applied++; + } - if (config.param[entry].compare(0, 22, "TRIGIN_READOUT_DISABLE")==0) { // TRIGIN_READOUT_DISABLE - int disable; - try { - disable = std::stoi( config.arg[entry] ); + if (config.param[entry].compare(0, 5, "IMDIR") == 0) { + this->camera.imdir(config.arg[entry]); + message.str(""); + message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite(function, message.str()); + this->camera.async.enqueue(message.str()); + applied++; + } - } catch (std::invalid_argument &) { - this->camera.log_error( function, "unable to convert TRIGIN_READOUT_DISABLE to integer" ); - return ERROR; + if (config.param[entry].compare(0, 7, "DIRMODE") == 0) { + std::string s(config.arg[entry]); + std::stringstream mode_bit; + mode_t mode = 0; + for (size_t i = 0; i < s.length(); i++) { + try { + mode = (mode << 3); + mode_bit.str(""); + mode_bit << s.at(i); + mode |= std::stoi(mode_bit.str()); + } catch (std::invalid_argument &) { + this->camera.log_error(function, "unable to convert mode bit to integer"); + return ERROR; + } catch (std::out_of_range &) { + this->camera.log_error(function, "out of range converting dirmode bit"); + return ERROR; + } + } + this->camera.set_dirmode(mode); + message.str(""); + message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite(function, message.str()); + this->camera.async.enqueue(message.str()); + applied++; + } - } catch (std::out_of_range &) { - this->camera.log_error( function, "TRIGIN_READOUT_DISABLE out of integer range" ); - return ERROR; + if (config.param[entry].compare(0, 8, "BASENAME") == 0) { + this->camera.basename(config.arg[entry]); + message.str(""); + message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite(function, message.str()); + this->camera.async.enqueue(message.str()); + applied++; + } } - this->trigin_readout_disable = disable; - message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite( function, message.str() ); - this->camera.async.enqueue( message.str() ); - applied++; - } - - if (config.param[entry].compare(0, 16, "SHUTENABLE_PARAM")==0) { // SHUTENABLE_PARAM - this->shutenableparam = config.arg[entry]; - message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite( function, message.str() ); - this->camera.async.enqueue( message.str() ); - applied++; - } - - if (config.param[entry].compare(0, 17, "SHUTENABLE_ENABLE")==0) { // SHUTENABLE_ENABLE - int enable; - try { - enable = std::stoi( config.arg[entry] ); - - } catch (std::invalid_argument &) { - this->camera.log_error( function, "unable to convert SHUTENABLE_ENABLE to integer" ); - return ERROR; - } catch (std::out_of_range &) { - this->camera.log_error( function, "SHUTENABLE_ENABLE out of integer range" ); - return ERROR; + message.str(""); + if (applied == 0) { + message << "ERROR: "; + error = ERROR; + } else { + error = NO_ERROR; } - this->shutenable_enable = enable; - message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite( function, message.str() ); - this->camera.async.enqueue( message.str() ); - applied++; - } + message << "applied " << applied << " configuration lines to controller"; + error == NO_ERROR ? logwrite(function, message.str()) : this->camera.log_error(function, message.str()); + return error; + } - if (config.param[entry].compare(0, 18, "SHUTENABLE_DISABLE")==0) { // SHUTENABLE_DISABLE - int disable; - try { - disable = std::stoi( config.arg[entry] ); + /***** Archon::Interface::configure_controller ******************************/ - } catch (std::invalid_argument &) { - this->camera.log_error( function, "unable to convert SHUTENABLE_DISABLE to integer" ); - return ERROR; - } catch (std::out_of_range &) { - this->camera.log_error( function, "SHUTENABLE_DISABLE out of integer range" ); - return ERROR; + /**************** Archon::Interface::prepare_image_buffer *******************/ + /** + * @fn prepare_image_buffer + * @brief prepare image_data buffer, allocating memory as needed + * @param none + * @return NO_ERROR if successful or ERROR on error + * + */ + long Interface::prepare_image_buffer() { + std::string function = "Archon::Interface::prepare_image_buffer"; + std::stringstream message; + + // If there is already a correctly-sized buffer allocated, + // then don't do anything except initialize that space to zero. + // + if ((this->image_data != nullptr) && + (this->image_data_bytes != 0) && + (this->image_data_allocated == this->image_data_bytes)) { + memset(this->image_data, 0, this->image_data_bytes); + message.str(""); + message << "initialized " << this->image_data_bytes << " bytes of image_data memory"; + logwrite(function, message.str()); + } else { + // If memory needs to be re-allocated, delete the old buffer + if (this->image_data != nullptr) { + logwrite(function, "deleting old image_data buffer"); + delete [] this->image_data; + this->image_data = nullptr; + } + // Allocate new memory + // + if (this->image_data_bytes != 0) { + this->image_data = new char[this->image_data_bytes]; + this->image_data_allocated = this->image_data_bytes; + message.str(""); + message << "allocated " << this->image_data_bytes << " bytes for image_data"; + logwrite(function, message.str()); + } else { + this->camera.log_error(function, "cannot allocate zero-length image memory"); + return ERROR; + } } - this->shutenable_disable = disable; - message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite( function, message.str() ); - this->camera.async.enqueue( message.str() ); - applied++; - } - // .firmware and .readout_time are STL maps but (for now) only one Archon per computer - // so map always to 0 - // - if (config.param[entry].compare(0, 16, "DEFAULT_FIRMWARE")==0) { - this->camera.firmware[0] = config.arg[entry]; - message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite( function, message.str() ); - this->camera.async.enqueue( message.str() ); - applied++; - } + return NO_ERROR; + } - if (config.param[entry].compare(0, 16, "HDR_SHIFT")==0) { - std::string dontcare; - this->hdrshift( config.arg[entry], dontcare ); - message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite( function, message.str() ); - this->camera.async.enqueue( message.str() ); - applied++; - } + /**************** Archon::Interface::prepare_image_buffer *******************/ - if (config.param[entry].compare(0, 12, "READOUT_TIME")==0) { - int readtime; - try { - readtime = std::stoi ( config.arg[entry] ); - } catch (std::invalid_argument &) { - this->camera.log_error( function, "unable to convert readout time to integer" ); - return ERROR; + /**************** Archon::Interface::connect_controller *********************/ + /** + * @fn connect_controller + * @brief + * @param none (devices_in here for future expansion) + * @return + * + */ + long Interface::connect_controller(const std::string &devices_in = "") { + std::string function = "Archon::Interface::connect_controller"; + std::stringstream message; + int adchans = 0; + long error = ERROR; - } catch (std::out_of_range &) { - this->camera.log_error( function, "readout time out of integer range" ); - return ERROR; + if (this->archon.isconnected()) { + logwrite(function, "camera connection already open"); + return NO_ERROR; } - this->camera.readout_time[0] = readtime; - message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite( function, message.str() ); - this->camera.async.enqueue( message.str() ); - applied++; - } - - if (config.param[entry].compare(0, 5, "IMDIR")==0) { - this->camera.imdir( config.arg[entry] ); - message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite( function, message.str() ); - this->camera.async.enqueue( message.str() ); - applied++; - } - if (config.param[entry].compare(0, 7, "DIRMODE")==0) { - std::string s( config.arg[entry] ); - std::stringstream mode_bit; - mode_t mode=0; - for ( size_t i=0; i < s.length(); i++ ) { - try { - mode = (mode << 3); - mode_bit.str(""); mode_bit << s.at(i); - mode |= std::stoi( mode_bit.str() ); - - } catch (std::invalid_argument &) { - this->camera.log_error( function, "unable to convert mode bit to integer" ); - return ERROR; + // Initialize the camera connection + // + logwrite(function, "opening a connection to the camera system"); - } catch (std::out_of_range &) { - this->camera.log_error( function, "out of range converting dirmode bit" ); + if (this->archon.Connect() != 0) { + message.str(""); + message << "connecting to " << this->camera_info.hostname << ":" << this->camera_info.port << ": " << + strerror(errno); + this->camera.log_error(function, message.str()); return ERROR; - } } - this->camera.set_dirmode( mode ); - message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite( function, message.str() ); - this->camera.async.enqueue( message.str() ); - applied++; - } - - if (config.param[entry].compare(0, 8, "BASENAME")==0) { - this->camera.basename( config.arg[entry] ); - message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite( function, message.str() ); - this->camera.async.enqueue( message.str() ); - applied++; - } - - } - - message.str(""); - if (applied==0) { - message << "ERROR: "; - error = ERROR; - } else { - error = NO_ERROR; - } - message << "applied " << applied << " configuration lines to controller"; - error == NO_ERROR ? logwrite( function, message.str() ) : this->camera.log_error( function, message.str() ) ; - return error; - } - /***** Archon::Interface::configure_controller ******************************/ - - - /**************** Archon::Interface::prepare_image_buffer *******************/ - /** - * @fn prepare_image_buffer - * @brief prepare image_data buffer, allocating memory as needed - * @param none - * @return NO_ERROR if successful or ERROR on error - * - */ - long Interface::prepare_image_buffer() { - std::string function = "Archon::Interface::prepare_image_buffer"; - std::stringstream message; - - // If there is already a correctly-sized buffer allocated, - // then don't do anything except initialize that space to zero. - // - if ( (this->image_data != nullptr) && - (this->image_data_bytes != 0) && - (this->image_data_allocated == this->image_data_bytes) ) { - memset(this->image_data, 0, this->image_data_bytes); - message.str(""); message << "initialized " << this->image_data_bytes << " bytes of image_data memory"; - logwrite(function, message.str()); - - } else { - // If memory needs to be re-allocated, delete the old buffer - if (this->image_data != nullptr) { - logwrite(function, "deleting old image_data buffer"); - delete [] this->image_data; - this->image_data=nullptr; - } - // Allocate new memory - // - if (this->image_data_bytes != 0) { - this->image_data = new char[this->image_data_bytes]; - this->image_data_allocated=this->image_data_bytes; - message.str(""); message << "allocated " << this->image_data_bytes << " bytes for image_data"; + message.str(""); + message << "socket connection to " << this->camera_info.hostname << ":" << this->camera_info.port << " " + << "established on fd " << this->archon.getfd(); logwrite(function, message.str()); - } else { - this->camera.log_error( function, "cannot allocate zero-length image memory" ); - return ERROR; - } - } + // Get the current system information for the installed modules + // + std::string reply; + error = this->archon_cmd(SYSTEM, reply); // first the whole reply in one string - return NO_ERROR; - } - /**************** Archon::Interface::prepare_image_buffer *******************/ - - - /**************** Archon::Interface::connect_controller *********************/ - /** - * @fn connect_controller - * @brief - * @param none (devices_in here for future expansion) - * @return - * - */ - long Interface::connect_controller(const std::string& devices_in="") { - std::string function = "Archon::Interface::connect_controller"; - std::stringstream message; - int adchans=0; - long error = ERROR; - - if ( this->archon.isconnected() ) { - logwrite(function, "camera connection already open"); - return NO_ERROR; - } + std::vector lines, tokens; + Tokenize(reply, lines, " "); // then each line in a separate token "lines" - // Initialize the camera connection - // - logwrite(function, "opening a connection to the camera system"); + for (const auto &line: lines) { + Tokenize(line, tokens, "_="); // finally break each line into tokens to get module, type and version + if (tokens.size() != 3) continue; // need 3 tokens - if ( this->archon.Connect() != 0 ) { - message.str(""); message << "connecting to " << this->camera_info.hostname << ":" << this->camera_info.port << ": " << strerror(errno); - this->camera.log_error( function, message.str() ); - return ERROR; - } + std::string version; + int module = 0; + int type = 0; - message.str(""); - message << "socket connection to " << this->camera_info.hostname << ":" << this->camera_info.port << " " - << "established on fd " << this->archon.getfd(); - logwrite(function, message.str()); + // get the module number + // + if (tokens[0].compare(0, 9, "BACKPLANE") == 0) { + if (tokens[1] == "VERSION") this->backplaneversion = tokens[2]; + continue; + } - // Get the current system information for the installed modules - // - std::string reply; - error = this->archon_cmd( SYSTEM, reply ); // first the whole reply in one string + // get the module and type of each module from MODn_TYPE + // + if ((tokens[0].compare(0, 3, "MOD") == 0) && (tokens[1] == "TYPE")) { + try { + module = std::stoi(tokens[0].substr(3)); + type = std::stoi(tokens[2]); + } catch (std::invalid_argument &) { + message.str(""); + message << "unable to convert module or type from " << tokens[0] << "=" << tokens[1] << + " to integer"; + this->camera.log_error(function, message.str()); + return ERROR; + } catch (std::out_of_range &) { + message.str(""); + message << "module " << tokens[0].substr(3) << " or type " << tokens[1] << " out of range"; + this->camera.log_error(function, message.str()); + return ERROR; + } + } else continue; - std::vector lines, tokens; - Tokenize( reply, lines, " " ); // then each line in a separate token "lines" + // get the module version + // + if (tokens[1] == "VERSION") version = tokens[2]; + else version = ""; - for ( const auto& line : lines ) { - Tokenize( line, tokens, "_=" ); // finally break each line into tokens to get module, type and version - if ( tokens.size() != 3 ) continue; // need 3 tokens + // now store it permanently + // + if ((module > 0) && (module <= nmods)) { + try { + this->modtype.at(module - 1) = type; // store the type in a vector indexed by module + this->modversion.at(module - 1) = version; // store the type in a vector indexed by module + } catch (std::out_of_range &) { + message.str(""); + message << "requested module " << module << " out of range {1:" << nmods; + this->camera.log_error(function, message.str()); + } + } else { + // else should never happen + message.str(""); + message << "module " << module << " outside range {1:" << nmods << "}"; + this->camera.log_error(function, message.str()); + return ERROR; + } - std::string version; - int module=0; - int type=0; + // Use the module type to resize the gain and offset vectors, + // but always use the largest possible value allowed. + // + if (type == 2) adchans = (adchans < MAXADCCHANS ? MAXADCCHANS : adchans); // ADC module (type=2) found + if (type == 17) adchans = (adchans < MAXADMCHANS ? MAXADMCHANS : adchans); // ADM module (type=17) found + this->gain.resize(adchans); + this->offset.resize(adchans); - // get the module number - // - if ( tokens[0].compare( 0, 9, "BACKPLANE" ) == 0 ) { - if ( tokens[1] == "VERSION" ) this->backplaneversion = tokens[2]; - continue; - } + // Check that the AD modules are installed in the correct slot + // + if ((type == 2 || type == 17) && (module < 5 || module > 8)) { + message.str(""); + message << "AD module (type=" << type << ") cannot be in slot " << module << ". Use slots 5-8"; + this->camera.log_error(function, message.str()); + return ERROR; + } + } // end for ( auto line : lines ) - // get the module and type of each module from MODn_TYPE - // - if ( ( tokens[0].compare( 0, 3, "MOD" ) == 0 ) && ( tokens[1] == "TYPE" ) ) { - try { - module = std::stoi( tokens[0].substr(3) ); - type = std::stoi( tokens[2] ); + // empty the Archon log + // + error = this->fetchlog(); - } catch (std::invalid_argument &) { - message.str(""); message << "unable to convert module or type from " << tokens[0] << "=" << tokens[1] << " to integer"; - this->camera.log_error( function, message.str() ); - return ERROR; + // Make sure the following systemkeys are added. + // They can be changed at any time by a command but since they have defaults + // they don't require a command so this ensures they get into the systemkeys db. + // + std::stringstream keystr; + keystr << "HDRSHIFT=" << this->n_hdrshift << "// number of HDR right-shift bits"; + this->systemkeys.addkey(keystr.str()); - } catch (std::out_of_range &) { - message.str(""); message << "module " << tokens[0].substr(3) << " or type " << tokens[1] << " out of range"; - this->camera.log_error( function, message.str() ); - return ERROR; - } + return error; + } - } else continue; + /**************** Archon::Interface::connect_controller *********************/ - // get the module version - // - if ( tokens[1] == "VERSION" ) version = tokens[2]; else version = ""; - // now store it permanently - // - if ( (module > 0) && (module <= nmods) ) { - try { - this->modtype.at(module-1) = type; // store the type in a vector indexed by module - this->modversion.at(module-1) = version; // store the type in a vector indexed by module + /**************** Archon::Interface::disconnect_controller ******************/ + /** + * @fn disconnect_controller + * @brief + * @param none + * @return + * + */ + long Interface::disconnect_controller() { + std::string function = "Archon::Interface::disconnect_controller"; + long error; + if (!this->archon.isconnected()) { + logwrite(function, "connection already closed"); + return (NO_ERROR); + } + // close the socket file descriptor to the Archon controller + // + error = this->archon.Close(); - } catch (std::out_of_range &) { - message.str(""); message << "requested module " << module << " out of range {1:" << nmods; - this->camera.log_error( function, message.str() ); + // Free the memory + // + if (this->image_data != nullptr) { + logwrite(function, "releasing allocated device memory"); + delete [] this->image_data; + this->image_data = nullptr; } - } else { // else should never happen - message.str(""); message << "module " << module << " outside range {1:" << nmods << "}"; - this->camera.log_error( function, message.str() ); - return ERROR; - } + // On success, write the value to the log and return + // + if (error == NO_ERROR) { + logwrite(function, "Archon connection terminated"); + } else { + // Throw an error for any other errors + this->camera.log_error(function, "disconnecting Archon camera"); + } - // Use the module type to resize the gain and offset vectors, - // but always use the largest possible value allowed. - // - if ( type == 2 ) adchans = ( adchans < MAXADCCHANS ? MAXADCCHANS : adchans ); // ADC module (type=2) found - if ( type == 17 ) adchans = ( adchans < MAXADMCHANS ? MAXADMCHANS : adchans ); // ADM module (type=17) found - this->gain.resize( adchans ); - this->offset.resize( adchans ); - - // Check that the AD modules are installed in the correct slot - // - if ( ( type == 2 || type == 17 ) && ( module < 5 || module > 8 ) ) { - message.str(""); message << "AD module (type=" << type << ") cannot be in slot " << module << ". Use slots 5-8"; - this->camera.log_error( function, message.str() ); - return ERROR; - } + return error; + } - } // end for ( auto line : lines ) + /**************** Archon::Interface::disconnect_controller ******************/ - // empty the Archon log - // - error = this->fetchlog(); - // Make sure the following systemkeys are added. - // They can be changed at any time by a command but since they have defaults - // they don't require a command so this ensures they get into the systemkeys db. - // - std::stringstream keystr; - keystr << "HDRSHIFT=" << this->n_hdrshift << "// number of HDR right-shift bits"; - this->systemkeys.addkey( keystr.str() ); - - return error; - } - /**************** Archon::Interface::connect_controller *********************/ - - - /**************** Archon::Interface::disconnect_controller ******************/ - /** - * @fn disconnect_controller - * @brief - * @param none - * @return - * - */ - long Interface::disconnect_controller() { - std::string function = "Archon::Interface::disconnect_controller"; - long error; - if (!this->archon.isconnected()) { - logwrite(function, "connection already closed"); - return (NO_ERROR); - } - // close the socket file descriptor to the Archon controller - // - error = this->archon.Close(); + /**************** Archon::Interface::native *********************************/ + /** + * @fn native + * @brief send native commands directly to Archon and log result + * @param std::string cmd + * @return long ret from archon_cmd() call + * + * This function simply calls archon_cmd() then breaks the reply into + * space-delimited tokens and puts each token into the asynchronous + * message queue. The result is that the reply comes out one line at + * a time on the async port. + * + */ + long Interface::native(const std::string &cmd) { + std::string function = "Archon::Interface::native"; + std::stringstream message; + std::string reply; + std::vector tokens; + long ret = archon_cmd(cmd, reply); + if (!reply.empty()) { + // Tokenize the reply and put each non-empty token into the asynchronous message queue. + // The reply message begins and ends with "CMD:BEGIN" and "CMD:END" and + // each line of the reply is prepended with "CMD:" where CMD is the native command + // which generated the message. + // + message << cmd << ":BEGIN"; + this->camera.async.enqueue(message.str()); - // Free the memory - // - if (this->image_data != nullptr) { - logwrite(function, "releasing allocated device memory"); - delete [] this->image_data; - this->image_data=nullptr; + Tokenize(reply, tokens, " "); + for (const auto &token: tokens) { + if (!token.empty() && token != "\n") { + message.str(""); + message << cmd << ":" << token; + this->camera.async.enqueue(message.str()); + } + } + message.str(""); + message << cmd << ":END"; + this->camera.async.enqueue(message.str()); + } + return ret; } - // On success, write the value to the log and return - // - if (error == NO_ERROR) { - logwrite(function, "Archon connection terminated"); + /**************** Archon::Interface::native *********************************/ + - } else { - // Throw an error for any other errors - this->camera.log_error( function, "disconnecting Archon camera" ); + /**************** Archon::Interface::archon_cmd *****************************/ + /** + * @fn archon_cmd + * @brief send a command to Archon + * @param cmd + * @param reply (optional) + * @return ERROR, BUSY or NO_ERROR + * + */ + long Interface::archon_cmd(const std::string &cmd) { + // use this form when the calling + std::string reply; // function doesn't need to look at the reply + return (archon_cmd(cmd, reply)); } - return error; - } - /**************** Archon::Interface::disconnect_controller ******************/ - - - /**************** Archon::Interface::native *********************************/ - /** - * @fn native - * @brief send native commands directly to Archon and log result - * @param std::string cmd - * @return long ret from archon_cmd() call - * - * This function simply calls archon_cmd() then breaks the reply into - * space-delimited tokens and puts each token into the asynchronous - * message queue. The result is that the reply comes out one line at - * a time on the async port. - * - */ - long Interface::native(const std::string& cmd) { - std::string function = "Archon::Interface::native"; - std::stringstream message; - std::string reply; - std::vector tokens; - long ret = archon_cmd(cmd, reply); - if (!reply.empty()) { - // Tokenize the reply and put each non-empty token into the asynchronous message queue. - // The reply message begins and ends with "CMD:BEGIN" and "CMD:END" and - // each line of the reply is prepended with "CMD:" where CMD is the native command - // which generated the message. - // - message << cmd << ":BEGIN"; - this->camera.async.enqueue( message.str() ); - - Tokenize(reply, tokens, " "); - for (const auto & token : tokens) { - if ( ! token.empty() && token != "\n" ) { - message.str(""); message << cmd << ":" << token; - this->camera.async.enqueue( message.str() ); + long Interface::archon_cmd(const std::string &cmd, std::string &reply) { + std::string function = "Archon::Interface::archon_cmd"; + std::stringstream message; + int retval; + char check[4]; + char buffer[4096]; //!< temporary buffer for holding Archon replies + int error = NO_ERROR; + + if (!this->archon.isconnected()) { + // nothing to do if no connection open to controller + this->camera.log_error(function, "connection not open to controller"); + return ERROR; } - } - message.str(""); message << cmd << ":END"; - this->camera.async.enqueue( message.str() ); - } - return ret; - } - /**************** Archon::Interface::native *********************************/ - - - /**************** Archon::Interface::archon_cmd *****************************/ - /** - * @fn archon_cmd - * @brief send a command to Archon - * @param cmd - * @param reply (optional) - * @return ERROR, BUSY or NO_ERROR - * - */ - long Interface::archon_cmd(const std::string& cmd) { // use this form when the calling - std::string reply; // function doesn't need to look at the reply - return( archon_cmd(cmd, reply) ); - } - long Interface::archon_cmd(const std::string& cmd, std::string &reply) { - std::string function = "Archon::Interface::archon_cmd"; - std::stringstream message; - int retval; - char check[4]; - char buffer[4096]; //!< temporary buffer for holding Archon replies - int error = NO_ERROR; - - if (!this->archon.isconnected()) { // nothing to do if no connection open to controller - this->camera.log_error( function, "connection not open to controller" ); - return ERROR; - } - if (this->archon_busy) { // only one command at a time - message.str(""); message << "Archon busy: ignored command " << cmd; - this->camera.log_error( function, message.str() ); - return BUSY; - } + if (this->archon_busy) { + // only one command at a time + message.str(""); + message << "Archon busy: ignored command " << cmd; + this->camera.log_error(function, message.str()); + return BUSY; + } - /** - * Hold a scoped lock for the duration of this function, - * to prevent multiple threads from accessing the Archon. - */ - const std::lock_guard lock(this->archon_mutex); - this->archon_busy = true; + /** + * Hold a scoped lock for the duration of this function, + * to prevent multiple threads from accessing the Archon. + */ + const std::lock_guard lock(this->archon_mutex); + this->archon_busy = true; - // build command: ">xxCOMMAND\n" where xx=hex msgref and COMMAND=command - // - this->msgref = (this->msgref + 1) % 256; // increment msgref for each new command sent - std::stringstream ssprefix; - ssprefix << ">" - << std::setfill('0') - << std::setw(2) - << std::hex - << this->msgref; - std::string prefix=ssprefix.str(); - try { - std::transform( prefix.begin(), prefix.end(), prefix.begin(), ::toupper ); // make uppercase - - } catch (...) { - message.str(""); message << "converting Archon command: " << prefix << " to uppercase"; - this->camera.log_error( function, message.str() ); - return ERROR; - } + // build command: ">xxCOMMAND\n" where xx=hex msgref and COMMAND=command + // + this->msgref = (this->msgref + 1) % 256; // increment msgref for each new command sent + std::stringstream ssprefix; + ssprefix << ">" + << std::setfill('0') + << std::setw(2) + << std::hex + << this->msgref; + std::string prefix = ssprefix.str(); + try { + std::transform(prefix.begin(), prefix.end(), prefix.begin(), ::toupper); // make uppercase + } catch (...) { + message.str(""); + message << "converting Archon command: " << prefix << " to uppercase"; + this->camera.log_error(function, message.str()); + return ERROR; + } - std::stringstream sscmd; // sscmd = stringstream, building command - sscmd << prefix << cmd << "\n"; - std::string scmd = sscmd.str(); // scmd = string, command to send + std::stringstream sscmd; // sscmd = stringstream, building command + sscmd << prefix << cmd << "\n"; + std::string scmd = sscmd.str(); // scmd = string, command to send - // build the command checksum: msgref used to check that reply matches command - // - SNPRINTF(check, "<%02X", this->msgref) + // build the command checksum: msgref used to check that reply matches command + // + SNPRINTF(check, "<%02X", this->msgref) - // log the command as long as it's not a STATUS, TIMER, WCONFIG or FRAME command - // - if ( (cmd.compare(0,7,"WCONFIG") != 0) && - (cmd.compare(0,5,"TIMER") != 0) && - (cmd.compare(0,6,"STATUS") != 0) && - (cmd.compare(0,5,"FRAME") != 0) ) { - // erase newline for logging purposes - std::string fcmd = scmd; - try { - fcmd.erase(fcmd.find('\n'), 1); - } catch(...) { } - message.str(""); message << "sending command: " << fcmd; - logwrite(function, message.str()); - } + // log the command as long as it's not a STATUS, TIMER, WCONFIG or FRAME command + // + if ((cmd.compare(0, 7, "WCONFIG") != 0) && + (cmd.compare(0, 5, "TIMER") != 0) && + (cmd.compare(0, 6, "STATUS") != 0) && + (cmd.compare(0, 5, "FRAME") != 0)) { + // erase newline for logging purposes + std::string fcmd = scmd; + try { + fcmd.erase(fcmd.find('\n'), 1); + } catch (...) { + } + message.str(""); + message << "sending command: " << fcmd; + logwrite(function, message.str()); + } - // send the command - // - if ( (this->archon.Write(scmd)) == -1) { - this->camera.log_error( function, "writing to camera socket"); - } + // send the command + // + if ((this->archon.Write(scmd)) == -1) { + this->camera.log_error(function, "writing to camera socket"); + } - // For the FETCH command we don't wait for a reply, but return immediately. - // FETCH results in a binary response which is handled elsewhere (in read_frame). - // Must also distinguish this from the FETCHLOG command, for which we do wait - // for a normal reply. - // - // The scoped mutex lock will be released automatically upon return. - // - if ( (cmd.compare(0,5,"FETCH")==0) - && (cmd.compare(0,8,"FETCHLOG")!=0) ) return (NO_ERROR); + // For the FETCH command we don't wait for a reply, but return immediately. + // FETCH results in a binary response which is handled elsewhere (in read_frame). + // Must also distinguish this from the FETCHLOG command, for which we do wait + // for a normal reply. + // + // The scoped mutex lock will be released automatically upon return. + // + if ((cmd.compare(0, 5, "FETCH") == 0) + && (cmd.compare(0, 8, "FETCHLOG") != 0)) + return (NO_ERROR); - // For all other commands, receive the reply - // - reply.clear(); // zero reply buffer - do { - if ( (retval=this->archon.Poll()) <= 0) { - if (retval==0) { - message.str(""); - message << "Poll timeout waiting for response from Archon command (maybe unrecognized command?)"; - error = TIMEOUT; + // For all other commands, receive the reply + // + reply.clear(); // zero reply buffer + do { + if ((retval = this->archon.Poll()) <= 0) { + if (retval == 0) { + message.str(""); + message << "Poll timeout waiting for response from Archon command (maybe unrecognized command?)"; + error = TIMEOUT; + } + if (retval < 0) { + message.str(""); + message << "Poll error waiting for response from Archon command"; + error = ERROR; + } + if (error != NO_ERROR) this->camera.log_error(function, message.str()); + break; + } + memset(buffer, '\0', 2048); // init temporary buffer + retval = this->archon.Read(buffer, 2048); // read into temp buffer + if (retval <= 0) { + this->camera.log_error(function, "reading Archon"); + break; + } + reply.append(buffer); // append read buffer into the reply string + } while (retval > 0 && reply.find('\n') == std::string::npos); + + // If there was an Archon error then clear the busy flag and get out now + // + if (error != NO_ERROR) { + this->archon_busy = false; + return error; } - if (retval<0) { + + // The first three bytes of the reply should contain the msgref of the + // command, which can be used as a check that the received reply belongs + // to the command which was sent. + // + // Error processing command (no other information is provided by Archon) + // + if (reply.compare(0, 1, "?") == 0) { + // "?" means Archon experienced an error processing command + error = ERROR; message.str(""); - message << "Poll error waiting for response from Archon command"; + message << "Archon controller returned error processing command: " << cmd; + this->camera.log_error(function, message.str()); + } else if (reply.compare(0, 3, check) != 0) { + // First 3 bytes of reply must equal checksum else reply doesn't belong to command error = ERROR; + // std::string hdr = reply; + try { + scmd.erase(scmd.find('\n'), 1); + } catch (...) { + } + message.str(""); + message << "command-reply mismatch for command: " + scmd + ": expected " + check + " but received " + reply; + this->camera.log_error(function, message.str()); + } else { + // command and reply are a matched pair + error = NO_ERROR; + + // log the command as long as it's not a STATUS, TIMER, WCONFIG or FRAME command + if ((cmd.compare(0, 7, "WCONFIG") != 0) && + (cmd.compare(0, 5, "TIMER") != 0) && + (cmd.compare(0, 6, "STATUS") != 0) && + (cmd.compare(0, 5, "FRAME") != 0)) { + message.str(""); + message << "command 0x" << std::setfill('0') << std::setw(2) << std::uppercase << std::hex << this-> + msgref << " success"; + logwrite(function, message.str()); + } + + reply.erase(0, 3); // strip off the msgref from the reply } - if ( error != NO_ERROR ) this->camera.log_error( function, message.str() ); - break; - } - memset(buffer, '\0', 2048); // init temporary buffer - retval = this->archon.Read(buffer, 2048); // read into temp buffer - if (retval <= 0) { - this->camera.log_error( function, "reading Archon" ); - break; - } - reply.append(buffer); // append read buffer into the reply string - } while(retval>0 && reply.find('\n') == std::string::npos); - // If there was an Archon error then clear the busy flag and get out now - // - if ( error != NO_ERROR ) { + // clear the semaphore (still had the mutex this entire function) + // this->archon_busy = false; - return error; - } - - // The first three bytes of the reply should contain the msgref of the - // command, which can be used as a check that the received reply belongs - // to the command which was sent. - // - // Error processing command (no other information is provided by Archon) - // - if (reply.compare(0, 1, "?")==0) { // "?" means Archon experienced an error processing command - error = ERROR; - message.str(""); message << "Archon controller returned error processing command: " << cmd; - this->camera.log_error( function, message.str() ); - - } else if (reply.compare(0, 3, check)!=0) { // First 3 bytes of reply must equal checksum else reply doesn't belong to command - error = ERROR; - // std::string hdr = reply; - try { - scmd.erase(scmd.find('\n'), 1); - } catch(...) { } - message.str(""); message << "command-reply mismatch for command: " + scmd + ": expected " + check + " but received " + reply ; - this->camera.log_error( function, message.str() ); - - } else { // command and reply are a matched pair - error = NO_ERROR; - - // log the command as long as it's not a STATUS, TIMER, WCONFIG or FRAME command - if ( (cmd.compare(0,7,"WCONFIG") != 0) && - (cmd.compare(0,5,"TIMER") != 0) && - (cmd.compare(0,6,"STATUS") != 0) && - (cmd.compare(0,5,"FRAME") != 0) ) { - message.str(""); - message << "command 0x" << std::setfill('0') << std::setw(2) << std::uppercase << std::hex << this->msgref << " success"; - logwrite(function, message.str()); - } - reply.erase(0, 3); // strip off the msgref from the reply + return error; } - // clear the semaphore (still had the mutex this entire function) - // - this->archon_busy = false; - - return error; - } - /**************** Archon::Interface::archon_cmd *****************************/ - - - /**************** Archon::Interface::read_parameter *************************/ - /** - * @fn read_parameter - * @brief read a parameter from Archon configuration memory - * @param paramname char pointer to name of paramter - * @param value reference to string for return value - * @return ERROR on error, NO_ERROR if okay. - * - * The string reference contains the value of the parameter - * to be returned to the user. - * - * No direct calls to Archon -- this function uses archon_cmd() - * which in turn handles all of the Archon in-use locking. - * - */ - long Interface::read_parameter(const std::string& paramname, std::string &value) { - std::string function = "Archon::Interface::read_parameter"; - std::stringstream message; - std::stringstream cmd; - std::string reply; - long error = NO_ERROR; - - if (this->parammap.find(paramname) == this->parammap.end()) { - message.str(""); message << "parameter \"" << paramname << "\" not found in ACF"; - this->camera.log_error( function, message.str() ); - return ERROR; - } + /**************** Archon::Interface::archon_cmd *****************************/ - // form the RCONFIG command to send to Archon - // - cmd.str(""); - cmd << "RCONFIG" - << std::uppercase << std::setfill('0') << std::setw(4) << std::hex - << this->parammap[paramname.c_str()].line; - error = this->archon_cmd(cmd.str(), reply); // send RCONFIG command here - - if ( error != NO_ERROR ) { - message.str(""); message << "ERROR: sending archon_cmd(" << cmd.str() << ")"; - logwrite( function, message.str() ); - return error; - } - try { - reply.erase(reply.find('\n'), 1); - } catch(...) { } // strip newline + /**************** Archon::Interface::read_parameter *************************/ + /** + * @fn read_parameter + * @brief read a parameter from Archon configuration memory + * @param paramname char pointer to name of paramter + * @param value reference to string for return value + * @return ERROR on error, NO_ERROR if okay. + * + * The string reference contains the value of the parameter + * to be returned to the user. + * + * No direct calls to Archon -- this function uses archon_cmd() + * which in turn handles all of the Archon in-use locking. + * + */ + long Interface::read_parameter(const std::string ¶mname, std::string &value) { + std::string function = "Archon::Interface::read_parameter"; + std::stringstream message; + std::stringstream cmd; + std::string reply; + long error = NO_ERROR; - // reply should now be of the form PARAMETERn=PARAMNAME=VALUE, - // and we want just the VALUE here - // + if (this->parammap.find(paramname) == this->parammap.end()) { + message.str(""); + message << "parameter \"" << paramname << "\" not found in ACF"; + this->camera.log_error(function, message.str()); + return ERROR; + } - size_t loc; - value = reply; - if (value.compare(0, 9, "PARAMETER") == 0) { // value: PARAMETERn=PARAMNAME=VALUE - if ( (loc=value.find('=')) != std::string::npos ) value = value.substr(++loc); // value: PARAMNAME=VALUE - else { - value="NaN"; - error = ERROR; - } - if ( (loc=value.find('=')) != std::string::npos ) value = value.substr(++loc); // value: VALUE - else { - value="NaN"; - error = ERROR; - } + // form the RCONFIG command to send to Archon + // + cmd.str(""); + cmd << "RCONFIG" + << std::uppercase << std::setfill('0') << std::setw(4) << std::hex + << this->parammap[paramname.c_str()].line; + error = this->archon_cmd(cmd.str(), reply); // send RCONFIG command here - } else { - value="NaN"; - error = ERROR; - } + if (error != NO_ERROR) { + message.str(""); + message << "ERROR: sending archon_cmd(" << cmd.str() << ")"; + logwrite(function, message.str()); + return error; + } - if (error==ERROR) { - message << "malformed reply: " << reply << " to Archon command " << cmd.str() << ": Expected PARAMETERn=PARAMNAME=VALUE"; - this->camera.log_error( function, message.str() ); + try { + reply.erase(reply.find('\n'), 1); + } catch (...) { + } // strip newline - } else { - message.str(""); message << paramname << " = " << value; - logwrite(function, message.str()); - } - return error; - } - /**************** Archon::Interface::read_parameter *************************/ - - - /**************** Archon::Interface::prep_parameter *************************/ - /** - * @fn prep_parameter - * @brief - * @param - * @return NO_ERROR or ERROR, return value from archon_cmd call - * - */ - long Interface::prep_parameter(const std::string& paramname, std::string value) { - std::string function = "Archon::Interface::prep_parameter"; - std::stringstream message; - std::stringstream scmd; - long error = NO_ERROR; - - // Prepare to apply it to the system -- will be loaded on next EXTLOAD signal - // - scmd << "FASTPREPPARAM " << paramname << " " << value; - error = this->archon_cmd(scmd.str()); + // reply should now be of the form PARAMETERn=PARAMNAME=VALUE, + // and we want just the VALUE here + // - if (error != NO_ERROR) { - message << "ERROR: prepping parameter \"" << paramname << "=" << value; - } + size_t loc; + value = reply; + if (value.compare(0, 9, "PARAMETER") == 0) { + // value: PARAMETERn=PARAMNAME=VALUE + if ((loc = value.find('=')) != std::string::npos) value = value.substr(++loc); // value: PARAMNAME=VALUE + else { + value = "NaN"; + error = ERROR; + } + if ((loc = value.find('=')) != std::string::npos) value = value.substr(++loc); // value: VALUE + else { + value = "NaN"; + error = ERROR; + } + } else { + value = "NaN"; + error = ERROR; + } - logwrite( function, message.str() ); - return error; - } - /**************** Archon::Interface::prep_parameter *************************/ - - - /**************** Archon::Interface::load_parameter *************************/ - /** - * @fn load_parameter - * @brief - * @param - * @return NO_ERROR or ERROR, return value from archon_cmd call - * - */ - long Interface::load_parameter(std::string paramname, std::string value) { - std::string function = "Archon::Interface::load_parameter"; - std::stringstream message; - std::stringstream scmd; - long error = NO_ERROR; - - scmd << "FASTLOADPARAM " << paramname << " " << value; - error = this->archon_cmd(scmd.str()); - - if (error != NO_ERROR) { - message << "ERROR: loading parameter \"" << paramname << "=" << value << "\" into Archon"; - - } else { - message << "parameter \"" << paramname << "=" << value << "\" loaded into Archon"; + if (error == ERROR) { + message << "malformed reply: " << reply << " to Archon command " << cmd.str() << + ": Expected PARAMETERn=PARAMNAME=VALUE"; + this->camera.log_error(function, message.str()); + } else { + message.str(""); + message << paramname << " = " << value; + logwrite(function, message.str()); + } + return error; } - logwrite( function, message.str() ); - return error; - } - /**************** Archon::Interface::load_parameter *************************/ - - - /**************** Archon::Interface::fetchlog *******************************/ - /** - * @fn fetchlog - * @brief fetch the archon log entry and log the response - * @param none - * @return NO_ERROR or ERROR, return value from archon_cmd call - * - * Send the FETCHLOG command to, then read the reply from Archon. - * Fetch until the log is empty. Log the response. - * - */ - long Interface::fetchlog() { - std::string function = "Archon::Interface::fetchlog"; - std::string reply; - std::stringstream message; - long retval; - - // send FETCHLOG command while reply is not (null) - // - do { - if ( (retval=this->archon_cmd(FETCHLOG, reply)) != NO_ERROR ) { // send command here - logwrite( function, "ERROR: calling FETCHLOG" ); - return retval; - } - if (reply != "(null)") { - try { - reply.erase(reply.find('\n'), 1); - } catch(...) { } // strip newline - logwrite(function, reply); // log reply here - } - } while (reply != "(null)"); // stop when reply is (null) - - return retval; - } - /**************** Archon::Interface::fetchlog *******************************/ - - - /**************** Archon::Interface::load_timing ****************************/ - /** - * @fn load_timing - * @brief loads the ACF file and applies the timing script and parameters only - * @param acffile, specified ACF to load - * @param retstring, reference to string for return values // TODO not yet implemented - * @return - * - * This function is overloaded. - * - * This function loads the ACF file then sends the LOADTIMING command - * which parses and compiles only the timing script and parameters. - * - */ - long Interface::load_timing(std::string acffile, std::string &retstring) { - return( this->load_timing( acffile ) ); - } - long Interface::load_timing(std::string acffile) { - std::string function = "Archon::Interface::load_timing"; - - // load the ACF file into configuration memory - // - long error = this->load_acf( acffile ); + /**************** Archon::Interface::read_parameter *************************/ - // parse timing script and parameters and apply them to the system - // - if (error == NO_ERROR) error = this->archon_cmd(LOADTIMING); - - return error; - } - /**************** Archon::Interface::load_timing ****************************/ - - - /**************** Archon::Interface::load_firmware **************************/ - /** - * @fn load_firmware - * @brief loads the ACF file and applies the complete system configuration - * @param none - * @return - * - * This function is overloaded. - * - * This version takes a single argument for the acf file to load. - * - * This function loads the ACF file and then sends an APPLYALL which - * parses and applies the complete system configuration from the - * configuration memory just loaded. The detector power will be off. - * - */ - long Interface::load_firmware(std::string acffile) { - // load the ACF file into configuration memory - // - long error = this->load_acf( acffile ); - // Parse and apply the complete system configuration from configuration memory. - // Detector power will be off after this. - // - if (error == NO_ERROR) error = this->archon_cmd(APPLYALL); + /**************** Archon::Interface::prep_parameter *************************/ + /** + * @fn prep_parameter + * @brief + * @param + * @return NO_ERROR or ERROR, return value from archon_cmd call + * + */ + long Interface::prep_parameter(const std::string ¶mname, std::string value) { + std::string function = "Archon::Interface::prep_parameter"; + std::stringstream message; + std::stringstream scmd; + long error = NO_ERROR; - if ( error != NO_ERROR ) this->fetchlog(); + // Prepare to apply it to the system -- will be loaded on next EXTLOAD signal + // + scmd << "FASTPREPPARAM " << paramname << " " << value; + error = this->archon_cmd(scmd.str()); - // If no errors then automatically set the mode to DEFAULT. - // This should come after APPLYALL in case any new parameters need to be written, - // which shouldn't be done until after they have been applied. - // - if ( error == NO_ERROR ) error = this->set_camera_mode( std::string( "DEFAULT" ) ); - - return error; - } - /**************** Archon::Interface::load_firmware **************************/ - /** - * @fn load_firmware - * @brief - * @param none - * @return - * - * This function is overloaded. - * - * This version is for future compatibility. - * The multiple-controller version will pass a reference to a return string. - * - */ - long Interface::load_firmware(std::string acffile, std::string &retstring) { - return( this->load_firmware( acffile ) ); - } - /**************** Archon::Interface::load_firmware **************************/ - - - /**************** Archon::Interface::load_acf *******************************/ - /** - * @fn load_acf - * @brief loads the ACF file into configuration memory (no APPLY!) - * @param acffile - * @return ERROR or NO_ERROR - * - * This function loads the specfied file into configuration memory. - * While the ACF is being read, an internal database (STL map) is being - * created to allow lookup access to the ACF file or parameters. - * - * The [MODE_XXX] sections are also parsed and parameters as a function - * of mode are saved in their own database. - * - * This function only loads (WCONFIGxxx) the configuration memory; it does - * not apply it to the system. Therefore, this function must be followed - * with a LOADTIMING or APPLYALL command, for example. - * - */ - long Interface::load_acf(std::string acffile) { - std::string function = "Archon::Interface::load_acf"; - std::stringstream message; - std::fstream filestream; // I/O stream class - std::string line; // the line read from the acffile - std::string mode; - std::string keyword, keystring, keyvalue, keytype, keycomment; - std::stringstream sscmd; - std::string key, value; - - int linecount; // the Archon configuration line number is required for writing back to config memory - long error=NO_ERROR; - bool parse_config=false; - - // get the acf filename, either passed here or from loaded default - // - if ( acffile.empty() ) { - acffile = this->camera.firmware[0]; + if (error != NO_ERROR) { + message << "ERROR: prepping parameter \"" << paramname << "=" << value; + } - } else { - this->camera.firmware[0] = acffile; + logwrite(function, message.str()); + return error; } - // try to open the file - // - try { - filestream.open(acffile, std::ios_base::in); - - } catch(...) { - message << "opening acf file " << acffile << ": " << std::strerror(errno); - this->camera.log_error( function, message.str() ); - return ERROR; - } + /**************** Archon::Interface::prep_parameter *************************/ - if ( ! filestream.is_open() || ! filestream.good() ) { - message << "acf file " << acffile << " could not be opened"; - this->camera.log_error( function, message.str() ); - return ERROR; - } - logwrite(function, acffile); + /**************** Archon::Interface::load_parameter *************************/ + /** + * @fn load_parameter + * @brief + * @param + * @return NO_ERROR or ERROR, return value from archon_cmd call + * + */ + long Interface::load_parameter(std::string paramname, std::string value) { + std::string function = "Archon::Interface::load_parameter"; + std::stringstream message; + std::stringstream scmd; + long error = NO_ERROR; - // The CPU in Archon is single threaded, so it checks for a network - // command, then does some background polling (reading bias voltages etc.), - // then checks again for a network command. "POLLOFF" disables this - // background checking, so network command responses are very fast. - // The downside is that bias voltages, temperatures, etc. are not updated - // until you give a "POLLON". - // - error = this->archon_cmd(POLLOFF); + scmd << "FASTLOADPARAM " << paramname << " " << value; + error = this->archon_cmd(scmd.str()); - // clear configuration memory for this controller - // - if (error == NO_ERROR) error = this->archon_cmd(CLEARCONFIG); + if (error != NO_ERROR) { + message << "ERROR: loading parameter \"" << paramname << "=" << value << "\" into Archon"; + } else { + message << "parameter \"" << paramname << "=" << value << "\" loaded into Archon"; + } - if ( error != NO_ERROR ) { - logwrite( function, "ERROR: could not prepare Archon for new ACF" ); + logwrite(function, message.str()); return error; } - // Any failure after clearing the configuration memory will mean - // no firmware is loaded. - // - this->firmwareloaded = false; - - modemap.clear(); // file is open, clear all modes - - linecount = 0; // init Archon configuration line number + /**************** Archon::Interface::load_parameter *************************/ - while ( getline(filestream, line) ) { // note that getline discards the newline "\n" character - // don't start parsing until [CONFIG] and stop on a newline or [SYSTEM] - // - if (line == "[CONFIG]") { parse_config=true; continue; } - if (line == "\n" ) { parse_config=false; continue; } - if (line == "[SYSTEM]") { parse_config=false; continue; } - - std::string savedline = line; // save un-edited line for error reporting + /**************** Archon::Interface::fetchlog *******************************/ + /** + * @fn fetchlog + * @brief fetch the archon log entry and log the response + * @param none + * @return NO_ERROR or ERROR, return value from archon_cmd call + * + * Send the FETCHLOG command to, then read the reply from Archon. + * Fetch until the log is empty. Log the response. + * + */ + long Interface::fetchlog() { + std::string function = "Archon::Interface::fetchlog"; + std::string reply; + std::stringstream message; + long retval; - // parse mode sections, looking for "[MODE_xxxxx]" - // - if (line.substr(0,6)=="[MODE_") { // this is a mode section - try { - line.erase(line.find('['), 1); // erase the opening square bracket - line.erase(line.find(']'), 1); // erase the closing square bracket - - } catch(...) { - message.str(""); message << "malformed mode section: " << savedline << ": expected [MODE_xxxx]"; - this->camera.log_error( function, message.str() ); - filestream.close(); - return ERROR; - } - if ( ! line.empty() ) { // What's remaining should be MODE_xxxxx - mode = line.substr(5); // everything after "MODE_" is the mode name - std::transform( mode.begin(), mode.end(), mode.begin(), ::toupper ); // make uppercase - - // got a mode, now check if one of this name has already been located - // and put into the modemap - // - if ( this->modemap.find(mode) != this->modemap.end() ) { - message.str(""); message << "duplicate definition of mode: " << mode << ": load aborted"; - this->camera.log_error( function, message.str() ); - filestream.close(); - return ERROR; + // send FETCHLOG command while reply is not (null) + // + do { + if ((retval = this->archon_cmd(FETCHLOG, reply)) != NO_ERROR) { + // send command here + logwrite(function, "ERROR: calling FETCHLOG"); + return retval; + } + if (reply != "(null)") { + try { + reply.erase(reply.find('\n'), 1); + } catch (...) { + } // strip newline + logwrite(function, reply); // log reply here + } + } while (reply != "(null)"); // stop when reply is (null) - } else { - parse_config = true; - message.str(""); message << "detected mode: " << mode; logwrite(function, message.str()); - this->modemap[mode].rawenable=-1; // initialize to -1, require it be set somewhere in the ACF - // this also ensures something is saved in the modemap for this mode - } + return retval; + } - } else { // somehow there's no xxx left after removing "[MODE_" and "]" - message.str(""); message << "malformed mode section: " << savedline << ": expected [MODE_xxxx]"; - this->camera.log_error( function, message.str() ); - filestream.close(); - return ERROR; - } - } + /**************** Archon::Interface::fetchlog *******************************/ - // Everything else is for parsing configuration lines so if we didn't get [CONFIG] then - // skip to the next line. - // - if (!parse_config) continue; - - // replace any TAB characters with a space - // - string_replace_char(line, "\t", " "); - - // replace any backslash characters with a forward slash - // - string_replace_char(line, "\\", "/"); - - // erase all quotes - try { - line.erase( std::remove(line.begin(), line.end(), '"'), line.end() ); - } catch(...) { } - - // Initialize key, value strings used to form WCONFIG KEY=VALUE command. - // As long as key stays empty then the WCONFIG command is not written to the Archon. - // This is what keeps TAGS: in the [MODE_xxxx] sections from being written to Archon, - // because these do not populate key. - // - key=""; - value=""; - - // ************************************************************ - // Store actual Archon parameters in their own STL map IN ADDITION to the map - // in which all other keywords are store, so that they can be accessed in - // a different way. Archon PARAMETER KEY=VALUE paris are formatted as: - // PARAMETERn=ParameterName=value - // where "PARAMETERn" is the key and "ParameterName=value" is the value. - // However, it is logical to access them by ParameterName only. That is what the - // parammap is for, hence the need for this STL map indexed on only the "ParameterName" - // portion of the value. Conversely, the configmap is indexed by the key. - // - // parammap stores ONLY the parameters, which are identified as PARAMETERxx="paramname=value" - // configmap stores every configuration line sent to Archon (which includes parameters) - // - // In order to modify these keywords in Archon, the entire above phrase - // (KEY=VALUE pair) must be preserved along with the line number on which it - // occurs in the config file. - // ************************************************************ - - // Look for TAGS: in the .acf file mode sections - // - // If tag is "ACF:" then it's a .acf line (could be a parameter or configuration) - // - if (line.compare(0,4,"ACF:")==0) { - std::vector tokens; - line = line.substr(4); // strip off the "ACF:" portion - std::string acf_key, acf_value; - try { - Tokenize(line, tokens, "="); // separate into tokens by "=" + /**************** Archon::Interface::load_timing ****************************/ + /** + * @fn load_timing + * @brief loads the ACF file and applies the timing script and parameters only + * @param acffile, specified ACF to load + * @param retstring, reference to string for return values // TODO not yet implemented + * @return + * + * This function is overloaded. + * + * This function loads the ACF file then sends the LOADTIMING command + * which parses and compiles only the timing script and parameters. + * + */ + long Interface::load_timing(std::string acffile, std::string &retstring) { + return (this->load_timing(acffile)); + } - if (tokens.size() == 1) { // KEY=, the VALUE is empty - acf_key = tokens[0]; - acf_value = ""; + long Interface::load_timing(std::string acffile) { + std::string function = "Archon::Interface::load_timing"; - } else if (tokens.size() == 2) { // KEY=VALUE - acf_key = tokens[0]; - acf_value = tokens[1]; + // load the ACF file into configuration memory + // + long error = this->load_acf(acffile); - } else { - message.str(""); message << "malformed ACF line: " << savedline << ": expected KEY=VALUE"; - this->camera.log_error( function, message.str() ); - filestream.close(); - return ERROR; - } - - bool keymatch = false; - - // If this key is in the main parammap then store it in the modemap's parammap for this mode - if (this->parammap.find( acf_key ) != this->parammap.end()) { - this->modemap[mode].parammap[ acf_key ].name = acf_key; - this->modemap[mode].parammap[ acf_key ].value = acf_value; - keymatch = true; - } - - // If this key is in the main configmap, then store it in the modemap's configmap for this mode - // - if (this->configmap.find( acf_key ) != this->configmap.end()) { - this->modemap[mode].configmap[ acf_key ].value = acf_value; - keymatch = true; - } - - // If this key is neither in the parammap nor in the configmap then return an error - // - if ( ! keymatch ) { - message.str(""); - message << "[MODE_" << mode << "] ACF directive: " << acf_key << "=" << acf_value << " is not a valid parameter or configuration key"; - logwrite(function, message.str()); - filestream.close(); - return ERROR; - } - - } catch ( ... ) { - message.str(""); message << "extracting KEY=VALUE pair from ACF line: " << savedline; - this->camera.log_error( function, message.str() ); - filestream.close(); - return ERROR; - } - // end if (line.compare(0,4,"ACF:")==0) - - } else if (line.compare(0,5,"ARCH:")==0) { - // The "ARCH:" tag is for internal (Archon_interface) variables - // using the KEY=VALUE format. - // - std::vector tokens; - line = line.substr(5); // strip off the "ARCH:" portion - Tokenize(line, tokens, "="); // separate into KEY, VALUE tokens - if (tokens.size() != 2) { - message.str(""); message << "malformed ARCH line: " << savedline << ": expected ARCH:KEY=VALUE"; - this->camera.log_error( function, message.str() ); - filestream.close(); - return ERROR; - } - if ( tokens[0] == "NUM_DETECT" ) { - this->modemap[mode].geometry.num_detect = std::stoi( tokens[1] ); + // parse timing script and parameters and apply them to the system + // + if (error == NO_ERROR) error = this->archon_cmd(LOADTIMING); - } else if ( tokens[0] == "HORI_AMPS" ) { - this->modemap[mode].geometry.amps[0] = std::stoi( tokens[1] ); + return error; + } - } else if ( tokens[0] == "VERT_AMPS" ) { - this->modemap[mode].geometry.amps[1] = std::stoi( tokens[1] ); + /**************** Archon::Interface::load_timing ****************************/ - } else { - message.str(""); message << "unrecognized internal parameter specified: "<< tokens[0]; - this->camera.log_error( function, message.str() ); - filestream.close(); - return ERROR; - } - // end else if (line.compare(0,5,"ARCH:")==0) - - } else if (line.compare(0,5,"FITS:")==0) { - // the "FITS:" tag is used to write custom keyword entries of the form "FITS:KEYWORD=VALUE/COMMENT" - // - std::vector tokens; - line = line.substr(5); // strip off the "FITS:" portion - - // First, tokenize on the equal sign "=". - // The token left of "=" is the keyword. Immediate right is the value - Tokenize(line, tokens, "="); - if (tokens.size() != 2) { // need at least two tokens at this point - message.str(""); message << "malformed FITS command: " << savedline << ": expected KEYWORD=value/comment"; - this->camera.log_error( function, message.str() ); - filestream.close(); - return ERROR; - } - keyword = tokens[0].substr(0,8); // truncate keyword to 8 characters - keystring = tokens[1]; // tokenize the rest in a moment - keycomment = ""; // initialize comment, assumed empty unless specified below - - // Next, tokenize on the slash "/". - // The token left of "/" is the value. Anything to the right is a comment. - // - Tokenize(keystring, tokens, "/"); - - if (tokens.empty()) { // no tokens found means no "/" characeter which means no comment - keyvalue = keystring; // therefore the keyvalue is the entire string - } - - if (not tokens.empty()) { // at least one token - keyvalue = tokens[0]; - } - - if (tokens.size() == 2) { // If there are two tokens here then the second is a comment - keycomment = tokens[1]; - } - - if (tokens.size() > 2) { // everything below this has been covered - message.str(""); message << "malformed FITS command: " << savedline << ": expected KEYWORD=VALUE/COMMENT"; - this->camera.log_error( function, message.str() ); - message.str(""); message << "too many \"/\" in comment string? " << keystring; - this->camera.log_error( function, message.str() ); - filestream.close(); - return ERROR; - } - - // Save all the user keyword information in a map for later - this->modemap[mode].acfkeys.keydb[keyword].keyword = keyword; - this->modemap[mode].acfkeys.keydb[keyword].keytype = this->camera_info.userkeys.get_keytype(keyvalue); - this->modemap[mode].acfkeys.keydb[keyword].keyvalue = keyvalue; - this->modemap[mode].acfkeys.keydb[keyword].keycomment = keycomment; - // end if (line.compare(0,5,"FITS:")==0) - // - // ----- all done looking for "TAGS:" ----- - // - - } else if ( (line.compare(0,11,"PARAMETERS=")!=0) && // not the "PARAMETERS=xx line - (line.compare(0, 9,"PARAMETER" )==0) ) { // but must start with "PARAMETER" - // If this is a PARAMETERn=ParameterName=value KEY=VALUE pair... - // - - std::vector tokens; - Tokenize(line, tokens, "="); // separate into PARAMETERn, ParameterName, value tokens - - if (tokens.size() != 3) { - message.str(""); message << "malformed paramter line: " << savedline << ": expected PARAMETERn=Param=value"; - this->camera.log_error( function, message.str() ); - filestream.close(); - return ERROR; - } - - // Tokenize broke everything up at the "=" and - // we need all three parts, but we also need a version containing the last - // two parts together, "ParameterName=value" so re-assemble them here. - // - std::stringstream paramnamevalue; - paramnamevalue << tokens[1] << "=" << tokens[2]; // reassemble ParameterName=value string - - // build an STL map "configmap" indexed on PARAMETERn, the part before the first "=" sign - // - this->configmap[ tokens[0] ].line = linecount; // configuration line number - this->configmap[ tokens[0] ].value = paramnamevalue.str(); // configuration value for PARAMETERn - - // build an STL map "parammap" indexed on ParameterName so that we can look up by the actual name - // - this->parammap[ tokens[1] ].key = tokens[0]; // PARAMETERn - this->parammap[ tokens[1] ].name = tokens[1] ; // ParameterName - this->parammap[ tokens[1] ].value = tokens[2]; // value - this->parammap[ tokens[1] ].line = linecount; // line number - - // assemble a KEY=VALUE pair used to form the WCONFIG command - key = tokens[0]; // PARAMETERn - value = paramnamevalue.str(); // ParameterName=value - // end If this is a PARAMETERn=ParameterName=value KEY=VALUE pair... - - } else { - // ...otherwise, for all other KEY=VALUE pairs, there is only the value and line number - // to be indexed by the key. Some lines may be equal to blank, e.g. "CONSTANTx=" so that - // only one token is made - // - std::vector tokens; - // Tokenize will return a size=1 even if there are no delimiters, - // so work around this by first checking for delimiters - // before calling Tokenize. - // - if (line.find_first_of('=', 0) == std::string::npos) { - continue; - } - Tokenize(line, tokens, "="); // separate into KEY, VALUE tokens - if (tokens.empty()) { - continue; // nothing to do here if no tokens (ie no "=") - } - if (!tokens.empty() ) { // at least one token is the key - key = tokens[0]; // KEY - value = ""; // VALUE can be empty (e.g. labels not required) - this->configmap[ tokens[0] ].line = linecount; - this->configmap[ tokens[0] ].value = value; - } - if (tokens.size() > 1 ) { // if a second token then that's the value - value = tokens[1]; // VALUE (there is a second token) - this->configmap[ tokens[0] ].value = tokens[1]; - } - } // end else - - // Form the WCONFIG command to Archon and - // write the config line to the controller memory (if key is not empty). - // - if ( !key.empty() ) { // value can be empty but key cannot - sscmd.str(""); - sscmd << "WCONFIG" - << std::uppercase << std::setfill('0') << std::setw(4) << std::hex - << linecount - << key << "=" << value << "\n"; - // send the WCONFIG command here - if (error == NO_ERROR) error = this->archon_cmd(sscmd.str()); - } // end if ( !key.empty() && !value.empty() ) - linecount++; - } // end while ( getline(filestream, line) ) - - // re-enable background polling - // - if (error == NO_ERROR) error = this->archon_cmd(POLLON); - - filestream.close(); - if (error == NO_ERROR) { - logwrite(function, "loaded Archon config file OK"); - this->firmwareloaded = true; - - // add to systemkeys keyword database - // - std::stringstream keystr; - keystr << "FIRMWARE=" << acffile << "// controller firmware"; - this->systemkeys.addkey( keystr.str() ); - } - // If there was an Archon error then read the Archon error log - // - if (error != NO_ERROR) error = this->fetchlog(); + /**************** Archon::Interface::load_firmware **************************/ + /** + * @fn load_firmware + * @brief loads the ACF file and applies the complete system configuration + * @param none + * @return + * + * This function is overloaded. + * + * This version takes a single argument for the acf file to load. + * + * This function loads the ACF file and then sends an APPLYALL which + * parses and applies the complete system configuration from the + * configuration memory just loaded. The detector power will be off. + * + */ + long Interface::load_firmware(std::string acffile) { + // load the ACF file into configuration memory + // + long error = this->load_acf(acffile); - this->modeselected = false; // require that a mode be selected after loading new firmware + // Parse and apply the complete system configuration from configuration memory. + // Detector power will be off after this. + // + if (error == NO_ERROR) error = this->archon_cmd(APPLYALL); - // Even if exptime, longexposure were previously set, a new ACF could have different - // default values than the server has, so reset these to "undefined" in order to - // force the server to ask for them. - // - this->camera_info.exposure_time = -1; - this->camera_info.exposure_factor = -1; - this->camera_info.exposure_unit.clear(); - - return error; - } - /**************** Archon::Interface::load_acf *******************************/ - - - /**************** Archon::Interface::set_camera_mode ************************/ - /** - * @fn set_camera_mode - * @brief - * @param none - * @return - * - */ - long Interface::set_camera_mode(std::string mode) { - std::string function = "Archon::Interface::set_camera_mode"; - std::stringstream message; - bool configchanged = false; - bool paramchanged = false; - long error; - - // No point in trying anything if no firmware has been loaded yet - // - if ( ! this->firmwareloaded ) { - this->camera.log_error( function, "no firmware loaded" ); - return ERROR; - } + if (error != NO_ERROR) this->fetchlog(); - std::transform( mode.begin(), mode.end(), mode.begin(), ::toupper ); // make uppercase + // If no errors then automatically set the mode to DEFAULT. + // This should come after APPLYALL in case any new parameters need to be written, + // which shouldn't be done until after they have been applied. + // + if (error == NO_ERROR) error = this->set_camera_mode(std::string("DEFAULT")); - // The requested mode must have been read in the current ACF file - // and put into the modemap... - // - if (this->modemap.find(mode) == this->modemap.end()) { - message.str(""); message << "undefined mode " << mode << " in ACF file " << this->camera.firmware[0]; - this->camera.log_error( function, message.str() ); - return ERROR; + return error; } - // load specific mode settings from .acf and apply to Archon - // - if ( load_mode_settings(mode) != NO_ERROR) { - message.str(""); message << "ERROR: failed to load mode settings for mode: " << mode; - logwrite( function, message.str() ); - return ERROR; + /**************** Archon::Interface::load_firmware **************************/ + /** + * @fn load_firmware + * @brief + * @param none + * @return + * + * This function is overloaded. + * + * This version is for future compatibility. + * The multiple-controller version will pass a reference to a return string. + * + */ + long Interface::load_firmware(std::string acffile, std::string &retstring) { + return (this->load_firmware(acffile)); } - // set internal variables based on new .acf values loaded - // - error = NO_ERROR; - if (error==NO_ERROR) error = get_configmap_value("FRAMEMODE", this->modemap[mode].geometry.framemode); - if (error==NO_ERROR) error = get_configmap_value("LINECOUNT", this->modemap[mode].geometry.linecount); - if (error==NO_ERROR) error = get_configmap_value("PIXELCOUNT", this->modemap[mode].geometry.pixelcount); - if (error==NO_ERROR) error = get_configmap_value("RAWENABLE", this->modemap[mode].rawenable); - if (error==NO_ERROR) error = get_configmap_value("RAWSEL", this->rawinfo.adchan); - if (error==NO_ERROR) error = get_configmap_value("RAWSAMPLES", this->rawinfo.rawsamples); - if (error==NO_ERROR) error = get_configmap_value("RAWENDLINE", this->rawinfo.rawlines); - - #ifdef LOGLEVEL_DEBUG - message.str(""); - message << "[DEBUG] mode=" << mode << " RAWENABLE=" << this->modemap[mode].rawenable - << " RAWSAMPLES=" << this->rawinfo.rawsamples << " RAWLINES=" << this->rawinfo.rawlines; - logwrite(function, message.str()); - #endif - - // get out if any errors at this point - // - if ( error != NO_ERROR ) { logwrite( function, "ERROR: one or more internal variables missing from configmap" ); return error; } + /**************** Archon::Interface::load_firmware **************************/ - int num_detect = this->modemap[mode].geometry.num_detect; // for convenience - // set current number of Archon buffers and resize local memory - // get out if an error - // - int bigbuf=-1; - if (error==NO_ERROR) error = get_configmap_value("BIGBUF", bigbuf); // get value of BIGBUF from loaded acf file - this->camera_info.activebufs = (bigbuf==1) ? 2 : 3; // set number of active buffers based on BIGBUF - if ( error != NO_ERROR ) { logwrite( function, "ERROR: unable to read BIGBUF from ACF" ); return error; } + /**************** Archon::Interface::load_acf *******************************/ + /** + * @fn load_acf + * @brief loads the ACF file into configuration memory (no APPLY!) + * @param acffile + * @return ERROR or NO_ERROR + * + * This function loads the specfied file into configuration memory. + * While the ACF is being read, an internal database (STL map) is being + * created to allow lookup access to the ACF file or parameters. + * + * The [MODE_XXX] sections are also parsed and parameters as a function + * of mode are saved in their own database. + * + * This function only loads (WCONFIGxxx) the configuration memory; it does + * not apply it to the system. Therefore, this function must be followed + * with a LOADTIMING or APPLYALL command, for example. + * + */ + long Interface::load_acf(std::string acffile) { + std::string function = "Archon::Interface::load_acf"; + std::stringstream message; + std::fstream filestream; // I/O stream class + std::string line; // the line read from the acffile + std::string mode; + std::string keyword, keystring, keyvalue, keytype, keycomment; + std::stringstream sscmd; + std::string key, value; + + int linecount; // the Archon configuration line number is required for writing back to config memory + long error = NO_ERROR; + bool parse_config = false; - // There is one special reserved mode name, "RAW" - // - if (mode=="RAW") { - this->camera_info.detector_pixels[0] = this->rawinfo.rawsamples; - this->camera_info.detector_pixels[1] = this->rawinfo.rawlines; - this->camera_info.detector_pixels[1]++; - // frame_type will determine the bits per pixel and where the detector_axes come from - this->camera_info.frame_type = Camera::FRAME_RAW; - this->camera_info.region_of_interest[0] = 1; - this->camera_info.region_of_interest[1] = this->camera_info.detector_pixels[0]; - this->camera_info.region_of_interest[2] = 1; - this->camera_info.region_of_interest[3] = this->camera_info.detector_pixels[1]; - // Binning factor (no binning) - this->camera_info.binning[0] = 1; - this->camera_info.binning[1] = 1; - #ifdef LOGLEVEL_DEBUG + // get the acf filename, either passed here or from loaded default + // + if (acffile.empty()) { + acffile = this->camera.firmware[0]; + } else { + this->camera.firmware[0] = acffile; + } + + // try to open the file + // + try { + filestream.open(acffile, std::ios_base::in); + } catch (...) { + message << "opening acf file " << acffile << ": " << std::strerror(errno); + this->camera.log_error(function, message.str()); + return ERROR; + } + + if (!filestream.is_open() || !filestream.good()) { + message << "acf file " << acffile << " could not be opened"; + this->camera.log_error(function, message.str()); + return ERROR; + } + + logwrite(function, acffile); + + // The CPU in Archon is single threaded, so it checks for a network + // command, then does some background polling (reading bias voltages etc.), + // then checks again for a network command. "POLLOFF" disables this + // background checking, so network command responses are very fast. + // The downside is that bias voltages, temperatures, etc. are not updated + // until you give a "POLLON". + // + error = this->archon_cmd(POLLOFF); + + // clear configuration memory for this controller + // + if (error == NO_ERROR) error = this->archon_cmd(CLEARCONFIG); + + if (error != NO_ERROR) { + logwrite(function, "ERROR: could not prepare Archon for new ACF"); + return error; + } + + // Any failure after clearing the configuration memory will mean + // no firmware is loaded. + // + this->firmwareloaded = false; + + modemap.clear(); // file is open, clear all modes + + linecount = 0; // init Archon configuration line number + + while (getline(filestream, line)) { + // note that getline discards the newline "\n" character + + // don't start parsing until [CONFIG] and stop on a newline or [SYSTEM] + // + if (line == "[CONFIG]") { + parse_config = true; + continue; + } + if (line == "\n") { + parse_config = false; + continue; + } + if (line == "[SYSTEM]") { + parse_config = false; + continue; + } + + std::string savedline = line; // save un-edited line for error reporting + + // parse mode sections, looking for "[MODE_xxxxx]" + // + if (line.substr(0, 6) == "[MODE_") { + // this is a mode section + try { + line.erase(line.find('['), 1); // erase the opening square bracket + line.erase(line.find(']'), 1); // erase the closing square bracket + } catch (...) { + message.str(""); + message << "malformed mode section: " << savedline << ": expected [MODE_xxxx]"; + this->camera.log_error(function, message.str()); + filestream.close(); + return ERROR; + } + if (!line.empty()) { + // What's remaining should be MODE_xxxxx + mode = line.substr(5); // everything after "MODE_" is the mode name + std::transform(mode.begin(), mode.end(), mode.begin(), ::toupper); // make uppercase + + // got a mode, now check if one of this name has already been located + // and put into the modemap + // + if (this->modemap.find(mode) != this->modemap.end()) { + message.str(""); + message << "duplicate definition of mode: " << mode << ": load aborted"; + this->camera.log_error(function, message.str()); + filestream.close(); + return ERROR; + } else { + parse_config = true; + message.str(""); + message << "detected mode: " << mode; + logwrite(function, message.str()); + this->modemap[mode].rawenable = -1; // initialize to -1, require it be set somewhere in the ACF + // this also ensures something is saved in the modemap for this mode + } + } else { + // somehow there's no xxx left after removing "[MODE_" and "]" + message.str(""); + message << "malformed mode section: " << savedline << ": expected [MODE_xxxx]"; + this->camera.log_error(function, message.str()); + filestream.close(); + return ERROR; + } + } + + // Everything else is for parsing configuration lines so if we didn't get [CONFIG] then + // skip to the next line. + // + if (!parse_config) continue; + + // replace any TAB characters with a space + // + string_replace_char(line, "\t", " "); + + // replace any backslash characters with a forward slash + // + string_replace_char(line, "\\", "/"); + + // erase all quotes + try { + line.erase(std::remove(line.begin(), line.end(), '"'), line.end()); + } catch (...) { + } + + // Initialize key, value strings used to form WCONFIG KEY=VALUE command. + // As long as key stays empty then the WCONFIG command is not written to the Archon. + // This is what keeps TAGS: in the [MODE_xxxx] sections from being written to Archon, + // because these do not populate key. + // + key = ""; + value = ""; + + // ************************************************************ + // Store actual Archon parameters in their own STL map IN ADDITION to the map + // in which all other keywords are store, so that they can be accessed in + // a different way. Archon PARAMETER KEY=VALUE paris are formatted as: + // PARAMETERn=ParameterName=value + // where "PARAMETERn" is the key and "ParameterName=value" is the value. + // However, it is logical to access them by ParameterName only. That is what the + // parammap is for, hence the need for this STL map indexed on only the "ParameterName" + // portion of the value. Conversely, the configmap is indexed by the key. + // + // parammap stores ONLY the parameters, which are identified as PARAMETERxx="paramname=value" + // configmap stores every configuration line sent to Archon (which includes parameters) + // + // In order to modify these keywords in Archon, the entire above phrase + // (KEY=VALUE pair) must be preserved along with the line number on which it + // occurs in the config file. + // ************************************************************ + + // Look for TAGS: in the .acf file mode sections + // + // If tag is "ACF:" then it's a .acf line (could be a parameter or configuration) + // + if (line.compare(0, 4, "ACF:") == 0) { + std::vector tokens; + line = line.substr(4); // strip off the "ACF:" portion + std::string acf_key, acf_value; + + try { + Tokenize(line, tokens, "="); // separate into tokens by "=" + + if (tokens.size() == 1) { + // KEY=, the VALUE is empty + acf_key = tokens[0]; + acf_value = ""; + } else if (tokens.size() == 2) { + // KEY=VALUE + acf_key = tokens[0]; + acf_value = tokens[1]; + } else { + message.str(""); + message << "malformed ACF line: " << savedline << ": expected KEY=VALUE"; + this->camera.log_error(function, message.str()); + filestream.close(); + return ERROR; + } + + bool keymatch = false; + + // If this key is in the main parammap then store it in the modemap's parammap for this mode + if (this->parammap.find(acf_key) != this->parammap.end()) { + this->modemap[mode].parammap[acf_key].name = acf_key; + this->modemap[mode].parammap[acf_key].value = acf_value; + keymatch = true; + } + + // If this key is in the main configmap, then store it in the modemap's configmap for this mode + // + if (this->configmap.find(acf_key) != this->configmap.end()) { + this->modemap[mode].configmap[acf_key].value = acf_value; + keymatch = true; + } + + // If this key is neither in the parammap nor in the configmap then return an error + // + if (!keymatch) { + message.str(""); + message << "[MODE_" << mode << "] ACF directive: " << acf_key << "=" << acf_value << + " is not a valid parameter or configuration key"; + logwrite(function, message.str()); + filestream.close(); + return ERROR; + } + } catch (...) { + message.str(""); + message << "extracting KEY=VALUE pair from ACF line: " << savedline; + this->camera.log_error(function, message.str()); + filestream.close(); + return ERROR; + } + // end if (line.compare(0,4,"ACF:")==0) + } else if (line.compare(0, 5, "ARCH:") == 0) { + // The "ARCH:" tag is for internal (Archon_interface) variables + // using the KEY=VALUE format. + // + std::vector tokens; + line = line.substr(5); // strip off the "ARCH:" portion + Tokenize(line, tokens, "="); // separate into KEY, VALUE tokens + if (tokens.size() != 2) { + message.str(""); + message << "malformed ARCH line: " << savedline << ": expected ARCH:KEY=VALUE"; + this->camera.log_error(function, message.str()); + filestream.close(); + return ERROR; + } + if (tokens[0] == "NUM_DETECT") { + this->modemap[mode].geometry.num_detect = std::stoi(tokens[1]); + } else if (tokens[0] == "HORI_AMPS") { + this->modemap[mode].geometry.amps[0] = std::stoi(tokens[1]); + } else if (tokens[0] == "VERT_AMPS") { + this->modemap[mode].geometry.amps[1] = std::stoi(tokens[1]); + } else { + message.str(""); + message << "unrecognized internal parameter specified: " << tokens[0]; + this->camera.log_error(function, message.str()); + filestream.close(); + return ERROR; + } + // end else if (line.compare(0,5,"ARCH:")==0) + } else if (line.compare(0, 5, "FITS:") == 0) { + // the "FITS:" tag is used to write custom keyword entries of the form "FITS:KEYWORD=VALUE/COMMENT" + // + std::vector tokens; + line = line.substr(5); // strip off the "FITS:" portion + + // First, tokenize on the equal sign "=". + // The token left of "=" is the keyword. Immediate right is the value + Tokenize(line, tokens, "="); + if (tokens.size() != 2) { + // need at least two tokens at this point + message.str(""); + message << "malformed FITS command: " << savedline << ": expected KEYWORD=value/comment"; + this->camera.log_error(function, message.str()); + filestream.close(); + return ERROR; + } + keyword = tokens[0].substr(0, 8); // truncate keyword to 8 characters + keystring = tokens[1]; // tokenize the rest in a moment + keycomment = ""; // initialize comment, assumed empty unless specified below + + // Next, tokenize on the slash "/". + // The token left of "/" is the value. Anything to the right is a comment. + // + Tokenize(keystring, tokens, "/"); + + if (tokens.empty()) { + // no tokens found means no "/" characeter which means no comment + keyvalue = keystring; // therefore the keyvalue is the entire string + } + + if (not tokens.empty()) { + // at least one token + keyvalue = tokens[0]; + } + + if (tokens.size() == 2) { + // If there are two tokens here then the second is a comment + keycomment = tokens[1]; + } + + if (tokens.size() > 2) { + // everything below this has been covered + message.str(""); + message << "malformed FITS command: " << savedline << ": expected KEYWORD=VALUE/COMMENT"; + this->camera.log_error(function, message.str()); + message.str(""); + message << "too many \"/\" in comment string? " << keystring; + this->camera.log_error(function, message.str()); + filestream.close(); + return ERROR; + } + + // Save all the user keyword information in a map for later + this->modemap[mode].acfkeys.keydb[keyword].keyword = keyword; + this->modemap[mode].acfkeys.keydb[keyword].keytype = this->camera_info.userkeys.get_keytype(keyvalue); + this->modemap[mode].acfkeys.keydb[keyword].keyvalue = keyvalue; + this->modemap[mode].acfkeys.keydb[keyword].keycomment = keycomment; + // end if (line.compare(0,5,"FITS:")==0) + // + // ----- all done looking for "TAGS:" ----- + // + } else if ((line.compare(0, 11, "PARAMETERS=") != 0) && // not the "PARAMETERS=xx line + (line.compare(0, 9, "PARAMETER") == 0)) { + // but must start with "PARAMETER" + // If this is a PARAMETERn=ParameterName=value KEY=VALUE pair... + // + + std::vector tokens; + Tokenize(line, tokens, "="); // separate into PARAMETERn, ParameterName, value tokens + + if (tokens.size() != 3) { + message.str(""); + message << "malformed paramter line: " << savedline << ": expected PARAMETERn=Param=value"; + this->camera.log_error(function, message.str()); + filestream.close(); + return ERROR; + } + + // Tokenize broke everything up at the "=" and + // we need all three parts, but we also need a version containing the last + // two parts together, "ParameterName=value" so re-assemble them here. + // + std::stringstream paramnamevalue; + paramnamevalue << tokens[1] << "=" << tokens[2]; // reassemble ParameterName=value string + + // build an STL map "configmap" indexed on PARAMETERn, the part before the first "=" sign + // + this->configmap[tokens[0]].line = linecount; // configuration line number + this->configmap[tokens[0]].value = paramnamevalue.str(); // configuration value for PARAMETERn + + // build an STL map "parammap" indexed on ParameterName so that we can look up by the actual name + // + this->parammap[tokens[1]].key = tokens[0]; // PARAMETERn + this->parammap[tokens[1]].name = tokens[1]; // ParameterName + this->parammap[tokens[1]].value = tokens[2]; // value + this->parammap[tokens[1]].line = linecount; // line number + + // assemble a KEY=VALUE pair used to form the WCONFIG command + key = tokens[0]; // PARAMETERn + value = paramnamevalue.str(); // ParameterName=value + // end If this is a PARAMETERn=ParameterName=value KEY=VALUE pair... + } else { + // ...otherwise, for all other KEY=VALUE pairs, there is only the value and line number + // to be indexed by the key. Some lines may be equal to blank, e.g. "CONSTANTx=" so that + // only one token is made + // + std::vector tokens; + // Tokenize will return a size=1 even if there are no delimiters, + // so work around this by first checking for delimiters + // before calling Tokenize. + // + if (line.find_first_of('=', 0) == std::string::npos) { + continue; + } + Tokenize(line, tokens, "="); // separate into KEY, VALUE tokens + if (tokens.empty()) { + continue; // nothing to do here if no tokens (ie no "=") + } + if (!tokens.empty()) { + // at least one token is the key + key = tokens[0]; // KEY + value = ""; // VALUE can be empty (e.g. labels not required) + this->configmap[tokens[0]].line = linecount; + this->configmap[tokens[0]].value = value; + } + if (tokens.size() > 1) { + // if a second token then that's the value + value = tokens[1]; // VALUE (there is a second token) + this->configmap[tokens[0]].value = tokens[1]; + } + } // end else + + // Form the WCONFIG command to Archon and + // write the config line to the controller memory (if key is not empty). + // + if (!key.empty()) { + // value can be empty but key cannot + sscmd.str(""); + sscmd << "WCONFIG" + << std::uppercase << std::setfill('0') << std::setw(4) << std::hex + << linecount + << key << "=" << value << "\n"; + // send the WCONFIG command here + if (error == NO_ERROR) error = this->archon_cmd(sscmd.str()); + } // end if ( !key.empty() && !value.empty() ) + linecount++; + } // end while ( getline(filestream, line) ) + + // re-enable background polling + // + if (error == NO_ERROR) error = this->archon_cmd(POLLON); + + filestream.close(); + if (error == NO_ERROR) { + logwrite(function, "loaded Archon config file OK"); + this->firmwareloaded = true; + + // add to systemkeys keyword database + // + std::stringstream keystr; + keystr << "FIRMWARE=" << acffile << "// controller firmware"; + this->systemkeys.addkey(keystr.str()); + } + + // If there was an Archon error then read the Archon error log + // + if (error != NO_ERROR) error = this->fetchlog(); + + this->modeselected = false; // require that a mode be selected after loading new firmware + + // Even if exptime, longexposure were previously set, a new ACF could have different + // default values than the server has, so reset these to "undefined" in order to + // force the server to ask for them. + // + this->camera_info.exposure_time = -1; + this->camera_info.exposure_factor = -1; + this->camera_info.exposure_unit.clear(); + + return error; + } + + /**************** Archon::Interface::load_acf *******************************/ + + + /**************** Archon::Interface::set_camera_mode ************************/ + /** + * @fn set_camera_mode + * @brief + * @param none + * @return + * + */ + long Interface::set_camera_mode(std::string mode) { + std::string function = "Archon::Interface::set_camera_mode"; + std::stringstream message; + bool configchanged = false; + bool paramchanged = false; + long error; + + // No point in trying anything if no firmware has been loaded yet + // + if (!this->firmwareloaded) { + this->camera.log_error(function, "no firmware loaded"); + return ERROR; + } + + std::transform(mode.begin(), mode.end(), mode.begin(), ::toupper); // make uppercase + + // The requested mode must have been read in the current ACF file + // and put into the modemap... + // + if (this->modemap.find(mode) == this->modemap.end()) { + message.str(""); + message << "undefined mode " << mode << " in ACF file " << this->camera.firmware[0]; + this->camera.log_error(function, message.str()); + return ERROR; + } + + // load specific mode settings from .acf and apply to Archon + // + if (load_mode_settings(mode) != NO_ERROR) { + message.str(""); + message << "ERROR: failed to load mode settings for mode: " << mode; + logwrite(function, message.str()); + return ERROR; + } + + // set internal variables based on new .acf values loaded + // + error = NO_ERROR; + if (error == NO_ERROR) error = get_configmap_value("FRAMEMODE", this->modemap[mode].geometry.framemode); + if (error == NO_ERROR) error = get_configmap_value("LINECOUNT", this->modemap[mode].geometry.linecount); + if (error == NO_ERROR) error = get_configmap_value("PIXELCOUNT", this->modemap[mode].geometry.pixelcount); + if (error == NO_ERROR) error = get_configmap_value("RAWENABLE", this->modemap[mode].rawenable); + if (error == NO_ERROR) error = get_configmap_value("RAWSEL", this->rawinfo.adchan); + if (error == NO_ERROR) error = get_configmap_value("RAWSAMPLES", this->rawinfo.rawsamples); + if (error == NO_ERROR) error = get_configmap_value("RAWENDLINE", this->rawinfo.rawlines); + +#ifdef LOGLEVEL_DEBUG + message.str(""); + message << "[DEBUG] mode=" << mode << " RAWENABLE=" << this->modemap[mode].rawenable + << " RAWSAMPLES=" << this->rawinfo.rawsamples << " RAWLINES=" << this->rawinfo.rawlines; + logwrite(function, message.str()); +#endif + + // get out if any errors at this point + // + if (error != NO_ERROR) { + logwrite(function, "ERROR: one or more internal variables missing from configmap"); + return error; + } + + int num_detect = this->modemap[mode].geometry.num_detect; // for convenience + + // set current number of Archon buffers and resize local memory + // get out if an error + // + int bigbuf = -1; + if (error == NO_ERROR) error = get_configmap_value("BIGBUF", bigbuf); + // get value of BIGBUF from loaded acf file + this->camera_info.activebufs = (bigbuf == 1) ? 2 : 3; // set number of active buffers based on BIGBUF + if (error != NO_ERROR) { + logwrite(function, "ERROR: unable to read BIGBUF from ACF"); + return error; + } + + // There is one special reserved mode name, "RAW" + // + if (mode == "RAW") { + this->camera_info.detector_pixels[0] = this->rawinfo.rawsamples; + this->camera_info.detector_pixels[1] = this->rawinfo.rawlines; + this->camera_info.detector_pixels[1]++; + // frame_type will determine the bits per pixel and where the detector_axes come from + this->camera_info.frame_type = Camera::FRAME_RAW; + this->camera_info.region_of_interest[0] = 1; + this->camera_info.region_of_interest[1] = this->camera_info.detector_pixels[0]; + this->camera_info.region_of_interest[2] = 1; + this->camera_info.region_of_interest[3] = this->camera_info.detector_pixels[1]; + // Binning factor (no binning) + this->camera_info.binning[0] = 1; + this->camera_info.binning[1] = 1; +#ifdef LOGLEVEL_DEBUG message.str(""); message << "[DEBUG] this->camera_info.detector_pixels[0] (RAWSAMPLES) = " << this->camera_info.detector_pixels[0]; logwrite(function, message.str()); message.str(""); message << "[DEBUG] this->camera_info.detector_pixels[1] (RAWENDLINE) = " << this->camera_info.detector_pixels[1]; logwrite(function, message.str()); - #endif - - } else { - // Any other mode falls under here - if (error==NO_ERROR) error = get_configmap_value("PIXELCOUNT", this->camera_info.detector_pixels[0]); - if (error==NO_ERROR) error = get_configmap_value("LINECOUNT", this->camera_info.detector_pixels[1]); - #ifdef LOGLEVEL_DEBUG +#endif + } else { + // Any other mode falls under here + if (error == NO_ERROR) error = get_configmap_value("PIXELCOUNT", this->camera_info.detector_pixels[0]); + if (error == NO_ERROR) error = get_configmap_value("LINECOUNT", this->camera_info.detector_pixels[1]); +#ifdef LOGLEVEL_DEBUG message.str(""); message << "[DEBUG] mode=" << mode; logwrite(function, message.str()); message.str(""); message << "[DEBUG] this->camera_info.detector_pixels[0] (PIXELCOUNT) = " << this->camera_info.detector_pixels[0] << " amps[0] = " << this->modemap[mode].geometry.amps[0]; @@ -1750,125 +1831,145 @@ namespace Archon { message.str(""); message << "[DEBUG] this->camera_info.detector_pixels[1] (LINECOUNT) = " << this->camera_info.detector_pixels[1] << " amps[1] = " << this->modemap[mode].geometry.amps[1]; logwrite(function, message.str()); - #endif - this->camera_info.detector_pixels[0] *= this->modemap[mode].geometry.amps[0]; - this->camera_info.detector_pixels[1] *= this->modemap[mode].geometry.amps[1]; - this->camera_info.frame_type = Camera::FRAME_IMAGE; - // ROI is the full detector - this->camera_info.region_of_interest[0] = 1; - this->camera_info.region_of_interest[1] = this->camera_info.detector_pixels[0]; - this->camera_info.region_of_interest[2] = 1; - this->camera_info.region_of_interest[3] = this->camera_info.detector_pixels[1]; - // Binning factor (no binning) - this->camera_info.binning[0] = 1; - this->camera_info.binning[1] = 1; - #ifdef LOGLEVEL_DEBUG +#endif + this->camera_info.detector_pixels[0] *= this->modemap[mode].geometry.amps[0]; + this->camera_info.detector_pixels[1] *= this->modemap[mode].geometry.amps[1]; + this->camera_info.frame_type = Camera::FRAME_IMAGE; + // ROI is the full detector + this->camera_info.region_of_interest[0] = 1; + this->camera_info.region_of_interest[1] = this->camera_info.detector_pixels[0]; + this->camera_info.region_of_interest[2] = 1; + this->camera_info.region_of_interest[3] = this->camera_info.detector_pixels[1]; + // Binning factor (no binning) + this->camera_info.binning[0] = 1; + this->camera_info.binning[1] = 1; +#ifdef LOGLEVEL_DEBUG message.str(""); message << "[DEBUG] this->camera_info.detector_pixels[0] (PIXELCOUNT) = " << this->camera_info.detector_pixels[0]; logwrite(function, message.str()); message.str(""); message << "[DEBUG] this->camera_info.detector_pixels[1] (LINECOUNT) = " << this->camera_info.detector_pixels[1]; logwrite(function, message.str()); - #endif - if ( error != NO_ERROR ) { logwrite( function, "ERROR: unable to get PIXELCOUNT,LINECOUNT from ACF" ); return error; } - } +#endif + if (error != NO_ERROR) { + logwrite(function, "ERROR: unable to get PIXELCOUNT,LINECOUNT from ACF"); + return error; + } + } - // set bitpix based on SAMPLEMODE - // - int samplemode=-1; - if (error==NO_ERROR) error = get_configmap_value("SAMPLEMODE", samplemode); // SAMPLEMODE=0 for 16bpp, =1 for 32bpp - if ( error != NO_ERROR ) { logwrite( function, "ERROR: unable to get SAMPLEMODE from ACF" ); return error; } - if (samplemode < 0) { this->camera.log_error( function, "bad or missing SAMPLEMODE from ACF" ); return ERROR; } - this->camera_info.bitpix = (samplemode==0) ? 16 : 32; - - // Load parameters and Apply CDS/Deint configuration if any of them changed - if ((error == NO_ERROR) && paramchanged) error = this->archon_cmd(LOADPARAMS); // TODO I think paramchanged is never set! - if ((error == NO_ERROR) && configchanged) error = this->archon_cmd(APPLYCDS); // TODO I think configchanged is never set! - - // Get the current frame buffer status - if (error == NO_ERROR) error = this->get_frame_status(); - if (error != NO_ERROR) { - logwrite( function, "ERROR: unable to get frame status" ); - return error; - } + // set bitpix based on SAMPLEMODE + // + int samplemode = -1; + if (error == NO_ERROR) error = get_configmap_value("SAMPLEMODE", samplemode); + // SAMPLEMODE=0 for 16bpp, =1 for 32bpp + if (error != NO_ERROR) { + logwrite(function, "ERROR: unable to get SAMPLEMODE from ACF"); + return error; + } + if (samplemode < 0) { + this->camera.log_error(function, "bad or missing SAMPLEMODE from ACF"); + return ERROR; + } + this->camera_info.bitpix = (samplemode == 0) ? 16 : 32; - // Set axes, image dimensions, calculate image_memory, etc. - // Raw will always be 16 bpp (USHORT). - // Image can be 16 or 32 bpp depending on SAMPLEMODE setting in ACF. - // Call set_axes(datatype) with the FITS data type needed, which will set the info.datatype variable. - // - error = this->camera_info.set_axes(); // 16 bit raw is unsigned short int -/********* - if (this->camera_info.frame_type == Camera::FRAME_RAW) { - error = this->camera_info.set_axes(USHORT_IMG); // 16 bit raw is unsigned short int - } - if (this->camera_info.frame_type == Camera::FRAME_IMAGE) { - if (this->camera_info.bitpix == 16) error = this->camera_info.set_axes(SHORT_IMG); // 16 bit image is short int - else - if (this->camera_info.bitpix == 32) error = this->camera_info.set_axes(FLOAT_IMG); // 32 bit image is float - else { - message.str(""); message << "bad bitpix " << this->camera_info.bitpix << ": expected 16 | 32"; - this->camera.log_error( function, message.str() ); - return (ERROR); - } - } -*********/ - if (error != NO_ERROR) { - this->camera.log_error( function, "setting axes" ); - return (ERROR); - } + // Load parameters and Apply CDS/Deint configuration if any of them changed + if ((error == NO_ERROR) && paramchanged) error = this->archon_cmd(LOADPARAMS); + // TODO I think paramchanged is never set! + if ((error == NO_ERROR) && configchanged) error = this->archon_cmd(APPLYCDS); + // TODO I think configchanged is never set! - // allocate image_data in blocks because the controller outputs data in units of blocks - // - this->image_data_bytes = (uint32_t) floor( ((this->camera_info.image_memory * num_detect) + BLOCK_LEN - 1 ) / BLOCK_LEN ) * BLOCK_LEN; + // Get the current frame buffer status + if (error == NO_ERROR) error = this->get_frame_status(); + if (error != NO_ERROR) { + logwrite(function, "ERROR: unable to get frame status"); + return error; + } - if (this->image_data_bytes == 0) { - this->camera.log_error( function, "image data size is zero! check NUM_DETECT, HORI_AMPS, VERT_AMPS in .acf file" ); - error = ERROR; - } + // Set axes, image dimensions, calculate image_memory, etc. + // Raw will always be 16 bpp (USHORT). + // Image can be 16 or 32 bpp depending on SAMPLEMODE setting in ACF. + // Call set_axes(datatype) with the FITS data type needed, which will set the info.datatype variable. + // + error = this->camera_info.set_axes(); // 16 bit raw is unsigned short int + /********* + if (this->camera_info.frame_type == Camera::FRAME_RAW) { + error = this->camera_info.set_axes(USHORT_IMG); // 16 bit raw is unsigned short int + } + if (this->camera_info.frame_type == Camera::FRAME_IMAGE) { + if (this->camera_info.bitpix == 16) error = this->camera_info.set_axes(SHORT_IMG); // 16 bit image is short int + else + if (this->camera_info.bitpix == 32) error = this->camera_info.set_axes(FLOAT_IMG); // 32 bit image is float + else { + message.str(""); message << "bad bitpix " << this->camera_info.bitpix << ": expected 16 | 32"; + this->camera.log_error( function, message.str() ); + return (ERROR); + } + } + *********/ + if (error != NO_ERROR) { + this->camera.log_error(function, "setting axes"); + return (ERROR); + } - this->camera_info.current_observing_mode = mode; // identify the newly selected mode in the camera_info class object - this->modeselected = true; // a valid mode has been selected + // allocate image_data in blocks because the controller outputs data in units of blocks + // + this->image_data_bytes = (uint32_t) floor( + ((this->camera_info.image_memory * num_detect) + BLOCK_LEN - 1) / BLOCK_LEN) * + BLOCK_LEN; - message.str(""); message << "new mode: " << mode << " will use " << this->camera_info.bitpix << " bits per pixel"; - logwrite(function, message.str()); + if (this->image_data_bytes == 0) { + this->camera.log_error( + function, "image data size is zero! check NUM_DETECT, HORI_AMPS, VERT_AMPS in .acf file"); + error = ERROR; + } - // Calculate amplifier sections - // - int rows = this->modemap[mode].geometry.linecount; // rows per section - int cols = this->modemap[mode].geometry.pixelcount; // cols per section + this->camera_info.current_observing_mode = mode; + // identify the newly selected mode in the camera_info class object + this->modeselected = true; // a valid mode has been selected - int hamps = this->modemap[mode].geometry.amps[0]; // horizontal amplifiers - int vamps = this->modemap[mode].geometry.amps[1]; // vertical amplifiers + message.str(""); + message << "new mode: " << mode << " will use " << this->camera_info.bitpix << " bits per pixel"; + logwrite(function, message.str()); - int x0=-1, x1, y0, y1; // for indexing - std::vector coords; // vector of coordinates, convention is x0,x1,y0,y1 - int framemode = this->modemap[mode].geometry.framemode; + // Calculate amplifier sections + // + int rows = this->modemap[mode].geometry.linecount; // rows per section + int cols = this->modemap[mode].geometry.pixelcount; // cols per section - this->camera_info.amp_section.clear(); // vector of coords vectors, one set of coords per extension + int hamps = this->modemap[mode].geometry.amps[0]; // horizontal amplifiers + int vamps = this->modemap[mode].geometry.amps[1]; // vertical amplifiers - for ( int y=0; y coords; // vector of coordinates, convention is x0,x1,y0,y1 + int framemode = this->modemap[mode].geometry.framemode; - } else { - x0++; x1=x0+1; - y0 = 0; y1=1; - } - coords.clear(); - coords.push_back( (x0*cols + 1) ); // x0 is xstart - coords.push_back( (x1)*cols ); // x1 is xstop, xrange = x0:x1 - coords.push_back( (y0*rows + 1) ); // y0 is ystart - coords.push_back( (y1)*rows ); // y1 is ystop, yrange = y0:y1 + this->camera_info.amp_section.clear(); // vector of coords vectors, one set of coords per extension - this->camera_info.amp_section.push_back( coords ); // (x0,x1,y0,y1) for this extension + for (int y = 0; y < vamps; y++) { + for (int x = 0; x < hamps; x++) { + if (framemode == 2) { + x0 = x; + x1 = x + 1; + y0 = y; + y1 = y + 1; + } else { + x0++; + x1 = x0 + 1; + y0 = 0; + y1 = 1; + } + coords.clear(); + coords.push_back((x0 * cols + 1)); // x0 is xstart + coords.push_back((x1) * cols); // x1 is xstop, xrange = x0:x1 + coords.push_back((y0 * rows + 1)); // y0 is ystart + coords.push_back((y1) * rows); // y1 is ystop, yrange = y0:y1 - } - } - message.str(""); message << "identified " << this->camera_info.amp_section.size() << " amplifier sections"; - logwrite( function, message.str() ); + this->camera_info.amp_section.push_back(coords); // (x0,x1,y0,y1) for this extension + } + } + message.str(""); + message << "identified " << this->camera_info.amp_section.size() << " amplifier sections"; + logwrite(function, message.str()); - #ifdef LOGLEVEL_DEBUG +#ifdef LOGLEVEL_DEBUG int ext=0; for ( const auto &sec : this->camera_info.amp_section ) { message.str(""); message << "[DEBUG] extension " << ext++ << ":"; @@ -1877,747 +1978,807 @@ namespace Archon { } logwrite( function, message.str() ); } - #endif - - return error; - } - /**************** Archon::Interface::set_camera_mode ************************/ - - - /**************** Archon::Interface::load_mode_settings *********************/ - /** - * @fn load_mode_settings - * @brief load into Archon settings specified in mode section of .acf file - * @param camera mode - * @return none - * - * At the end of the .acf file are optional sections for each camera - * observing mode. These sections can contain any number of configuration - * lines and parameters to set for the given mode. Those lines are read - * when the configuration file is loaded. This function writes them to - * the Archon controller. - */ - long Interface::load_mode_settings(std::string mode) { - std::string function = "Archon::Interface::load_mode_settings"; - std::stringstream message; - - long error=NO_ERROR; - cfg_map_t::iterator cfg_it; - param_map_t::iterator param_it; - bool paramchanged = false; - bool configchanged = false; - - std::stringstream errstr; +#endif - /** - * iterate through configmap, writing each config key in the map - */ - for (cfg_it = this->modemap[mode].configmap.begin(); - cfg_it != this->modemap[mode].configmap.end(); - cfg_it++) { - error = this->write_config_key( cfg_it->first.c_str(), cfg_it->second.value.c_str(), configchanged ); - if (error != NO_ERROR) { - errstr << "ERROR: writing config key:" << cfg_it->first << " value:" << cfg_it->second.value << " for mode " << mode; - break; - } + return error; } + /**************** Archon::Interface::set_camera_mode ************************/ + + + /**************** Archon::Interface::load_mode_settings *********************/ /** - * if no errors from writing config keys, then - * iterate through the parammap, writing each parameter in the map + * @fn load_mode_settings + * @brief load into Archon settings specified in mode section of .acf file + * @param camera mode + * @return none + * + * At the end of the .acf file are optional sections for each camera + * observing mode. These sections can contain any number of configuration + * lines and parameters to set for the given mode. Those lines are read + * when the configuration file is loaded. This function writes them to + * the Archon controller. */ - if (error == NO_ERROR) { - for (param_it = this->modemap[mode].parammap.begin(); - param_it != this->modemap[mode].parammap.end(); - param_it++) { - error = this->write_parameter( param_it->first.c_str(), param_it->second.value.c_str(), paramchanged ); - message.str(""); message << "paramchanged=" << (paramchanged?"true":"false"); - logwrite(function, message.str()); - if (error != NO_ERROR) { - errstr << "ERROR: writing parameter key:" << param_it->first << " value:" << param_it->second.value << " for mode " << mode; - break; + long Interface::load_mode_settings(std::string mode) { + std::string function = "Archon::Interface::load_mode_settings"; + std::stringstream message; + + long error = NO_ERROR; + cfg_map_t::iterator cfg_it; + param_map_t::iterator param_it; + bool paramchanged = false; + bool configchanged = false; + + std::stringstream errstr; + + /** + * iterate through configmap, writing each config key in the map + */ + for (cfg_it = this->modemap[mode].configmap.begin(); + cfg_it != this->modemap[mode].configmap.end(); + cfg_it++) { + error = this->write_config_key(cfg_it->first.c_str(), cfg_it->second.value.c_str(), configchanged); + if (error != NO_ERROR) { + errstr << "ERROR: writing config key:" << cfg_it->first << " value:" << cfg_it->second.value << + " for mode " << mode; + break; + } } - } - } - /** - * apply the new settings to the system here, only if something changed - */ - if ( (error == NO_ERROR) && paramchanged ) error = this->archon_cmd(LOADPARAMS); - if ( (error == NO_ERROR) && configchanged ) error = this->archon_cmd(APPLYCDS); + /** + * if no errors from writing config keys, then + * iterate through the parammap, writing each parameter in the map + */ + if (error == NO_ERROR) { + for (param_it = this->modemap[mode].parammap.begin(); + param_it != this->modemap[mode].parammap.end(); + param_it++) { + error = this->write_parameter(param_it->first.c_str(), param_it->second.value.c_str(), paramchanged); + message.str(""); + message << "paramchanged=" << (paramchanged ? "true" : "false"); + logwrite(function, message.str()); + if (error != NO_ERROR) { + errstr << "ERROR: writing parameter key:" << param_it->first << " value:" << param_it->second.value + << " for mode " << mode; + break; + } + } + } - if (error == NO_ERROR) { - message.str(""); message << "loaded mode: " << mode; - logwrite(function, message.str()); + /** + * apply the new settings to the system here, only if something changed + */ + if ((error == NO_ERROR) && paramchanged) error = this->archon_cmd(LOADPARAMS); + if ((error == NO_ERROR) && configchanged) error = this->archon_cmd(APPLYCDS); - } else { - logwrite( function, errstr.str() ); - return error; - } + if (error == NO_ERROR) { + message.str(""); + message << "loaded mode: " << mode; + logwrite(function, message.str()); + } else { + logwrite(function, errstr.str()); + return error; + } - // The new mode could contain a ShutterEnable param, - // and if it does then the server needs to know about that. - // - if ( !this->shutenableparam.empty() ) { + // The new mode could contain a ShutterEnable param, + // and if it does then the server needs to know about that. + // + if (!this->shutenableparam.empty()) { + // first read the parameter + // + std::string lshutten; + if (read_parameter(this->shutenableparam, lshutten) != NO_ERROR) { + message.str(""); + message << "ERROR: reading \"" << this->shutenableparam << "\" parameter from Archon"; + logwrite(function, message.str()); + return ERROR; + } - // first read the parameter - // - std::string lshutten; - if ( read_parameter( this->shutenableparam, lshutten ) != NO_ERROR ) { - message.str(""); message << "ERROR: reading \"" << this->shutenableparam << "\" parameter from Archon"; - logwrite( function, message.str() ); - return ERROR; - } + // parse the parameter value + // and convert it to a string for the shutter command + // + std::string shuttenstr; + if (lshutten == "1") shuttenstr = "enable"; + else if (lshutten == "0") shuttenstr = "disable"; + else { + message.str(""); + message << "ERROR: unrecognized shutter enable parameter value " << lshutten << ": expected {0,1}"; + logwrite(function, message.str()); + return ERROR; + } + + // Tell the server + // + std::string dontcare; + if (this->shutter(shuttenstr, dontcare) != NO_ERROR) { + logwrite(function, "ERROR: setting shutter enable parameter"); + return ERROR; + } + } + + /** + * read back some TAPLINE information + */ + if (error == NO_ERROR) error = get_configmap_value("TAPLINES", this->taplines); // total number of taps + + std::vector tokens; + std::stringstream tap; + std::string adchan; + + // Remove all GAIN* and OFFSET* keywords from the systemkeys database + // because the new mode could have different channels (so simply + // over-writing existing keys might not be sufficient). + // + // The new GAIN* and OFFSET* system keys will be added in the next loop. + // + this->systemkeys.EraseKeys("GAIN"); + this->systemkeys.EraseKeys("OFFSET"); + + // Loop through every tap to get the offset for each + // + for (int tapn = 0; tapn < this->taplines; tapn++) { + tap.str(""); + tap << "TAPLINE" << tapn; // used to find the tapline in the configmap + + // The value of TAPLINEn = ADxx,gain,offset -- + // tokenize by comma to separate out each parameter... + // + Tokenize(this->configmap[tap.str().c_str()].value, tokens, ","); + + // If all three tokens present (A?xx,gain,offset) then parse it, + // otherwise it's an unused tap, and we can skip it. + // + if (tokens.size() == 3) { + // defined tap has three tokens + adchan = tokens[0]; // AD channel is the first (0th) token + char chars[] = "ADMLR"; // characters to remove in order to get just the AD channel number + + // Before removing these characters, set the max allowed AD number based on the tapline syntax. + // "ADxx,gain,offset" is for ADC module + // "AMxx,gain,offset" is for ADM module + // + int admax = 0; + if (adchan.find("AD") != std::string::npos) admax = MAXADCCHANS; + else if (adchan.find("AM") != std::string::npos) admax = MAXADMCHANS; + else { + message.str(""); + message << "bad tapline syntax. Expected ADn or AMn but got " << adchan; + this->camera.log_error(function, message.str()); + return ERROR; + } + + // remove AD, AM, L, R from the adchan string, to get just the AD channel number + // + for (unsigned int j = 0; j < strlen(chars); j++) { + adchan.erase(std::remove(adchan.begin(), adchan.end(), chars[j]), adchan.end()); + } + + // AD# in TAPLINE is 1-based (numbered 1,2,3,...) + // but convert here to 0-based (numbered 0,1,2,...) and check value before using + // + int adnum; + try { + adnum = std::stoi(adchan) - 1; + } catch (std::invalid_argument &) { + message.str(""); + message << "unable to convert AD number \'" << adchan << "\' to integer"; + this->camera.log_error(function, message.str()); + return ERROR; + } catch (std::out_of_range &) { + this->camera.log_error(function, "AD number out of integer range"); + return ERROR; + } + + if ((adnum < 0) || (adnum > admax)) { + message.str(""); + message << "ADC channel " << adnum << " outside range {0:" << admax << "}"; + this->camera.log_error(function, message.str()); + return ERROR; + } + // Now that adnum is OK, convert next two tokens to gain, offset + // + int gain_try = 0, offset_try = 0; + try { + gain_try = std::stoi(tokens[1]); // gain as function of AD channel + offset_try = std::stoi(tokens[2]); // offset as function of AD channel + } catch (std::invalid_argument &) { + message.str(""); + message << "unable to convert GAIN \"" << tokens[1] << "\" and/or OFFSET \"" << tokens[2] << + "\" to integer"; + this->camera.log_error(function, message.str()); + return ERROR; + } catch (std::out_of_range &) { + message.str(""); + message << "GAIN " << tokens[1] << ", OFFSET " << tokens[2] << " outside integer range"; + this->camera.log_error(function, message.str()); + return ERROR; + } + // Now assign the gain,offsets to their appropriate position in the vectors + // + try { + this->gain.at(adnum) = gain_try; // gain as function of AD channel + this->offset.at(adnum) = offset_try; // offset as function of AD channel - // parse the parameter value - // and convert it to a string for the shutter command - // - std::string shuttenstr; - if ( lshutten == "1" ) shuttenstr = "enable"; - else if ( lshutten == "0" ) shuttenstr = "disable"; - else { - message.str(""); message << "ERROR: unrecognized shutter enable parameter value " << lshutten << ": expected {0,1}"; + // Add the gain/offset as system header keywords. + // + std::stringstream keystr; + keystr.str(""); + keystr << "GAIN" << std::setfill('0') << std::setw(2) << adnum << "=" << this->gain.at(adnum) << + "// gain for AD chan " << adnum; + this->systemkeys.addkey(keystr.str()); + keystr.str(""); + keystr << "OFFSET" << std::setfill('0') << std::setw(2) << adnum << "=" << this->offset.at(adnum) << + "// offset for AD chan " << adnum; + this->systemkeys.addkey(keystr.str()); + } catch (std::out_of_range &) { + message.str(""); + message << "AD# " << adnum << " outside range {0:" << (this->gain.size() & this->offset.size()) << + "}"; + this->camera.log_error(function, message.str()); + if (this->gain.empty() || this->offset.empty()) { + this->camera.log_error( + function, "gain/offset vectors are empty: no ADC or ADM board installed?"); + } + return ERROR; + } +#ifdef LOGLEVEL_DEBUG + message.str(""); message << "[DEBUG] tap #" << tapn << " ad#" << adnum; logwrite( function, message.str() ); - return ERROR; - } +#endif + } // end if (tokens.size() == 3) + } // end for (int tapn=0; tapntaplines; tapn++) - // Tell the server - // - std::string dontcare; - if ( this->shutter( shuttenstr, dontcare ) != NO_ERROR ) { logwrite( function, "ERROR: setting shutter enable parameter" ); return ERROR; } + return error; } + /**************** Archon::Interface::load_mode_settings *********************/ + + + /**************** Archon::Interface::get_frame_status ***********************/ /** - * read back some TAPLINE information + * @fn get_frame_status + * @brief get the current frame buffer status + * @param none + * @return + * + * Sends the "FRAME" command to Archon, reads back the reply, then parses the + * reply and stores parameters into the frame structure (of type frame_data_t). + * */ - if (error==NO_ERROR) error = get_configmap_value("TAPLINES", this->taplines); // total number of taps + long Interface::get_frame_status() { + std::string function = "Archon::Interface::get_frame_status"; + std::string reply; + std::stringstream message; + int newestframe, newestbuf; + long error = NO_ERROR; - std::vector tokens; - std::stringstream tap; - std::string adchan; + // send FRAME command to get frame buffer status + // + if ((error = this->archon_cmd(FRAME, reply))) { + if (error == ERROR) logwrite(function, "ERROR: sending FRAME command"); // don't log here if BUSY + return error; + } - // Remove all GAIN* and OFFSET* keywords from the systemkeys database - // because the new mode could have different channels (so simply - // over-writing existing keys might not be sufficient). - // - // The new GAIN* and OFFSET* system keys will be added in the next loop. - // - this->systemkeys.EraseKeys( "GAIN" ); - this->systemkeys.EraseKeys( "OFFSET" ); + // First Tokenize breaks the single, continuous reply string into vector of individual strings, + // from "TIMER=xxxx RBUF=xxxx " to: + // tokens[0] : TIMER=xxxx + // tokens[1] : RBUF=xxxx + // tokens[2] : etc. + std::vector tokens; + Tokenize(reply, tokens, " "); + + // loop over all tokens in reply + for (const auto &token: tokens) { + // Second Tokenize separates the parameter from the value + // subtokens[0] = keyword + // subtokens[1] = value + std::vector subtokens; + subtokens.clear(); + Tokenize(token, subtokens, "="); + + // Each entry in the FRAME message must have two tokens, one for each side of the "=" equal sign + // (in other words there must be two subtokens per token) + if (subtokens.size() != 2) { + message.str(""); + message << "expected 2 but received invalid number of tokens (" << subtokens.size() << + ") in FRAME message:"; + for (const auto &subtoken: subtokens) message << " " << subtoken; + this->camera.log_error(function, message.str()); + return ERROR; // We could continue; but if one is bad then we could miss seeing a larger problem + } - // Loop through every tap to get the offset for each - // - for (int tapn=0; tapntaplines; tapn++) { - tap.str(""); - tap << "TAPLINE" << tapn; // used to find the tapline in the configmap + int bufnum = 0; + int value = 0; + uint64_t lvalue = 0; + + // Parse subtokens[1] (value) based on subtoken[0] (keyword) + + // timer is a string + if (subtokens[0] == "TIMER") { + this->frame.timer = subtokens[1]; + } else { + // everything else is going to be a number + // use "try...catch" to catch exceptions converting strings to numbers + try { + // for all "BUFnSOMETHING=VALUE" we want the bufnum "n" + if (subtokens[0].compare(0, 3, "BUF") == 0) { + // extract the "n" here which is 1-based (1,2,3) + bufnum = std::stoi(subtokens[0].substr(3, 1)); + } - // The value of TAPLINEn = ADxx,gain,offset -- - // tokenize by comma to separate out each parameter... - // - Tokenize(this->configmap[tap.str().c_str()].value, tokens, ","); + // for "BUFnBASE=xxx" the value is uint64 + if (subtokens[0].substr(4) == "BASE") { + lvalue = std::stol(subtokens[1]); // this value will get assigned to the corresponding parameter - // If all three tokens present (A?xx,gain,offset) then parse it, - // otherwise it's an unused tap, and we can skip it. - // - if (tokens.size() == 3) { // defined tap has three tokens - adchan = tokens[0]; // AD channel is the first (0th) token - char chars[] = "ADMLR"; // characters to remove in order to get just the AD channel number + // for any "xxxTIMESTAMPxxx" the value is uint64 + } else if (subtokens[0].find("TIMESTAMP") != std::string::npos) { + lvalue = std::stol(subtokens[1]); // this value will get assigned to the corresponding parameter - // Before removing these characters, set the max allowed AD number based on the tapline syntax. - // "ADxx,gain,offset" is for ADC module - // "AMxx,gain,offset" is for ADM module - // - int admax = 0; - if ( adchan.find("AD") != std::string::npos ) admax = MAXADCCHANS; - else if ( adchan.find("AM") != std::string::npos ) admax = MAXADMCHANS; - else { - message.str(""); message << "bad tapline syntax. Expected ADn or AMn but got " << adchan; - this->camera.log_error( function, message.str() ); - return ERROR; - } + // everything else is an int + } else { + value = std::stoi(subtokens[1]); // this value will get assigned to the corresponding parameter + } + } catch (std::invalid_argument &) { + message.str(""); + message << "unable to convert buffer: " << subtokens[0] << " or value: " << subtokens[1] << + " from FRAME message to integer. Expected BUFnSOMETHING=nnnn"; + this->camera.log_error(function, message.str()); + return ERROR; + } catch (std::out_of_range &) { + message.str(""); + message << "buffer: " << subtokens[0] << " or value: " << subtokens[1] << + " from FRAME message outside integer range. Expected BUFnSOMETHING=nnnn"; + this->camera.log_error(function, message.str()); + return ERROR; + } + } // end else everything else is a number - // remove AD, AM, L, R from the adchan string, to get just the AD channel number - // - for (unsigned int j = 0; j < strlen(chars); j++) { - adchan.erase(std::remove(adchan.begin(), adchan.end(), chars[j]), adchan.end()); + // subtokens value has been parsed based on keyword + + // get currently locked buffers + if (subtokens[0] == "RBUF") this->frame.rbuf = value; // locked for reading + if (subtokens[0] == "WBUF") this->frame.wbuf = value; // locked for writing + + // The next group are BUFnSOMETHING=VALUE + // Extract the "n" which must be a number from 1 to Archon::nbufs + // After getting the buffer number we assign the corresponding value. + // + if (subtokens[0].compare(0, 3, "BUF") == 0) { + bufnum = std::stoi(subtokens[0].substr(3, 1)); + // verify buffer number (1,2, or 3) + if (bufnum < 1 || bufnum > Archon::nbufs) { + message.str(""); + message << "buffer number " << bufnum << " from FRAME message outside range {1:" << Archon::nbufs << + "}"; + this->camera.log_error(function, message.str()); + return ERROR; + } + bufnum--; // subtract 1 because it is 1-based in the message but need 0-based for the indexing + // Assign value to appropriate variable in frame structure + if (subtokens[0].substr(4) == "SAMPLE") this->frame.bufsample[bufnum] = value; + if (subtokens[0].substr(4) == "COMPLETE") this->frame.bufcomplete[bufnum] = value; + if (subtokens[0].substr(4) == "MODE") this->frame.bufmode[bufnum] = value; + if (subtokens[0].substr(4) == "BASE") this->frame.bufbase[bufnum] = lvalue; + if (subtokens[0].substr(4) == "FRAME") this->frame.bufframen[bufnum] = value; + if (subtokens[0].substr(4) == "WIDTH") this->frame.bufwidth[bufnum] = value; + if (subtokens[0].substr(4) == "HEIGHT") this->frame.bufheight[bufnum] = value; + if (subtokens[0].substr(4) == "PIXELS") this->frame.bufpixels[bufnum] = value; + if (subtokens[0].substr(4) == "LINES") this->frame.buflines[bufnum] = value; + if (subtokens[0].substr(4) == "RAWBLOCKS") this->frame.bufrawblocks[bufnum] = value; + if (subtokens[0].substr(4) == "RAWLINES") this->frame.bufrawlines[bufnum] = value; + if (subtokens[0].substr(4) == "RAWOFFSET") this->frame.bufrawoffset[bufnum] = value; + if (subtokens[0].substr(4) == "TIMESTAMP") this->frame.buftimestamp[bufnum] = lvalue; + if (subtokens[0].substr(4) == "RETIMESTAMP") this->frame.bufretimestamp[bufnum] = lvalue; + if (subtokens[0].substr(4) == "FETIMESTAMP") this->frame.buffetimestamp[bufnum] = lvalue; + } // end if token is BUFnSOMETHiNG + } // end loop over all returned tokens from FRAME reply + + // FRAME reply has now been parsed into frame structure variables + // now update frame.frame, frame.index, and frame.next_index + + // Current frame.index + newestbuf = this->frame.index; + + // Is frame.index a legal index? + if (this->frame.index < (int) this->frame.bufframen.size()) { + // get the current frame number + newestframe = this->frame.bufframen[this->frame.index]; + } else { + // frame.index value is illegal + message.str(""); + message << "newest buf " << this->frame.index << " from FRAME message exceeds number of buffers " << this-> + frame.bufframen.size(); + this->camera.log_error(function, message.str()); + return ERROR; } - // AD# in TAPLINE is 1-based (numbered 1,2,3,...) - // but convert here to 0-based (numbered 0,1,2,...) and check value before using - // - int adnum; - try { - adnum = std::stoi(adchan) - 1; + int num_zero = 0; // count zero buffers - } catch (std::invalid_argument &) { - message.str(""); message << "unable to convert AD number \'" << adchan << "\' to integer"; - this->camera.log_error( function, message.str() ); - return ERROR; + // loop through the buffers + for (int bc = 0; bc < Archon::nbufs; bc++) { + // count number of zero-valued buffers + if (this->frame.bufframen[bc] == 0) num_zero++; - } catch (std::out_of_range &) { - this->camera.log_error( function, "AD number out of integer range" ); - return ERROR; + // Is the latest frame greater than the current one and is it complete? + if ((this->frame.bufframen[bc] > newestframe) && this->frame.bufcomplete[bc]) { + // if so, update frame and buffer numbers + newestframe = this->frame.bufframen[bc]; + newestbuf = bc; + } } - if ( (adnum < 0) || (adnum > admax) ) { - message.str(""); message << "ADC channel " << adnum << " outside range {0:" << admax << "}"; - this->camera.log_error( function, message.str() ); - return ERROR; + // In start-up case, all frame buffers are zero + if (num_zero == Archon::nbufs) { + // initialize frame number and buffer index both to zero + newestframe = 0; + newestbuf = 0; } - // Now that adnum is OK, convert next two tokens to gain, offset - // - int gain_try=0, offset_try=0; - try { - gain_try = std::stoi( tokens[1] ); // gain as function of AD channel - offset_try = std::stoi( tokens[2] ); // offset as function of AD channel - } catch (std::invalid_argument &) { - message.str(""); message << "unable to convert GAIN \"" << tokens[1] << "\" and/or OFFSET \"" << tokens[2] << "\" to integer"; - this->camera.log_error( function, message.str() ); - return ERROR; + // save the newest frame number and index of newest buffer. + this->frame.frame = newestframe; + this->frame.index = newestbuf; - } catch (std::out_of_range &) { - message.str(""); message << "GAIN " << tokens[1] << ", OFFSET " << tokens[2] << " outside integer range"; - this->camera.log_error( function, message.str() ); - return ERROR; + // startup condition for next_frame: + // frame number is 1 and corresponding buffer is not complete + if ((this->frame.bufframen[this->frame.index]) == 1 && this->frame.bufcomplete[this->frame.index] == 0) { + this->frame.next_index = 0; + } else { + // Index of next frame is this->frame.index+1 + this->frame.next_index = this->frame.index + 1; + + // frame.next_index wraps to 0 when it reaches the maximum number of active buffers. + if (this->frame.next_index >= this->camera_info.activebufs) { + this->frame.next_index = 0; + } } - // Now assign the gain,offsets to their appropriate position in the vectors - // - try { - this->gain.at( adnum ) = gain_try; // gain as function of AD channel - this->offset.at( adnum ) = offset_try; // offset as function of AD channel - - // Add the gain/offset as system header keywords. - // - std::stringstream keystr; - keystr.str(""); keystr << "GAIN" << std::setfill('0') << std::setw(2) << adnum << "=" << this->gain.at( adnum ) << "// gain for AD chan " << adnum; - this->systemkeys.addkey( keystr.str() ); - keystr.str(""); keystr << "OFFSET" << std::setfill('0') << std::setw(2) << adnum << "=" << this->offset.at( adnum ) << "// offset for AD chan " << adnum; - this->systemkeys.addkey( keystr.str() ); - - } catch ( std::out_of_range & ) { - message.str(""); message << "AD# " << adnum << " outside range {0:" << (this->gain.size() & this->offset.size()) << "}"; - this->camera.log_error( function, message.str() ); - if ( this->gain.empty() || this->offset.empty() ) { - this->camera.log_error( function, "gain/offset vectors are empty: no ADC or ADM board installed?" ); - } - return ERROR; - } - #ifdef LOGLEVEL_DEBUG - message.str(""); message << "[DEBUG] tap #" << tapn << " ad#" << adnum; - logwrite( function, message.str() ); - #endif - } // end if (tokens.size() == 3) - } // end for (int tapn=0; tapntaplines; tapn++) - - return error; - } - /**************** Archon::Interface::load_mode_settings *********************/ - - - /**************** Archon::Interface::get_frame_status ***********************/ - /** - * @fn get_frame_status - * @brief get the current frame buffer status - * @param none - * @return - * - * Sends the "FRAME" command to Archon, reads back the reply, then parses the - * reply and stores parameters into the frame structure (of type frame_data_t). - * - */ - long Interface::get_frame_status() { - std::string function = "Archon::Interface::get_frame_status"; - std::string reply; - std::stringstream message; - int newestframe, newestbuf; - long error=NO_ERROR; - - // send FRAME command to get frame buffer status - // - if ( (error = this->archon_cmd(FRAME, reply)) ) { - if ( error == ERROR ) logwrite( function, "ERROR: sending FRAME command" ); // don't log here if BUSY - return error; + + return error; } - // First Tokenize breaks the single, continuous reply string into vector of individual strings, - // from "TIMER=xxxx RBUF=xxxx " to: - // tokens[0] : TIMER=xxxx - // tokens[1] : RBUF=xxxx - // tokens[2] : etc. - std::vector tokens; - Tokenize(reply, tokens, " "); - - // loop over all tokens in reply - for (const auto & token : tokens) { - - // Second Tokenize separates the parameter from the value - // subtokens[0] = keyword - // subtokens[1] = value - std::vector subtokens; - subtokens.clear(); - Tokenize(token, subtokens, "="); - - // Each entry in the FRAME message must have two tokens, one for each side of the "=" equal sign - // (in other words there must be two subtokens per token) - if (subtokens.size() != 2) { - message.str(""); - message << "expected 2 but received invalid number of tokens (" << subtokens.size() << ") in FRAME message:"; - for (const auto & subtoken : subtokens) message << " " << subtoken; - this->camera.log_error( function, message.str() ); - return ERROR; // We could continue; but if one is bad then we could miss seeing a larger problem - } + /**************** Archon::Interface::get_frame_status ***********************/ - int bufnum=0; - int value=0; - uint64_t lvalue=0; - // Parse subtokens[1] (value) based on subtoken[0] (keyword) + /**************** Archon::Interface::print_frame_status *********************/ + /** + * @fn print_frame_status + * @brief print the Archon frame buffer status + * @param none + * @return none + * + * Writes the Archon frame buffer status to the log file, + * I.E. information in this->frame structure, obtained from + * the "FRAME" command. See Archon::Interface::get_frame_status() + * + */ + void Interface::print_frame_status() { + std::string function = "Archon::Interface::print_frame_status"; + std::stringstream message; + int bufn; + char statestr[Archon::nbufs][4]; - // timer is a string - if (subtokens[0]=="TIMER") { - this->frame.timer = subtokens[1]; + // write as log message + // + message.str(""); + message << " buf base rawoff frame ready lines rawlines rblks width height state"; + logwrite(function, message.str()); + message.str(""); + message << " --- ---------- ---------- ----- ----- ----- -------- ----- ----- ------ -----"; + logwrite(function, message.str()); + message.str(""); + for (bufn = 0; bufn < Archon::nbufs; bufn++) { + memset(statestr[bufn], '\0', 4); + if ((this->frame.rbuf - 1) == bufn) strcat(statestr[bufn], "R"); + if ((this->frame.wbuf - 1) == bufn) strcat(statestr[bufn], "W"); + if (this->frame.bufcomplete[bufn]) strcat(statestr[bufn], "C"); + } + for (bufn = 0; bufn < Archon::nbufs; bufn++) { + message << std::setw(3) << (bufn == this->frame.index ? "-->" : "") << " "; // buf + message << std::setw(3) << bufn + 1 << " "; + message << "0x" << std::uppercase << std::setfill('0') << std::setw(8) << std::hex + << this->frame.bufbase[bufn] << " "; // base + message << "0x" << std::uppercase << std::setfill('0') << std::setw(8) << std::hex + << this->frame.bufrawoffset[bufn] << " "; // rawoff + message << std::setfill(' ') << std::setw(5) << std::dec << this->frame.bufframen[bufn] << " "; // frame + message << std::setw(5) << this->frame.bufcomplete[bufn] << " "; // ready + message << std::setw(5) << this->frame.buflines[bufn] << " "; // lines + message << std::setw(8) << this->frame.bufrawlines[bufn] << " "; // rawlines + message << std::setw(5) << this->frame.bufrawblocks[bufn] << " "; // rblks + message << std::setw(5) << this->frame.bufwidth[bufn] << " "; // width + message << std::setw(6) << this->frame.bufheight[bufn] << " "; // height + message << std::setw(5) << statestr[bufn] << " "; // state + message << std::setw(5) << this->frame.bufpixels[bufn]; + logwrite(function, message.str()); + message.str(""); + } + } - } else { - // everything else is going to be a number - // use "try...catch" to catch exceptions converting strings to numbers - try { - // for all "BUFnSOMETHING=VALUE" we want the bufnum "n" - if (subtokens[0].compare(0, 3, "BUF")==0) { - // extract the "n" here which is 1-based (1,2,3) - bufnum = std::stoi( subtokens[0].substr(3, 1) ); - } + /**************** Archon::Interface::print_frame_status *********************/ - // for "BUFnBASE=xxx" the value is uint64 - if (subtokens[0].substr(4)=="BASE" ) { - lvalue = std::stol( subtokens[1] ); // this value will get assigned to the corresponding parameter - // for any "xxxTIMESTAMPxxx" the value is uint64 - } else if (subtokens[0].find("TIMESTAMP") != std::string::npos) { - lvalue = std::stol( subtokens[1] ); // this value will get assigned to the corresponding parameter + /**************** Archon::Interface::lock_buffer ****************************/ + /** + * @fn lock_buffer + * @brief lock Archon frame buffer + * @param int frame buffer to lock + * @return + * + */ + long Interface::lock_buffer(int buffer) { + std::string function = "Archon::Interface::lock_buffer"; + std::stringstream message; + std::stringstream sscmd; + + sscmd.str(""); + sscmd << "LOCK" << buffer; + if (this->archon_cmd(sscmd.str())) { + message.str(""); + message << "ERROR: sending Archon command to lock frame buffer " << buffer; + logwrite(function, message.str()); + return ERROR; + } + return (NO_ERROR); + } - // everything else is an int - } else { - value = std::stoi( subtokens[1] ); // this value will get assigned to the corresponding parameter - } + /**************** Archon::Interface::lock_buffer ****************************/ - } catch (std::invalid_argument &) { - message.str(""); message << "unable to convert buffer: " << subtokens[0] << " or value: " << subtokens[1] << " from FRAME message to integer. Expected BUFnSOMETHING=nnnn"; - this->camera.log_error( function, message.str() ); - return ERROR; - } catch (std::out_of_range &) { - message.str(""); message << "buffer: " << subtokens[0] << " or value: " << subtokens[1] << " from FRAME message outside integer range. Expected BUFnSOMETHING=nnnn"; - this->camera.log_error( function, message.str() ); - return ERROR; - } - } // end else everything else is a number - - // subtokens value has been parsed based on keyword - - // get currently locked buffers - if (subtokens[0]=="RBUF") this->frame.rbuf = value; // locked for reading - if (subtokens[0]=="WBUF") this->frame.wbuf = value; // locked for writing - - // The next group are BUFnSOMETHING=VALUE - // Extract the "n" which must be a number from 1 to Archon::nbufs - // After getting the buffer number we assign the corresponding value. - // - if (subtokens[0].compare(0, 3, "BUF")==0) { - bufnum = std::stoi( subtokens[0].substr(3, 1) ); - // verify buffer number (1,2, or 3) - if (bufnum < 1 || bufnum > Archon::nbufs) { - message.str(""); message << "buffer number " << bufnum << " from FRAME message outside range {1:" << Archon::nbufs << "}"; - this->camera.log_error( function, message.str() ); - return ERROR; - } - bufnum--; // subtract 1 because it is 1-based in the message but need 0-based for the indexing - // Assign value to appropriate variable in frame structure - if (subtokens[0].substr(4) == "SAMPLE") this->frame.bufsample[bufnum] = value; - if (subtokens[0].substr(4) == "COMPLETE") this->frame.bufcomplete[bufnum] = value; - if (subtokens[0].substr(4) == "MODE") this->frame.bufmode[bufnum] = value; - if (subtokens[0].substr(4) == "BASE") this->frame.bufbase[bufnum] = lvalue; - if (subtokens[0].substr(4) == "FRAME") this->frame.bufframen[bufnum] = value; - if (subtokens[0].substr(4) == "WIDTH") this->frame.bufwidth[bufnum] = value; - if (subtokens[0].substr(4) == "HEIGHT") this->frame.bufheight[bufnum] = value; - if (subtokens[0].substr(4) == "PIXELS") this->frame.bufpixels[bufnum] = value; - if (subtokens[0].substr(4) == "LINES") this->frame.buflines[bufnum] = value; - if (subtokens[0].substr(4) == "RAWBLOCKS") this->frame.bufrawblocks[bufnum] = value; - if (subtokens[0].substr(4) == "RAWLINES") this->frame.bufrawlines[bufnum] = value; - if (subtokens[0].substr(4) == "RAWOFFSET") this->frame.bufrawoffset[bufnum] = value; - if (subtokens[0].substr(4) == "TIMESTAMP") this->frame.buftimestamp[bufnum] = lvalue; - if (subtokens[0].substr(4) == "RETIMESTAMP") this->frame.bufretimestamp[bufnum] = lvalue; - if (subtokens[0].substr(4) == "FETIMESTAMP") this->frame.buffetimestamp[bufnum] = lvalue; - } // end if token is BUFnSOMETHiNG - - } // end loop over all returned tokens from FRAME reply - - // FRAME reply has now been parsed into frame structure variables - // now update frame.frame, frame.index, and frame.next_index - - // Current frame.index - newestbuf = this->frame.index; - - // Is frame.index a legal index? - if (this->frame.index < (int)this->frame.bufframen.size()) { - // get the current frame number - newestframe = this->frame.bufframen[this->frame.index]; - - } else { - // frame.index value is illegal - message.str(""); message << "newest buf " << this->frame.index << " from FRAME message exceeds number of buffers " << this->frame.bufframen.size(); - this->camera.log_error( function, message.str() ); - return ERROR; - } + /**************** Archon::Interface::get_timer ******************************/ + /** + * @fn get_timer + * @brief read the 64 bit interal timer from the Archon controller + * @param *timer pointer to type unsigned long int + * @return errno on error, 0 if okay. + * + * Sends the "TIMER" command to Archon, reads back the reply, and stores the + * value as (unsigned long int) in the argument variable pointed to by *timer. + * + * This is an internal 64 bit timer/counter. One tick of the counter is 10 ns. + * + */ + long Interface::get_timer(unsigned long int *timer) { + std::string function = "Archon::Interface::get_timer"; + std::string reply; + std::stringstream message, timer_ss; + std::vector tokens; + long error; - int num_zero = 0; // count zero buffers + // send TIMER command to get frame buffer status + // + if ((error = this->archon_cmd(TIMER, reply)) != NO_ERROR) { + return error; + } - // loop through the buffers - for (int bc=0; bccamera.log_error(function, message.str()); + return ERROR; + } - // Is the latest frame greater than the current one and is it complete? - if ( (this->frame.bufframen[bc] > newestframe) && this->frame.bufcomplete[bc] ) { - // if so, update frame and buffer numbers - newestframe = this->frame.bufframen[bc]; - newestbuf = bc; - } - } + // Second token must be a hexidecimal string + // + std::string timer_str = tokens[1]; + try { + timer_str.erase(timer_str.find('\n'), 1); + } catch (...) { + } // remove newline + if (!std::all_of(timer_str.begin(), timer_str.end(), ::isxdigit)) { + message.str(""); + message << "unrecognized timer value: " << timer_str << ". Expected hexadecimal string"; + this->camera.log_error(function, message.str()); + return ERROR; + } - // In start-up case, all frame buffers are zero - if (num_zero == Archon::nbufs) { - // initialize frame number and buffer index both to zero - newestframe = 0; - newestbuf = 0; + // convert from hex string to integer and save return value + // + timer_ss << std::hex << tokens[1]; + timer_ss >> *timer; + return NO_ERROR; } - // save the newest frame number and index of newest buffer. - this->frame.frame = newestframe; - this->frame.index = newestbuf; + /**************** Archon::Interface::get_timer ******************************/ - // startup condition for next_frame: - // frame number is 1 and corresponding buffer is not complete - if ( ( this->frame.bufframen[ this->frame.index ] ) == 1 && this->frame.bufcomplete[ this->frame.index ] == 0 ) { - this->frame.next_index = 0; - } else { - // Index of next frame is this->frame.index+1 - this->frame.next_index = this->frame.index + 1; + /**************** Archon::Interface::fetch **********************************/ + /** + * @fn fetch + * @brief fetch Archon frame buffer + * @param int frame buffer to lock + * @return + * + */ + long Interface::fetch(uint64_t bufaddr, uint32_t bufblocks) { + std::string function = "Archon::Interface::fetch"; + std::stringstream message; + uint32_t maxblocks = (uint32_t) (1.5E9 / this->camera_info.activebufs / 1024); + uint64_t maxoffset = this->frame.bufbase[this->frame.index]; + uint64_t maxaddr = maxoffset + maxblocks; - // frame.next_index wraps to 0 when it reaches the maximum number of active buffers. - if (this->frame.next_index >= this->camera_info.activebufs) { - this->frame.next_index = 0; + if (bufaddr > maxaddr) { + message.str(""); + message << "fetch Archon buffer requested address 0x" << std::hex << bufaddr << " exceeds 0x" << maxaddr; + this->camera.log_error(function, message.str()); + return ERROR; + } + if (bufblocks > maxblocks) { + message.str(""); + message << "fetch Archon buffer requested blocks 0x" << std::hex << bufblocks << " exceeds 0x" << maxblocks; + this->camera.log_error(function, message.str()); + return ERROR; } - } - return error; - } - /**************** Archon::Interface::get_frame_status ***********************/ - - - /**************** Archon::Interface::print_frame_status *********************/ - /** - * @fn print_frame_status - * @brief print the Archon frame buffer status - * @param none - * @return none - * - * Writes the Archon frame buffer status to the log file, - * I.E. information in this->frame structure, obtained from - * the "FRAME" command. See Archon::Interface::get_frame_status() - * - */ - void Interface::print_frame_status() { - std::string function = "Archon::Interface::print_frame_status"; - std::stringstream message; - int bufn; - char statestr[Archon::nbufs][4]; - - // write as log message - // - message.str(""); message << " buf base rawoff frame ready lines rawlines rblks width height state"; - logwrite(function, message.str()); - message.str(""); message << " --- ---------- ---------- ----- ----- ----- -------- ----- ----- ------ -----"; - logwrite(function, message.str()); - message.str(""); - for (bufn=0; bufn < Archon::nbufs; bufn++) { - memset(statestr[bufn], '\0', 4); - if ( (this->frame.rbuf-1) == bufn) strcat(statestr[bufn], "R"); - if ( (this->frame.wbuf-1) == bufn) strcat(statestr[bufn], "W"); - if ( this->frame.bufcomplete[bufn] ) strcat(statestr[bufn], "C"); - } - for (bufn=0; bufn < Archon::nbufs; bufn++) { - message << std::setw(3) << (bufn==this->frame.index ? "-->" : "") << " "; // buf - message << std::setw(3) << bufn+1 << " "; - message << "0x" << std::uppercase << std::setfill('0') << std::setw(8) << std::hex - << this->frame.bufbase[bufn] << " "; // base - message << "0x" << std::uppercase << std::setfill('0') << std::setw(8) << std::hex - << this->frame.bufrawoffset[bufn] << " "; // rawoff - message << std::setfill(' ') << std::setw(5) << std::dec << this->frame.bufframen[bufn] << " "; // frame - message << std::setw(5) << this->frame.bufcomplete[bufn] << " "; // ready - message << std::setw(5) << this->frame.buflines[bufn] << " "; // lines - message << std::setw(8) << this->frame.bufrawlines[bufn] << " "; // rawlines - message << std::setw(5) << this->frame.bufrawblocks[bufn] << " "; // rblks - message << std::setw(5) << this->frame.bufwidth[bufn] << " "; // width - message << std::setw(6) << this->frame.bufheight[bufn] << " "; // height - message << std::setw(5) << statestr[bufn] << " "; // state - message << std::setw(5) << this->frame.bufpixels[bufn]; - logwrite(function, message.str()); - message.str(""); - } - } - /**************** Archon::Interface::print_frame_status *********************/ - - - /**************** Archon::Interface::lock_buffer ****************************/ - /** - * @fn lock_buffer - * @brief lock Archon frame buffer - * @param int frame buffer to lock - * @return - * - */ - long Interface::lock_buffer(int buffer) { - std::string function = "Archon::Interface::lock_buffer"; - std::stringstream message; - std::stringstream sscmd; - - sscmd.str(""); - sscmd << "LOCK" << buffer; - if ( this->archon_cmd(sscmd.str()) ) { - message.str(""); message << "ERROR: sending Archon command to lock frame buffer " << buffer; - logwrite( function, message.str() ); - return ERROR; - } - return (NO_ERROR); - } - /**************** Archon::Interface::lock_buffer ****************************/ - - - /**************** Archon::Interface::get_timer ******************************/ - /** - * @fn get_timer - * @brief read the 64 bit interal timer from the Archon controller - * @param *timer pointer to type unsigned long int - * @return errno on error, 0 if okay. - * - * Sends the "TIMER" command to Archon, reads back the reply, and stores the - * value as (unsigned long int) in the argument variable pointed to by *timer. - * - * This is an internal 64 bit timer/counter. One tick of the counter is 10 ns. - * - */ - long Interface::get_timer(unsigned long int *timer) { - std::string function = "Archon::Interface::get_timer"; - std::string reply; - std::stringstream message, timer_ss; - std::vector tokens; - long error; - - // send TIMER command to get frame buffer status - // - if ( (error = this->archon_cmd(TIMER, reply)) != NO_ERROR ) { - return error; - } + std::stringstream sscmd; + sscmd << "FETCH" + << std::setfill('0') << std::setw(8) << std::hex + << bufaddr + << std::setfill('0') << std::setw(8) << std::hex + << bufblocks; + std::string scmd = sscmd.str(); + try { + std::transform(scmd.begin(), scmd.end(), scmd.begin(), ::toupper); // make uppercase + } catch (...) { + message.str(""); + message << "converting command: " << sscmd.str() << " to uppercase"; + this->camera.log_error(function, message.str()); + return ERROR; + } - Tokenize(reply, tokens, "="); // Tokenize the reply + if (this->archon_cmd(scmd) == ERROR) { + logwrite(function, "ERROR: sending FETCH command. Aborting read."); + this->archon_cmd(UNLOCK); // unlock all buffers + return ERROR; + } - // Reponse should be "TIMER=xxxx\n" so there needs - // to be two tokens - // - if (tokens.size() != 2) { - message.str(""); message << "unrecognized timer response: " << reply << ". Expected TIMER=xxxx"; - this->camera.log_error( function, message.str() ); - return ERROR; + message.str(""); + message << "reading " << (this->camera_info.frame_type == Camera::FRAME_RAW ? "raw" : "image") << " with " << + scmd; + logwrite(function, message.str()); + return NO_ERROR; } - // Second token must be a hexidecimal string - // - std::string timer_str = tokens[1]; - try { - timer_str.erase(timer_str.find('\n'), 1); - } catch(...) { } // remove newline - if (!std::all_of(timer_str.begin(), timer_str.end(), ::isxdigit)) { - message.str(""); message << "unrecognized timer value: " << timer_str << ". Expected hexadecimal string"; - this->camera.log_error( function, message.str() ); - return ERROR; - } + /**************** Archon::Interface::fetch **********************************/ - // convert from hex string to integer and save return value - // - timer_ss << std::hex << tokens[1]; - timer_ss >> *timer; - return NO_ERROR; - } - /**************** Archon::Interface::get_timer ******************************/ - - - /**************** Archon::Interface::fetch **********************************/ - /** - * @fn fetch - * @brief fetch Archon frame buffer - * @param int frame buffer to lock - * @return - * - */ - long Interface::fetch(uint64_t bufaddr, uint32_t bufblocks) { - std::string function = "Archon::Interface::fetch"; - std::stringstream message; - uint32_t maxblocks = (uint32_t)(1.5E9 / this->camera_info.activebufs / 1024 ); - uint64_t maxoffset = this->frame.bufbase[this->frame.index]; - uint64_t maxaddr = maxoffset + maxblocks; - - if ( bufaddr > maxaddr ) { - message.str(""); message << "fetch Archon buffer requested address 0x" << std::hex << bufaddr << " exceeds 0x" << maxaddr; - this->camera.log_error( function, message.str() ); - return ERROR; - } - if ( bufblocks > maxblocks ) { - message.str(""); message << "fetch Archon buffer requested blocks 0x" << std::hex << bufblocks << " exceeds 0x" << maxblocks; - this->camera.log_error( function, message.str() ); - return ERROR; - } - std::stringstream sscmd; - sscmd << "FETCH" - << std::setfill('0') << std::setw(8) << std::hex - << bufaddr - << std::setfill('0') << std::setw(8) << std::hex - << bufblocks; - std::string scmd = sscmd.str(); - try { - std::transform( scmd.begin(), scmd.end(), scmd.begin(), ::toupper ); // make uppercase - - } catch (...) { - message.str(""); message << "converting command: " << sscmd.str() << " to uppercase"; - this->camera.log_error( function, message.str() ); - return ERROR; - } + /**************** Archon::Interface::read_frame *****************************/ + /** + * @fn read_frame + * @brief read latest Archon frame buffer + * @param none + * @return ERROR or NO_ERROR + * + * This is function is overloaded. + * + * This version, with no parameter, is the one that is called by the server. + * The decision is made here if the frame to be read is a RAW or an IMAGE + * frame based on this->camera_info.current_observing_mode, then the + * overloaded version of read_frame(frame_type) is called with the appropriate + * frame type of IMAGE or RAW. + * + * This function WILL call write_frame(...) to write data after reading it. + * + */ + long Interface::read_frame() { + std::string function = "Archon::Interface::read_frame"; + std::stringstream message; + long error = NO_ERROR; - if (this->archon_cmd(scmd) == ERROR) { - logwrite( function, "ERROR: sending FETCH command. Aborting read." ); - this->archon_cmd(UNLOCK); // unlock all buffers - return ERROR; - } + if (!this->modeselected) { + this->camera.log_error(function, "no mode selected"); + return ERROR; + } - message.str(""); message << "reading " << (this->camera_info.frame_type==Camera::FRAME_RAW?"raw":"image") << " with " << scmd; - logwrite(function, message.str()); - return NO_ERROR; - } - /**************** Archon::Interface::fetch **********************************/ - - - /**************** Archon::Interface::read_frame *****************************/ - /** - * @fn read_frame - * @brief read latest Archon frame buffer - * @param none - * @return ERROR or NO_ERROR - * - * This is function is overloaded. - * - * This version, with no parameter, is the one that is called by the server. - * The decision is made here if the frame to be read is a RAW or an IMAGE - * frame based on this->camera_info.current_observing_mode, then the - * overloaded version of read_frame(frame_type) is called with the appropriate - * frame type of IMAGE or RAW. - * - * This function WILL call write_frame(...) to write data after reading it. - * - */ - long Interface::read_frame() { - std::string function = "Archon::Interface::read_frame"; - std::stringstream message; - long error = NO_ERROR; - - if ( ! this->modeselected ) { - this->camera.log_error( function, "no mode selected" ); - return ERROR; - } + int rawenable = this->modemap[this->camera_info.current_observing_mode].rawenable; - int rawenable = this->modemap[this->camera_info.current_observing_mode].rawenable; + if (rawenable == -1) { + this->camera.log_error(function, "RAWENABLE is undefined"); + return ERROR; + } - if (rawenable == -1) { - this->camera.log_error( function, "RAWENABLE is undefined" ); - return ERROR; - } + // RAW-only + // + if (this->camera_info.current_observing_mode == "RAW") { + // "RAW" is the only reserved mode name - // RAW-only - // - if (this->camera_info.current_observing_mode == "RAW") { // "RAW" is the only reserved mode name - - // the RAWENABLE parameter must be set in the ACF file, in order to read RAW data - // - if (rawenable==0) { - this->camera.log_error( function, "observing mode is RAW but RAWENABLE=0 -- change mode or set RAWENABLE?" ); - return ERROR; - - } else { - error = this->read_frame(Camera::FRAME_RAW); // read raw frame - if ( error != NO_ERROR ) { logwrite( function, "ERROR: reading raw frame" ); return error; } - error = this->write_frame(); // write raw frame - if ( error != NO_ERROR ) { logwrite( function, "ERROR: writing raw frame" ); return error; } - } + // the RAWENABLE parameter must be set in the ACF file, in order to read RAW data + // + if (rawenable == 0) { + this->camera.log_error( + function, "observing mode is RAW but RAWENABLE=0 -- change mode or set RAWENABLE?"); + return ERROR; + } else { + error = this->read_frame(Camera::FRAME_RAW); // read raw frame + if (error != NO_ERROR) { + logwrite(function, "ERROR: reading raw frame"); + return error; + } + error = this->write_frame(); // write raw frame + if (error != NO_ERROR) { + logwrite(function, "ERROR: writing raw frame"); + return error; + } + } + } else { + // IMAGE, or IMAGE+RAW + // datacube was already set = true in the expose function + error = this->read_frame(Camera::FRAME_IMAGE); // read image frame + if (error != NO_ERROR) { + logwrite(function, "ERROR: reading image frame"); + return error; + } + error = this->write_frame(); // write image frame + if (error != NO_ERROR) { + logwrite(function, "ERROR: writing image frame"); + return error; + } - } else { - // IMAGE, or IMAGE+RAW - // datacube was already set = true in the expose function - error = this->read_frame(Camera::FRAME_IMAGE); // read image frame - if ( error != NO_ERROR ) { logwrite( function, "ERROR: reading image frame" ); return error; } - error = this->write_frame(); // write image frame - if ( error != NO_ERROR ) { logwrite( function, "ERROR: writing image frame" ); return error; } - - // If mode is not RAW but RAWENABLE=1, then we will first read an image - // frame (just done above) and then a raw frame (below). To do that we - // must switch to raw mode then read the raw frame. Afterward, switch back - // to the original mode, for any subsequent exposures. - // - if (rawenable == 1) { - #ifdef LOGLEVEL_DEBUG + // If mode is not RAW but RAWENABLE=1, then we will first read an image + // frame (just done above) and then a raw frame (below). To do that we + // must switch to raw mode then read the raw frame. Afterward, switch back + // to the original mode, for any subsequent exposures. + // + if (rawenable == 1) { +#ifdef LOGLEVEL_DEBUG logwrite(function, "[DEBUG] rawenable is set -- IMAGE+RAW file will be saved"); logwrite(function, "[DEBUG] switching to mode=RAW"); - #endif - std::string orig_mode = this->camera_info.current_observing_mode; // save the original mode, so we can come back to it - error = this->set_camera_mode("raw"); // switch to raw mode - if ( error != NO_ERROR ) { logwrite( function, "ERROR: switching to raw mode" ); return error; } +#endif + std::string orig_mode = this->camera_info.current_observing_mode; + // save the original mode, so we can come back to it + error = this->set_camera_mode("raw"); // switch to raw mode + if (error != NO_ERROR) { + logwrite(function, "ERROR: switching to raw mode"); + return error; + } - #ifdef LOGLEVEL_DEBUG +#ifdef LOGLEVEL_DEBUG message.str(""); message << "error=" << error << "[DEBUG] calling read_frame(Camera::FRAME_RAW) if error=0"; logwrite(function, message.str()); - #endif - error = this->read_frame(Camera::FRAME_RAW); // read raw frame - if ( error != NO_ERROR ) { logwrite( function, "ERROR: reading raw frame" ); return error; } - #ifdef LOGLEVEL_DEBUG +#endif + error = this->read_frame(Camera::FRAME_RAW); // read raw frame + if (error != NO_ERROR) { + logwrite(function, "ERROR: reading raw frame"); + return error; + } +#ifdef LOGLEVEL_DEBUG message.str(""); message << "error=" << error << "[DEBUG] calling write_frame() for raw data if error=0"; logwrite(function, message.str()); - #endif - error = this->write_frame(); // write raw frame - if ( error != NO_ERROR ) { logwrite( function, "ERROR: writing raw frame" ); return error; } - #ifdef LOGLEVEL_DEBUG +#endif + error = this->write_frame(); // write raw frame + if (error != NO_ERROR) { + logwrite(function, "ERROR: writing raw frame"); + return error; + } +#ifdef LOGLEVEL_DEBUG message.str(""); message << "error=" << error << "[DEBUG] switching back to original mode if error=0"; logwrite(function, message.str()); - #endif - error = this->set_camera_mode(orig_mode); // switch back to the original mode - if ( error != NO_ERROR ) { logwrite( function, "ERROR: switching back to previous mode" ); return error; } - } +#endif + error = this->set_camera_mode(orig_mode); // switch back to the original mode + if (error != NO_ERROR) { + logwrite(function, "ERROR: switching back to previous mode"); + return error; + } + } + } + + return error; } - return error; - } - /**************** Archon::Interface::read_frame *****************************/ - - /**************** Archon::Interface::hread_frame *****************************/ - /** - * @fn hread_frame - * @brief read latest Archon frame buffer - * @param frame_type - * @return ERROR or NO_ERROR - * - * This is the read_frame function which performs the actual read of the - * selected frame type. - * - * No write takes place here! - * - */ + /**************** Archon::Interface::read_frame *****************************/ + + /**************** Archon::Interface::hread_frame *****************************/ + /** + * @fn hread_frame + * @brief read latest Archon frame buffer + * @param frame_type + * @return ERROR or NO_ERROR + * + * This is the read_frame function which performs the actual read of the + * selected frame type. + * + * No write takes place here! + * + */ long Interface::hread_frame() { std::string function = "Archon::Interface::hread_frame"; std::stringstream message; @@ -2627,7 +2788,7 @@ namespace Archon { char *ptr_image; int bytesread, totalbytesread, toread; uint64_t bufaddr; - unsigned int block, bufblocks=0; + unsigned int block, bufblocks = 0; long error = ERROR; int num_detect = this->modemap[this->camera_info.current_observing_mode].geometry.num_detect; @@ -2636,12 +2797,15 @@ namespace Archon { bufready = this->frame.index + 1; if (bufready < 1 || bufready > this->camera_info.activebufs) { - message.str(""); message << "invalid Archon buffer " << bufready << " requested. Expected {1:" << this->camera_info.activebufs << "}"; - this->camera.log_error( function, message.str() ); + message.str(""); + message << "invalid Archon buffer " << bufready << " requested. Expected {1:" << this->camera_info. + activebufs << "}"; + this->camera.log_error(function, message.str()); return ERROR; } - message.str(""); message << "will read image data from Archon controller buffer " << bufready << " frame " << this->frame.frame; + message.str(""); + message << "will read image data from Archon controller buffer " << bufready << " frame " << this->frame.frame; logwrite(function, message.str()); // Lock the frame buffer before reading it @@ -2656,25 +2820,278 @@ namespace Archon { // has a message header. // Archon buffer base address - bufaddr = this->frame.bufbase[this->frame.index]; + bufaddr = this->frame.bufbase[this->frame.index]; // Calculate the number of blocks expected. image_memory is bytes per detector bufblocks = - (unsigned int) floor( ((this->camera_info.image_memory * num_detect) + BLOCK_LEN - 1 ) / BLOCK_LEN ); + (unsigned int) floor(((this->camera_info.image_memory * num_detect) + BLOCK_LEN - 1) / BLOCK_LEN); + + message.str(""); + message << "will read " << std::dec << this->camera_info.image_memory << " bytes " + << "0x" << std::uppercase << std::hex << bufblocks << " blocks from bufaddr=0x" << bufaddr; + logwrite(function, message.str()); + + if (!this->is_autofetch) { + // send the FETCH command. + // This will take the archon_busy semaphore, but not release it -- must release in this function! + // + error = this->fetch(bufaddr, bufblocks); + if (error != NO_ERROR) { + logwrite(function, "ERROR: fetching Archon buffer"); + return error; + } + } + + // Read the data from the connected socket into memory, one block at a time + // + ptr_image = this->image_data; + totalbytesread = 0; + std::cerr << "reading bytes: "; + for (block = 0; block < bufblocks; block++) { + // Are there data to read? + if ((retval = this->archon.Poll()) <= 0) { + if (retval == 0) { + message.str(""); + message << "Poll timeout waiting for Archon frame data"; + error = ERROR; + } // TODO should error=TIMEOUT? + + if (retval < 0) { + message.str(""); + message << "Poll error waiting for Archon frame data"; + error = ERROR; + } + + if (error != NO_ERROR) this->camera.log_error(function, message.str()); + break; // breaks out of for loop + } + + // Wait for a block+header Bytes to be available + // (but don't wait more than 1 second -- this should be tens of microseconds or less) + // + auto start = std::chrono::steady_clock::now(); // start a timer now + + 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 + std::cerr << "\n"; + this->camera.log_error(function, "timeout waiting for data from Archon"); + error = ERROR; + break; // breaks out of while loop + } + } + if (error != NO_ERROR) break; // needed to also break out of for loop on error + + // Check message header + // + SNPRINTF(check, "<%02X:", this->msgref) + if ((retval = this->archon.Read(header, 4)) != 4) { + message.str(""); + message << "code " << retval << " reading Archon frame header"; + this->camera.log_error(function, message.str()); + error = ERROR; + break; // break out of for loop + } + if (header[0] == '?') { + // Archon retured an error + message.str(""); + message << "Archon returned \'?\' reading image data"; + this->camera.log_error(function, message.str()); + this->fetchlog(); // check the Archon log for error messages + error = ERROR; + break; // break out of for loop + } else if (strncmp(header, check, 4) != 0) { + message.str(""); + message << "Archon command-reply mismatch reading image data. header=" << header << " check=" << check; + this->camera.log_error(function, message.str()); + error = ERROR; + break; // break out of for loop + } + + // Read the frame contents + // + bytesread = 0; + do { + toread = BLOCK_LEN - bytesread; + if ((retval = this->archon.Read(ptr_image, (size_t) toread)) > 0) { + bytesread += retval; // this will get zeroed after each block + totalbytesread += retval; // this won't (used only for info purposes) + std::cerr << std::setw(10) << totalbytesread << "\b\b\b\b\b\b\b\b\b\b"; + ptr_image += retval; // advance pointer + } + } while (bytesread < BLOCK_LEN); + } // end of loop: for (block=0; block lock(this->archon_mutex); + this->archon_busy = false; + this->archon_mutex.unlock(); + + std::cerr << std::setw(10) << totalbytesread << " complete\n"; // display progress on same line of std err + + // If we broke out of the for loop for an error then report incomplete read + // + if (error == ERROR || block < bufblocks) { + message.str(""); + message << "incomplete frame read " << std::dec + << totalbytesread << " bytes: " << block << " of " << bufblocks << " 1024-byte blocks"; + logwrite(function, message.str()); + } + + // Unlock the frame buffer + // + // if (error == NO_ERROR) error = this->archon_cmd(UNLOCK); + + // On success, write the value to the log and return + // + if (error == NO_ERROR) { + message.str(""); + message << "successfully read " << std::dec << totalbytesread + << " image bytes (0x" << std::uppercase << std::hex << bufblocks << + " blocks) from Archon controller"; + logwrite(function, message.str()); + } else { + // Throw an error for any other errors + logwrite(function, "ERROR: reading Archon camera data to memory!"); + } + return error; + } + + /**************** Archon::Interface::hread_frame *****************************/ + + /**************** Archon::Interface::read_frame *****************************/ + /** + * @fn read_frame + * @brief read latest Archon frame buffer + * @param frame_type + * @return ERROR or NO_ERROR + * + * This is the overloaded read_frame function which accepts the frame_type argument. + * This is called only by this->read_frame() to perform the actual read of the + * selected frame type. + * + * No write takes place here! + * + */ + long Interface::read_frame(Camera::frame_type_t frame_type) { + std::string function = "Archon::Interface::read_frame"; + std::stringstream message; + int retval; + int bufready; + char check[5], header[5]; + char *ptr_image; + int bytesread, totalbytesread, toread; + uint64_t bufaddr; + unsigned int block, bufblocks = 0; + long error = ERROR; + int num_detect = this->modemap[this->camera_info.current_observing_mode].geometry.num_detect; + + this->camera_info.frame_type = frame_type; + + /*** + // Check that image buffer is prepared //TODO should I call prepare_image_buffer() here, automatically? + // + if ( (this->image_data == nullptr) || + (this->image_data_bytes == 0) ) { + this->camera.log_error( function, "image buffer not ready" ); + // return ERROR; + } + + if ( this->image_data_allocated != this->image_data_bytes ) { + message.str(""); message << "incorrect image buffer size: " + << this->image_data_allocated << " bytes allocated but " << this->image_data_bytes << " needed"; + this->camera.log_error( function, message.str() ); + // return ERROR; + } + ***/ + + error = this->prepare_image_buffer(); + if (error == ERROR) { + logwrite(function, "ERROR: unable to allocate an image buffer"); + return ERROR; + } + + // TODO removed 2021-Jun-09 + // This shouldn't be needed since wait_for_readout() was called previously. + // // Get the current frame buffer status + // // + // error = this->get_frame_status(); + // + // if (error != NO_ERROR) { + // this->camera.log_error( function, "unable to get frame status"); + // return error; + // } + + // Archon buffer number of the last frame read into memory + // + bufready = this->frame.index + 1; + + if (bufready < 1 || bufready > this->camera_info.activebufs) { + message.str(""); + message << "invalid Archon buffer " << bufready << " requested. Expected {1:" << this->camera_info. + activebufs << "}"; + this->camera.log_error(function, message.str()); + return ERROR; + } + + message.str(""); + message << "will read " << (frame_type == Camera::FRAME_RAW ? "raw" : "image") + << " data from Archon controller buffer " << bufready << " frame " << this->frame.frame; + logwrite(function, message.str()); + + // Lock the frame buffer before reading it + // + if (this->lock_buffer(bufready) == ERROR) { + logwrite(function, "ERROR locking frame buffer"); + return (ERROR); + } + + // Send the FETCH command to read the memory buffer from the Archon backplane. + // Archon replies with one binary response per requested block. Each response + // has a message header. + // + switch (frame_type) { + case Camera::FRAME_RAW: + // Archon buffer base address + bufaddr = this->frame.bufbase[this->frame.index] + this->frame.bufrawoffset[this->frame.index]; + + // Calculate the number of blocks expected. image_memory is bytes per detector + bufblocks = (unsigned int) floor((this->camera_info.image_memory + BLOCK_LEN - 1) / BLOCK_LEN); + break; + + case Camera::FRAME_IMAGE: + // Archon buffer base address + bufaddr = this->frame.bufbase[this->frame.index]; + + // Calculate the number of blocks expected. image_memory is bytes per detector + bufblocks = + (unsigned int) floor( + ((this->camera_info.image_memory * num_detect) + BLOCK_LEN - 1) / BLOCK_LEN); + break; + + default: // should be impossible + message.str(""); + message << "unknown frame type specified: " << frame_type << ": expected FRAME_RAW | FRAME_IMAGE"; + this->camera.log_error(function, message.str()); + return ERROR; + break; + } - message.str(""); message << "will read " << std::dec << this->camera_info.image_memory << " bytes " - << "0x" << std::uppercase << std::hex << bufblocks << " blocks from bufaddr=0x" << bufaddr; + message.str(""); + message << "will read " << std::dec << this->camera_info.image_memory << " bytes " + << "0x" << std::uppercase << std::hex << bufblocks << " blocks from bufaddr=0x" << bufaddr; logwrite(function, message.str()); - if (!this->is_autofetch) { - // send the FETCH command. - // This will take the archon_busy semaphore, but not release it -- must release in this function! - // - error = this->fetch(bufaddr, bufblocks); - if (error != NO_ERROR) { - logwrite(function, "ERROR: fetching Archon buffer"); - return error; - } + // send the FETCH command. + // This will take the archon_busy semaphore, but not release it -- must release in this function! + // + error = this->fetch(bufaddr, bufblocks); + if (error != NO_ERROR) { + logwrite(function, "ERROR: fetching Archon buffer"); + return error; } // Read the data from the connected socket into memory, one block at a time @@ -2682,64 +3099,71 @@ namespace Archon { ptr_image = this->image_data; totalbytesread = 0; std::cerr << "reading bytes: "; - for (block=0; blockarchon.Poll()) <= 0) { - if (retval==0) { + if ((retval = this->archon.Poll()) <= 0) { + if (retval == 0) { message.str(""); message << "Poll timeout waiting for Archon frame data"; error = ERROR; - } // TODO should error=TIMEOUT? + } // TODO should error=TIMEOUT? - if (retval<0) { + if (retval < 0) { message.str(""); message << "Poll error waiting for Archon frame data"; error = ERROR; } - if ( error != NO_ERROR ) this->camera.log_error( function, message.str() ); - break; // breaks out of for loop + if (error != NO_ERROR) this->camera.log_error(function, message.str()); + break; // breaks out of for loop } // Wait for a block+header Bytes to be available // (but don't wait more than 1 second -- this should be tens of microseconds or less) // - auto start = std::chrono::steady_clock::now(); // start a timer now + auto start = std::chrono::steady_clock::now(); // start a timer now - 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 + 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 std::cerr << "\n"; - this->camera.log_error( function, "timeout waiting for data from Archon" ); + this->camera.log_error(function, "timeout waiting for data from Archon"); error = ERROR; - break; // breaks out of while loop + break; // breaks out of while loop } } - if ( error != NO_ERROR ) break; // needed to also break out of for loop on error + if (error != NO_ERROR) break; // needed to also break out of for loop on error // Check message header // SNPRINTF(check, "<%02X:", this->msgref) - if ( (retval=this->archon.Read(header, 4)) != 4 ) { - message.str(""); message << "code " << retval << " reading Archon frame header"; - this->camera.log_error( function, message.str() ); + if ((retval = this->archon.Read(header, 4)) != 4) { + message.str(""); + message << "code " << retval << " reading Archon frame header"; + this->camera.log_error(function, message.str()); error = ERROR; - break; // break out of for loop + break; // break out of for loop } - if (header[0] == '?') { // Archon retured an error - message.str(""); message << "Archon returned \'?\' reading image data"; - this->camera.log_error( function, message.str() ); - this->fetchlog(); // check the Archon log for error messages + if (header[0] == '?') { + // Archon retured an error + message.str(""); + message << "Archon returned \'?\' reading " << (frame_type == Camera::FRAME_RAW ? "raw " : "image ") << + " data"; + this->camera.log_error(function, message.str()); + this->fetchlog(); // check the Archon log for error messages error = ERROR; - break; // break out of for loop - + break; // break out of for loop } else if (strncmp(header, check, 4) != 0) { - message.str(""); message << "Archon command-reply mismatch reading image data. header=" << header << " check=" << check; - this->camera.log_error( function, message.str() ); + message.str(""); + message << "Archon command-reply mismatch reading " << (frame_type == Camera::FRAME_RAW + ? "raw " + : "image ") + << " data. header=" << header << " check=" << check; + this->camera.log_error(function, message.str()); error = ERROR; - break; // break out of for loop + break; // break out of for loop } // Read the frame contents @@ -2747,14 +3171,13 @@ namespace Archon { bytesread = 0; do { toread = BLOCK_LEN - bytesread; - if ( (retval=this->archon.Read(ptr_image, (size_t)toread)) > 0 ) { - bytesread += retval; // this will get zeroed after each block - totalbytesread += retval; // this won't (used only for info purposes) + if ((retval = this->archon.Read(ptr_image, (size_t) toread)) > 0) { + bytesread += retval; // this will get zeroed after each block + totalbytesread += retval; // this won't (used only for info purposes) std::cerr << std::setw(10) << totalbytesread << "\b\b\b\b\b\b\b\b\b\b"; - ptr_image += retval; // advance pointer + ptr_image += retval; // advance pointer } } while (bytesread < BLOCK_LEN); - } // end of loop: for (block=0; blockarchon_busy = false; this->archon_mutex.unlock(); - std::cerr << std::setw(10) << totalbytesread << " complete\n"; // display progress on same line of std err + std::cerr << std::setw(10) << totalbytesread << " complete\n"; // display progress on same line of std err // If we broke out of the for loop for an error then report incomplete read // - if ( error==ERROR || block < bufblocks) { - message.str(""); message << "incomplete frame read " << std::dec - << totalbytesread << " bytes: " << block << " of " << bufblocks << " 1024-byte blocks"; - logwrite( function, message.str() ); + if (error == ERROR || block < bufblocks) { + message.str(""); + message << "incomplete frame read " << std::dec + << totalbytesread << " bytes: " << block << " of " << bufblocks << " 1024-byte blocks"; + logwrite(function, message.str()); } // Unlock the frame buffer // - // if (error == NO_ERROR) error = this->archon_cmd(UNLOCK); + if (error == NO_ERROR) error = this->archon_cmd(UNLOCK); // On success, write the value to the log and return // if (error == NO_ERROR) { - message.str(""); message << "successfully read " << std::dec << totalbytesread - << " image bytes (0x" << std::uppercase << std::hex << bufblocks << " blocks) from Archon controller"; + message.str(""); + message << "successfully read " << std::dec << totalbytesread << (frame_type == Camera::FRAME_RAW + ? " raw" + : " image") + << " bytes (0x" << std::uppercase << std::hex << bufblocks << " blocks) from Archon controller"; logwrite(function, message.str()); - } else { // Throw an error for any other errors - logwrite( function, "ERROR: reading Archon camera data to memory!" ); + logwrite(function, "ERROR: reading Archon camera data to memory!"); } return error; } - /**************** Archon::Interface::hread_frame *****************************/ - - /**************** Archon::Interface::read_frame *****************************/ - /** - * @fn read_frame - * @brief read latest Archon frame buffer - * @param frame_type - * @return ERROR or NO_ERROR - * - * This is the overloaded read_frame function which accepts the frame_type argument. - * This is called only by this->read_frame() to perform the actual read of the - * selected frame type. - * - * No write takes place here! - * - */ - long Interface::read_frame(Camera::frame_type_t frame_type) { - std::string function = "Archon::Interface::read_frame"; - std::stringstream message; - int retval; - int bufready; - char check[5], header[5]; - char *ptr_image; - int bytesread, totalbytesread, toread; - uint64_t bufaddr; - unsigned int block, bufblocks=0; - long error = ERROR; - int num_detect = this->modemap[this->camera_info.current_observing_mode].geometry.num_detect; - - this->camera_info.frame_type = frame_type; - -/*** - // Check that image buffer is prepared //TODO should I call prepare_image_buffer() here, automatically? - // - if ( (this->image_data == nullptr) || - (this->image_data_bytes == 0) ) { - this->camera.log_error( function, "image buffer not ready" ); -// return ERROR; - } - - if ( this->image_data_allocated != this->image_data_bytes ) { - message.str(""); message << "incorrect image buffer size: " - << this->image_data_allocated << " bytes allocated but " << this->image_data_bytes << " needed"; - this->camera.log_error( function, message.str() ); -// return ERROR; - } -***/ - error = this->prepare_image_buffer(); - if (error == ERROR) { - logwrite( function, "ERROR: unable to allocate an image buffer" ); - return ERROR; - } - -// TODO removed 2021-Jun-09 -// This shouldn't be needed since wait_for_readout() was called previously. -// // Get the current frame buffer status -// // -// error = this->get_frame_status(); -// -// if (error != NO_ERROR) { -// this->camera.log_error( function, "unable to get frame status"); -// return error; -// } - - // Archon buffer number of the last frame read into memory - // - bufready = this->frame.index + 1; + /**************** Archon::Interface::read_frame *****************************/ - if (bufready < 1 || bufready > this->camera_info.activebufs) { - message.str(""); message << "invalid Archon buffer " << bufready << " requested. Expected {1:" << this->camera_info.activebufs << "}"; - this->camera.log_error( function, message.str() ); - return ERROR; - } - message.str(""); message << "will read " << (frame_type == Camera::FRAME_RAW ? "raw" : "image") - << " data from Archon controller buffer " << bufready << " frame " << this->frame.frame; - logwrite(function, message.str()); + /**************** Archon::Interface::write_frame ****************************/ + /** + * @fn write_frame + * @brief creates a FITS_file object to write the image_data buffer to disk + * @param none + * @return ERROR or NO_ERROR + * + * A FITS_file object is created here to write the data. This object MUST remain + * valid while any (all) threads are writing data, so the write_data function + * will keep track of threads so that it doesn't terminate until all of its + * threads terminate. + * + * The camera_info class was copied into fits_info when the exposure was started, //TODO I've un-done this. + * so use fits_info from here on out. //TODO Don't use fits_info right now. + * //TODO Only using camera_info + */ + long Interface::write_frame() { + std::string function = "Archon::Interface::write_frame"; + std::stringstream message; + uint32_t *cbuf32; //!< used to cast char buf into 32 bit int + uint16_t *cbuf16; //!< used to cast char buf into 16 bit int + int16_t *cbuf16s; //!< used to cast char buf into 16 bit int + long error = NO_ERROR; - // Lock the frame buffer before reading it - // - if ( this->lock_buffer(bufready) == ERROR) { - logwrite( function, "ERROR locking frame buffer" ); - return (ERROR); - } + if (!this->modeselected) { + this->camera.log_error(function, "no mode selected"); + return ERROR; + } - // Send the FETCH command to read the memory buffer from the Archon backplane. - // Archon replies with one binary response per requested block. Each response - // has a message header. - // - switch (frame_type) { - case Camera::FRAME_RAW: - // Archon buffer base address - bufaddr = this->frame.bufbase[this->frame.index] + this->frame.bufrawoffset[this->frame.index]; + // message.str(""); message << "writing " << this->fits_info.bitpix << "-bit data from memory to disk"; //TODO + message.str(""); + message << "writing " << this->camera_info.bitpix << "-bit data from memory to disk"; + logwrite(function, message.str()); - // Calculate the number of blocks expected. image_memory is bytes per detector - bufblocks = (unsigned int) floor( (this->camera_info.image_memory + BLOCK_LEN - 1 ) / BLOCK_LEN ); - break; + // The Archon sends four 8-bit numbers per pixel. To convert this into something usable, + // cast the image buffer into integers. Handled differently depending on bits per pixel. + // + switch (this->camera_info.bitpix) { + // convert four 8-bit values into a 32-bit value and scale by 2^16 + // + case 32: { + cbuf32 = (uint32_t *) this->image_data; // cast here to 32b + + // Write each amplifier as a separate extension + // + if (this->camera.cubeamps()) { + float *fext = nullptr; + + for (int ext = 0; ext < (int) this->camera_info.amp_section.size(); ext++) { + try { + // get the coordinates of this amplifier extension + // + long x1 = this->camera_info.amp_section.at(ext).at(0); + long x2 = this->camera_info.amp_section.at(ext).at(1); + long y1 = this->camera_info.amp_section.at(ext).at(2); + long y2 = this->camera_info.amp_section.at(ext).at(3); + + // assign this amplifier section as the region of interest + // + this->camera_info.region_of_interest[0] = x1; + this->camera_info.region_of_interest[1] = x2; + this->camera_info.region_of_interest[2] = y1; + this->camera_info.region_of_interest[3] = y2; + + // This call to set_axes() is to set the axis_pixels, axes, and section_size, + // needed for the FITS writer + // + error = this->camera_info.set_axes(); - case Camera::FRAME_IMAGE: - // Archon buffer base address - bufaddr = this->frame.bufbase[this->frame.index]; +#ifdef LOGLEVEL_DEBUG + message.str(""); message << "[DEBUG] x1=" << x1 << " x2=" << x2 << " y1=" << y1 << " y2=" << y2; + logwrite( function, message.str() ); + message.str(""); message << "[DEBUG] axes[0]=" << this->camera_info.axes[0] << " axes[1]=" << this->camera_info.axes[1]; + logwrite( function, message.str() ); +#endif - // Calculate the number of blocks expected. image_memory is bytes per detector - bufblocks = - (unsigned int) floor( ((this->camera_info.image_memory * num_detect) + BLOCK_LEN - 1 ) / BLOCK_LEN ); - break; - - default: // should be impossible - message.str(""); message << "unknown frame type specified: " << frame_type << ": expected FRAME_RAW | FRAME_IMAGE"; - this->camera.log_error( function, message.str() ); - return ERROR; - break; - } + // allocate the number of pixels needed for this amplifier extension + // + long ext_size = (x2 - x1 + 1) * (y2 - y1 + 1); + fext = new float[ext_size]; - message.str(""); message << "will read " << std::dec << this->camera_info.image_memory << " bytes " - << "0x" << std::uppercase << std::hex << bufblocks << " blocks from bufaddr=0x" << bufaddr; - logwrite(function, message.str()); +#ifdef LOGLEVEL_DEBUG + message.str(""); message << "[DEBUG] allocated " << ext_size << " pixels for extension " << this->camera_info.extension+1; + logwrite( function, message.str() ); +#endif - // send the FETCH command. - // This will take the archon_busy semaphore, but not release it -- must release in this function! - // - error = this->fetch(bufaddr, bufblocks); - if ( error != NO_ERROR ) { - logwrite( function, "ERROR: fetching Archon buffer" ); - return error; - } + // copy this amplifier from the main cbuf32, + // at the same time right-shift the requested number of bits + // + long pix = 0; + long ncols = this->camera_info.detector_pixels[0]; // PIXELCOUNT + for (long row = y1 - 1; row < y2; row++) { + for (long col = x1 - 1; col < x2; col++) { + fext[pix++] = (float) (cbuf32[row * ncols + col] >> this->n_hdrshift); + } + } - // Read the data from the connected socket into memory, one block at a time - // - ptr_image = this->image_data; - totalbytesread = 0; - std::cerr << "reading bytes: "; - for (block=0; blockarchon.Poll()) <= 0) { - if (retval==0) { - message.str(""); - message << "Poll timeout waiting for Archon frame data"; - error = ERROR; - } // TODO should error=TIMEOUT? +#ifdef LOGLEVEL_DEBUG + message.str(""); message << "[DEBUG] calling fits_file.write_image( ) for extension " << this->camera_info.extension+1; + logwrite( function, message.str() ); +#endif - if (retval<0) { - message.str(""); - message << "Poll error waiting for Archon frame data"; - error = ERROR; - } + error = this->fits_file.write_image(fext, this->camera_info); // write the image to disk + this->camera_info.extension++; // increment extension for cubes + if (fext != nullptr) { + delete [] fext; + fext = nullptr; + } // dynamic object not automatic so must be destroyed + } catch (std::out_of_range &) { + message.str(""); + message << "ERROR: " << ext << " is a bad extension number"; + logwrite(function, message.str()); + if (fext != nullptr) { + delete [] fext; + fext = nullptr; + } // dynamic object not automatic so must be destroyed + return ERROR; + } + } + // end if this->camera.cubeamps() + } else { + // Write all amplifiers to the same extension + // + float *fbuf = nullptr; + // fbuf = new float[ this->fits_info.section_size ]; // allocate a float buffer of same number of pixels for scaling //TODO + fbuf = new float[this->camera_info.section_size]; + // allocate a float buffer of same number of pixels for scaling + + // for (long pix=0; pix < this->fits_info.section_size; pix++) //TODO + for (long pix = 0; pix < this->camera_info.section_size; pix++) { + fbuf[pix] = (float) (cbuf32[pix] >> this->n_hdrshift); + // right shift the requested number of bits + } - if ( error != NO_ERROR ) this->camera.log_error( function, message.str() ); - break; // breaks out of for loop - } + // error = fits_file.write_image(fbuf, this->fits_info); // write the image to disk //TODO + error = this->fits_file.write_image(fbuf, this->camera_info); // write the image to disk + if (error != NO_ERROR) { this->camera.log_error(function, "writing 32-bit image to disk"); } + delete [] fbuf; + } + break; + } - // Wait for a block+header Bytes to be available - // (but don't wait more than 1 second -- this should be tens of microseconds or less) - // - auto start = std::chrono::steady_clock::now(); // start a timer now + // convert four 8-bit values into 16 bit values + // + case 16: { + if (this->camera_info.datatype == USHORT_IMG) { + // raw + cbuf16 = (uint16_t *) this->image_data; // cast to 16b unsigned int + // error = fits_file.write_image(cbuf16, this->fits_info); // write the image to disk //TODO + error = this->fits_file.write_image(cbuf16, this->camera_info); // write the image to disk + if (error != NO_ERROR) { this->camera.log_error(function, "writing 16-bit raw image to disk"); } + } else if (this->camera_info.datatype == SHORT_IMG) { + cbuf16s = (int16_t *) this->image_data; // cast to 16b signed int + int16_t *ibuf = nullptr; + ibuf = new int16_t[this->camera_info.section_size]; + for (long pix = 0; pix < this->camera_info.section_size; pix++) { + ibuf[pix] = cbuf16s[pix] - 32768; // subtract 2^15 from every pixel + } + error = this->fits_file.write_image(ibuf, this->camera_info); // write the image to disk + if (error != NO_ERROR) { this->camera.log_error(function, "writing 16-bit image to disk"); } + delete [] ibuf; + } else { + message.str(""); + message << "unsupported 16 bit datatype " << this->camera_info.datatype; + this->camera.log_error(function, message.str()); + error = ERROR; + } + break; + } - 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 - std::cerr << "\n"; - this->camera.log_error( function, "timeout waiting for data from Archon" ); - error = ERROR; - break; // breaks out of while loop + // shouldn't happen + // + default: + // message.str(""); message << "unrecognized bits per pixel: " << this->fits_info.bitpix; //TODO + message.str(""); + message << "unrecognized bits per pixel: " << this->camera_info.bitpix; + this->camera.log_error(function, message.str()); + error = ERROR; + break; } - } - if ( error != NO_ERROR ) break; // needed to also break out of for loop on error - - // Check message header - // - SNPRINTF(check, "<%02X:", this->msgref) - if ( (retval=this->archon.Read(header, 4)) != 4 ) { - message.str(""); message << "code " << retval << " reading Archon frame header"; - this->camera.log_error( function, message.str() ); - error = ERROR; - break; // break out of for loop - } - if (header[0] == '?') { // Archon retured an error - message.str(""); message << "Archon returned \'?\' reading " << (frame_type==Camera::FRAME_RAW?"raw ":"image ") << " data"; - this->camera.log_error( function, message.str() ); - this->fetchlog(); // check the Archon log for error messages - error = ERROR; - break; // break out of for loop - - } else if (strncmp(header, check, 4) != 0) { - message.str(""); message << "Archon command-reply mismatch reading " << (frame_type==Camera::FRAME_RAW?"raw ":"image ") - << " data. header=" << header << " check=" << check; - this->camera.log_error( function, message.str() ); - error = ERROR; - break; // break out of for loop - } - // Read the frame contents - // - bytesread = 0; - do { - toread = BLOCK_LEN - bytesread; - if ( (retval=this->archon.Read(ptr_image, (size_t)toread)) > 0 ) { - bytesread += retval; // this will get zeroed after each block - totalbytesread += retval; // this won't (used only for info purposes) - std::cerr << std::setw(10) << totalbytesread << "\b\b\b\b\b\b\b\b\b\b"; - ptr_image += retval; // advance pointer + // Things to do after successful write + // + if (error == NO_ERROR) { + if (this->camera.datacube()) { + this->camera_info.extension++; // increment extension for cubes + message.str(""); + message << "DATACUBE:" << this->camera_info.extension << " " << (error == NO_ERROR + ? "COMPLETE" + : "ERROR"); + this->camera.async.enqueue(message.str()); + error == NO_ERROR ? logwrite(function, message.str()) : this->camera.log_error(function, message.str()); + } + logwrite(function, "frame write complete"); + } else { + logwrite(function, "ERROR: writing image"); } - } while (bytesread < BLOCK_LEN); - - } // end of loop: for (block=0; block lock(this->archon_mutex); - this->archon_busy = false; - this->archon_mutex.unlock(); - - std::cerr << std::setw(10) << totalbytesread << " complete\n"; // display progress on same line of std err - // If we broke out of the for loop for an error then report incomplete read - // - if ( error==ERROR || block < bufblocks) { - message.str(""); message << "incomplete frame read " << std::dec - << totalbytesread << " bytes: " << block << " of " << bufblocks << " 1024-byte blocks"; - logwrite( function, message.str() ); + return error; } - // Unlock the frame buffer - // - if (error == NO_ERROR) error = this->archon_cmd(UNLOCK); - - // On success, write the value to the log and return - // - if (error == NO_ERROR) { - message.str(""); message << "successfully read " << std::dec << totalbytesread << (frame_type==Camera::FRAME_RAW?" raw":" image") - << " bytes (0x" << std::uppercase << std::hex << bufblocks << " blocks) from Archon controller"; - logwrite(function, message.str()); - - } else { - // Throw an error for any other errors - logwrite( function, "ERROR: reading Archon camera data to memory!" ); - } - return error; - } - /**************** Archon::Interface::read_frame *****************************/ - - - /**************** Archon::Interface::write_frame ****************************/ - /** - * @fn write_frame - * @brief creates a FITS_file object to write the image_data buffer to disk - * @param none - * @return ERROR or NO_ERROR - * - * A FITS_file object is created here to write the data. This object MUST remain - * valid while any (all) threads are writing data, so the write_data function - * will keep track of threads so that it doesn't terminate until all of its - * threads terminate. - * - * The camera_info class was copied into fits_info when the exposure was started, //TODO I've un-done this. - * so use fits_info from here on out. //TODO Don't use fits_info right now. - * //TODO Only using camera_info - */ - long Interface::write_frame() { - std::string function = "Archon::Interface::write_frame"; - std::stringstream message; - uint32_t *cbuf32; //!< used to cast char buf into 32 bit int - uint16_t *cbuf16; //!< used to cast char buf into 16 bit int - int16_t *cbuf16s; //!< used to cast char buf into 16 bit int - long error=NO_ERROR; - - if ( ! this->modeselected ) { - this->camera.log_error( function, "no mode selected" ); - return ERROR; - } + /**************** Archon::Interface::write_frame ****************************/ -// message.str(""); message << "writing " << this->fits_info.bitpix << "-bit data from memory to disk"; //TODO - message.str(""); message << "writing " << this->camera_info.bitpix << "-bit data from memory to disk"; - logwrite(function, message.str()); - // The Archon sends four 8-bit numbers per pixel. To convert this into something usable, - // cast the image buffer into integers. Handled differently depending on bits per pixel. - // - switch (this->camera_info.bitpix) { + /**************** Archon::Interface::write_raw ******************************/ + /** + * @fn write_raw + * @brief write raw 16 bit data to a FITS file + * @param none + * @return ERROR or NO_ERROR + * + */ + long Interface::write_raw() { + std::string function = "Archon::Interface::write_raw"; + std::stringstream message; - // convert four 8-bit values into a 32-bit value and scale by 2^16 - // - case 32: { - cbuf32 = (uint32_t *)this->image_data; // cast here to 32b + unsigned short *cbuf16; //!< used to cast char buf into 16 bit int - // Write each amplifier as a separate extension + // Cast the image buffer of chars into integers to convert four 8-bit values + // into a 16-bit value // - if ( this->camera.cubeamps() ) { - float *fext = nullptr; - - for ( int ext=0; ext < (int)this->camera_info.amp_section.size(); ext++ ) { - try { - // get the coordinates of this amplifier extension - // - long x1 = this->camera_info.amp_section.at(ext).at(0); - long x2 = this->camera_info.amp_section.at(ext).at(1); - long y1 = this->camera_info.amp_section.at(ext).at(2); - long y2 = this->camera_info.amp_section.at(ext).at(3); - - // assign this amplifier section as the region of interest - // - this->camera_info.region_of_interest[0] = x1; - this->camera_info.region_of_interest[1] = x2; - this->camera_info.region_of_interest[2] = y1; - this->camera_info.region_of_interest[3] = y2; - - // This call to set_axes() is to set the axis_pixels, axes, and section_size, - // needed for the FITS writer - // - error = this->camera_info.set_axes(); - - #ifdef LOGLEVEL_DEBUG - message.str(""); message << "[DEBUG] x1=" << x1 << " x2=" << x2 << " y1=" << y1 << " y2=" << y2; - logwrite( function, message.str() ); - message.str(""); message << "[DEBUG] axes[0]=" << this->camera_info.axes[0] << " axes[1]=" << this->camera_info.axes[1]; - logwrite( function, message.str() ); - #endif + cbuf16 = (unsigned short *) this->image_data; - // allocate the number of pixels needed for this amplifier extension - // - long ext_size = (x2-x1+1) * (y2-y1+1); - fext = new float[ ext_size ]; - - #ifdef LOGLEVEL_DEBUG - message.str(""); message << "[DEBUG] allocated " << ext_size << " pixels for extension " << this->camera_info.extension+1; - logwrite( function, message.str() ); - #endif - - // copy this amplifier from the main cbuf32, - // at the same time right-shift the requested number of bits - // - long pix=0; - long ncols=this->camera_info.detector_pixels[0]; // PIXELCOUNT - for ( long row=y1-1; row> this->n_hdrshift ); - } - } + fitsfile *FP = nullptr; + int status = 0; + int naxes = 2; + long axes[2]; + long firstele = 1; + long nelements; - #ifdef LOGLEVEL_DEBUG - message.str(""); message << "[DEBUG] calling fits_file.write_image( ) for extension " << this->camera_info.extension+1; - logwrite( function, message.str() ); - #endif + axes[0] = this->camera_info.axes[0]; + axes[1] = this->camera_info.axes[1]; - error = this->fits_file.write_image(fext, this->camera_info); // write the image to disk - this->camera_info.extension++; // increment extension for cubes - if ( fext != nullptr ) { delete [] fext; fext=nullptr; } // dynamic object not automatic so must be destroyed + nelements = axes[0] * axes[1]; - } catch( std::out_of_range & ) { - message.str(""); message << "ERROR: " << ext << " is a bad extension number"; - logwrite( function, message.str() ); - if ( fext != nullptr ) { delete [] fext; fext=nullptr; } // dynamic object not automatic so must be destroyed - return ERROR; + // create fits file + // + if (this->camera_info.extension == 0) { +#ifdef LOGLEVEL_DEBUG + logwrite(function, "[DEBUG] creating fits file with cfitsio"); +#endif + if (fits_create_file(&FP, this->camera_info.fits_name.c_str(), &status)) { + message.str(""); + message << "cfitsio error " << status << " creating FITS file " << this->camera_info.fits_name; + this->camera.log_error(function, message.str()); + return ERROR; } - } - // end if this->camera.cubeamps() - } else { - // Write all amplifiers to the same extension - // - float *fbuf = nullptr; -// fbuf = new float[ this->fits_info.section_size ]; // allocate a float buffer of same number of pixels for scaling //TODO - fbuf = new float[ this->camera_info.section_size ]; // allocate a float buffer of same number of pixels for scaling - -// for (long pix=0; pix < this->fits_info.section_size; pix++) //TODO - for (long pix=0; pix < this->camera_info.section_size; pix++) { - fbuf[pix] = (float) ( cbuf32[pix] >> this->n_hdrshift ); // right shift the requested number of bits +#ifdef LOGLEVEL_DEBUG + logwrite(function, "[DEBUG] opening fits file with cfitsio"); + message.str(""); message << "[DEBUG] file=" << this->camera_info.fits_name << " extension=" << this->camera_info.extension + << " bitpix=" << this->camera_info.bitpix; + logwrite(function, message.str()); +#endif + if (fits_open_file(&FP, this->camera_info.fits_name.c_str(), READWRITE, &status)) { + message.str(""); + message << "cfitsio error " << status << " opening FITS file " << this->camera_info.fits_name; + this->camera.log_error(function, message.str()); + return ERROR; } + } -// error = fits_file.write_image(fbuf, this->fits_info); // write the image to disk //TODO - error = this->fits_file.write_image(fbuf, this->camera_info); // write the image to disk - if ( error != NO_ERROR ) { this->camera.log_error( function, "writing 32-bit image to disk" ); } - delete [] fbuf; + // create image + // + logwrite(function, "create image"); + message.str(""); + message << "axes=" << axes[0] << " " << axes[1]; + logwrite(function, message.str()); + if (fits_create_img(FP, USHORT_IMG, naxes, axes, &status)) { + message.str(""); + message << "fitsio error " << status << " creating FITS image for " << this->camera_info.fits_name; + this->camera.log_error(function, message.str()); + return ERROR; } - break; - } - // convert four 8-bit values into 16 bit values - // - case 16: { - if (this->camera_info.datatype == USHORT_IMG) { // raw - cbuf16 = (uint16_t *)this->image_data; // cast to 16b unsigned int -// error = fits_file.write_image(cbuf16, this->fits_info); // write the image to disk //TODO - error = this->fits_file.write_image(cbuf16, this->camera_info); // write the image to disk - if ( error != NO_ERROR ) { this->camera.log_error( function, "writing 16-bit raw image to disk" ); } - - } else if (this->camera_info.datatype == SHORT_IMG) { - cbuf16s = (int16_t *)this->image_data; // cast to 16b signed int - int16_t *ibuf = nullptr; - ibuf = new int16_t[ this->camera_info.section_size ]; - for (long pix=0; pix < this->camera_info.section_size; pix++) { - ibuf[pix] = cbuf16s[pix] - 32768; // subtract 2^15 from every pixel - } - error = this->fits_file.write_image(ibuf, this->camera_info); // write the image to disk - if ( error != NO_ERROR ) { this->camera.log_error( function, "writing 16-bit image to disk" ); } - delete [] ibuf; + // supplemental header keywords + // + fits_write_key(FP, TSTRING, "MODE", &this->camera_info.current_observing_mode, "observing mode", &status); - } else { - message.str(""); message << "unsupported 16 bit datatype " << this->camera_info.datatype; - this->camera.log_error( function, message.str() ); - error = ERROR; + // write HDU + // + logwrite(function, "write HDU"); + if (fits_write_img(FP, TUSHORT, firstele, nelements, cbuf16, &status)) { + message.str(""); + message << "fitsio error " << status << " writing FITS image HDU to " << this->camera_info.fits_name; + this->camera.log_error(function, message.str()); + return ERROR; + } + + // close file + // + logwrite(function, "close file"); + if (fits_close_file(FP, &status)) { + message.str(""); + message << "fitsio error " << status << " closing fits file " << this->camera_info.fits_name; + this->camera.log_error(function, message.str()); + return ERROR; } - break; - } - // shouldn't happen - // - default: -// message.str(""); message << "unrecognized bits per pixel: " << this->fits_info.bitpix; //TODO - message.str(""); message << "unrecognized bits per pixel: " << this->camera_info.bitpix; - this->camera.log_error( function, message.str() ); - error = ERROR; - break; + return NO_ERROR; } - // Things to do after successful write - // - if ( error == NO_ERROR ) { - if ( this->camera.datacube() ) { - this->camera_info.extension++; // increment extension for cubes - message.str(""); message << "DATACUBE:" << this->camera_info.extension << " " << ( error==NO_ERROR ? "COMPLETE" : "ERROR" ); - this->camera.async.enqueue( message.str() ); - error == NO_ERROR ? logwrite( function, message.str() ) : this->camera.log_error( function, message.str() ); - } - logwrite(function, "frame write complete"); + /**************** Archon::Interface::write_raw ******************************/ - } else { - logwrite( function, "ERROR: writing image" ); - } - return error; - } - /**************** Archon::Interface::write_frame ****************************/ + /**************** Archon::Interface::write_config_key ***********************/ + /** + * @fn write_config_key + * @brief write a configuration KEY=VALUE pair to the Archon controller + * @param key + * @param newvalue + * @return ERROR or NO_ERROR + * + */ + long Interface::write_config_key(const char *key, const char *newvalue, bool &changed) { + std::string function = "Archon::Interface::write_config_key"; + std::stringstream message, sscmd; + long error = NO_ERROR; + if (key == nullptr || newvalue == nullptr) { + error = ERROR; + this->camera.log_error(function, "key|value cannot have NULL"); + } else if (this->configmap.find(key) == this->configmap.end()) { + error = ERROR; + message.str(""); + message << "requested key " << key << " not found in configmap"; + this->camera.log_error(function, message.str()); + } else if (this->configmap[key].value == newvalue) { + // If no change in value then don't send the command + error = NO_ERROR; + message.str(""); + message << "config key " << key << "=" << newvalue << " not written: no change in value"; + logwrite(function, message.str()); + } else { + /** + * Format and send the Archon WCONFIG command + * to write the KEY=VALUE pair to controller memory + */ + sscmd << "WCONFIG" + << std::uppercase << std::setfill('0') << std::setw(4) << std::hex + << this->configmap[key].line + << key + << "=" + << newvalue; + message.str(""); + message << "sending: archon_cmd(" << sscmd.str() << ")"; + logwrite(function, message.str()); + error = this->archon_cmd((char *) sscmd.str().c_str()); // send the WCONFIG command here + if (error == NO_ERROR) { + this->configmap[key].value = newvalue; // save newvalue in the STL map + changed = true; + } else { + message.str(""); + message << "ERROR: config key=value: " << key << "=" << newvalue << " not written"; + logwrite(function, message.str()); + } + } + return error; + } - /**************** Archon::Interface::write_raw ******************************/ - /** - * @fn write_raw - * @brief write raw 16 bit data to a FITS file - * @param none - * @return ERROR or NO_ERROR - * - */ - long Interface::write_raw() { - std::string function = "Archon::Interface::write_raw"; - std::stringstream message; + long Interface::write_config_key(const char *key, int newvalue, bool &changed) { + std::stringstream newvaluestr; + newvaluestr << newvalue; + return (write_config_key(key, newvaluestr.str().c_str(), changed)); + } - unsigned short *cbuf16; //!< used to cast char buf into 16 bit int + /**************** Archon::Interface::write_config_key ***********************/ - // Cast the image buffer of chars into integers to convert four 8-bit values - // into a 16-bit value - // - cbuf16 = (unsigned short *)this->image_data; - fitsfile *FP = nullptr; - int status = 0; - int naxes = 2; - long axes[2]; - long firstele = 1; - long nelements; + /**************** Archon::Interface::write_parameter ************************/ + /** + * @fn write_parameter + * @brief write a parameter to the Archon configuration memory + * @param paramname + * @param newvalue + * @return NO_ERROR or ERROR + * + * After writing a parameter, requires an APPLYALL or LOADPARAMS command + * + */ + long Interface::write_parameter(const char *paramname, const char *newvalue, bool &changed) { + std::string function = "Archon::Interface::write_parameter"; + std::stringstream message, sscmd; + long error = NO_ERROR; - axes[0] = this->camera_info.axes[0]; - axes[1] = this->camera_info.axes[1]; +#ifdef LOGLEVEL_DEBUG + message.str(""); message << "[DEBUG] paramname=" << paramname << " value=" << newvalue; + logwrite( function, message.str() ); +#endif - nelements = axes[0] * axes[1]; + if (paramname == nullptr || newvalue == nullptr) { + error = ERROR; + this->camera.log_error(function, "paramname|value cannot have NULL"); + } else if (this->parammap.find(paramname) == this->parammap.end()) { + error = ERROR; + message.str(""); + message << "parameter \"" << paramname << "\" not found in parammap"; + this->camera.log_error(function, message.str()); + } - // create fits file - // - if (this->camera_info.extension == 0) { - #ifdef LOGLEVEL_DEBUG - logwrite(function, "[DEBUG] creating fits file with cfitsio"); - #endif - if (fits_create_file( &FP, this->camera_info.fits_name.c_str(), &status ) ) { - message.str(""); - message << "cfitsio error " << status << " creating FITS file " << this->camera_info.fits_name; - this->camera.log_error( function, message.str() ); - return ERROR; - } + /** + * If no change in value then don't send the command + */ + if (error == NO_ERROR && this->parammap[paramname].value == newvalue) { + error = NO_ERROR; + message.str(""); + message << "parameter " << paramname << "=" << newvalue << " not written: no change in value"; + logwrite(function, message.str()); + } else if (error == NO_ERROR) { + /** + * Format and send the Archon command WCONFIGxxxxttt...ttt + * which writes the text ttt...ttt to configuration line xxx (hex) + * to controller memory. + */ + sscmd << "WCONFIG" + << std::uppercase << std::setfill('0') << std::setw(4) << std::hex + << this->parammap[paramname].line + << this->parammap[paramname].key + << "=" + << this->parammap[paramname].name + << "=" + << newvalue; + message.str(""); + message << "sending archon_cmd(" << sscmd.str() << ")"; + logwrite(function, message.str()); + error = this->archon_cmd((char *) sscmd.str().c_str()); // send the WCONFIG command here + if (error == NO_ERROR) { + this->parammap[paramname].value = newvalue; // save newvalue in the STL map + changed = true; + } else logwrite(function, "ERROR: sending WCONFIG command"); + } - } else { - #ifdef LOGLEVEL_DEBUG - logwrite(function, "[DEBUG] opening fits file with cfitsio"); - message.str(""); message << "[DEBUG] file=" << this->camera_info.fits_name << " extension=" << this->camera_info.extension - << " bitpix=" << this->camera_info.bitpix; - logwrite(function, message.str()); - #endif - if (fits_open_file( &FP, this->camera_info.fits_name.c_str(), READWRITE, &status ) ) { - message.str(""); - message << "cfitsio error " << status << " opening FITS file " << this->camera_info.fits_name; - this->camera.log_error( function, message.str() ); - return ERROR; - } + return error; } - // create image - // - logwrite(function, "create image"); - message.str(""); message << "axes=" << axes[0] << " " << axes[1]; - logwrite(function, message.str()); - if ( fits_create_img( FP, USHORT_IMG, naxes, axes, &status) ) { - message.str(""); - message << "fitsio error " << status << " creating FITS image for " << this->camera_info.fits_name; - this->camera.log_error( function, message.str() ); - return ERROR; + long Interface::write_parameter(const char *paramname, int newvalue, bool &changed) { + std::stringstream newvaluestr; + newvaluestr << newvalue; + return (write_parameter(paramname, newvaluestr.str().c_str(), changed)); } - // supplemental header keywords - // - fits_write_key( FP, TSTRING, "MODE", &this->camera_info.current_observing_mode, "observing mode", &status ); - - // write HDU - // - logwrite(function, "write HDU"); - if ( fits_write_img( FP, TUSHORT, firstele, nelements, cbuf16, &status) ) { - message.str(""); - message << "fitsio error " << status << " writing FITS image HDU to " << this->camera_info.fits_name; - this->camera.log_error( function, message.str() ); - return ERROR; + long Interface::write_parameter(const char *paramname, const char *newvalue) { + bool dontcare = false; + return (write_parameter(paramname, newvalue, dontcare)); } - // close file - // - logwrite(function, "close file"); - if ( fits_close_file( FP, &status ) ) { - message.str(""); - message << "fitsio error " << status << " closing fits file " << this->camera_info.fits_name; - this->camera.log_error( function, message.str() ); - return ERROR; + long Interface::write_parameter(const char *paramname, int newvalue) { + bool dontcare = false; + std::stringstream newvaluestr; + newvaluestr << newvalue; + return (write_parameter(paramname, newvaluestr.str().c_str(), dontcare)); } - return NO_ERROR; - } - /**************** Archon::Interface::write_raw ******************************/ - - - /**************** Archon::Interface::write_config_key ***********************/ - /** - * @fn write_config_key - * @brief write a configuration KEY=VALUE pair to the Archon controller - * @param key - * @param newvalue - * @return ERROR or NO_ERROR - * - */ - long Interface::write_config_key( const char *key, const char *newvalue, bool &changed ) { - std::string function = "Archon::Interface::write_config_key"; - std::stringstream message, sscmd; - long error=NO_ERROR; - - if ( key==nullptr || newvalue==nullptr ) { - error = ERROR; - this->camera.log_error( function, "key|value cannot have NULL" ); - - } else if ( this->configmap.find(key) == this->configmap.end() ) { - error = ERROR; - message.str(""); message << "requested key " << key << " not found in configmap"; - this->camera.log_error( function, message.str() ); - - } else if ( this->configmap[key].value == newvalue ) { - // If no change in value then don't send the command - error = NO_ERROR; - message.str(""); message << "config key " << key << "=" << newvalue << " not written: no change in value"; - logwrite(function, message.str()); + /**************** Archon::Interface::write_parameter ************************/ - } else { - /** - * Format and send the Archon WCONFIG command - * to write the KEY=VALUE pair to controller memory + + /**************** Archon::Interface::get_configmap_value ********************/ + /** + * @fn get_configmap_value + * @brief get the VALUE from configmap for a givenn KEY and assign to a variable + * @param string key_in is the KEY + * @param T& value_out reference to variable to contain the VALUE + * @return ERROR or NO_ERROR + * + * This is a template class function so the &value_out reference can be any type. + * If the key_in KEY is not found then an error message is logged and ERROR is + * returned, otherwise the VALUE associated with key_in is assigned to &value_out, + * and NO_ERROR is returned. + * */ - sscmd << "WCONFIG" - << std::uppercase << std::setfill('0') << std::setw(4) << std::hex - << this->configmap[key].line - << key - << "=" - << newvalue; - message.str(""); message << "sending: archon_cmd(" << sscmd.str() << ")"; - logwrite(function, message.str()); - error = this->archon_cmd((char *)sscmd.str().c_str()); // send the WCONFIG command here - if (error==NO_ERROR) { - this->configmap[key].value = newvalue; // save newvalue in the STL map - changed = true; + template + long Interface::get_configmap_value(std::string key_in, T &value_out) { + std::string function = "Archon::Interface::get_configmap_value"; + std::stringstream message; - } else { - message.str(""); message << "ERROR: config key=value: " << key << "=" << newvalue << " not written"; - logwrite( function, message.str() ); - } + if (this->configmap.find(key_in) != this->configmap.end()) { + std::istringstream(this->configmap[key_in].value) >> value_out; +#ifdef LOGLEVEL_DEBUG + message.str(""); message << "[DEBUG] key=" << key_in << " value=" << value_out << " line=" << this->configmap[key_in].line; + logwrite(function, message.str()); +#endif + return NO_ERROR; + } else { + message.str(""); + message << "requested key: " << key_in << " not found in configmap"; + this->camera.log_error(function, message.str()); + return ERROR; + } } - return error; - } - - long Interface::write_config_key( const char *key, int newvalue, bool &changed ) { - std::stringstream newvaluestr; - newvaluestr << newvalue; - return ( write_config_key(key, newvaluestr.str().c_str(), changed) ); - } - /**************** Archon::Interface::write_config_key ***********************/ - - - /**************** Archon::Interface::write_parameter ************************/ - /** - * @fn write_parameter - * @brief write a parameter to the Archon configuration memory - * @param paramname - * @param newvalue - * @return NO_ERROR or ERROR - * - * After writing a parameter, requires an APPLYALL or LOADPARAMS command - * - */ - long Interface::write_parameter( const char *paramname, const char *newvalue, bool &changed ) { - std::string function = "Archon::Interface::write_parameter"; - std::stringstream message, sscmd; - long error=NO_ERROR; - - #ifdef LOGLEVEL_DEBUG - message.str(""); message << "[DEBUG] paramname=" << paramname << " value=" << newvalue; - logwrite( function, message.str() ); - #endif - if ( paramname==nullptr || newvalue==nullptr ) { - error = ERROR; - this->camera.log_error( function, "paramname|value cannot have NULL" ); + /**************** Archon::Interface::get_configmap_value ********************/ - } else if ( this->parammap.find(paramname) == this->parammap.end() ) { - error = ERROR; - message.str(""); message << "parameter \"" << paramname << "\" not found in parammap"; - this->camera.log_error( function, message.str() ); - } + /**************** Archon::Interface::add_filename_key ***********************/ /** - * If no change in value then don't send the command + * @fn add_filename_key + * @brief adds the current filename to the systemkeys database + * @param none + * @return none + * */ - if ( error==NO_ERROR && this->parammap[paramname].value == newvalue ) { - error = NO_ERROR; - message.str(""); message << "parameter " << paramname << "=" << newvalue << " not written: no change in value"; - logwrite(function, message.str()); - } else if (error==NO_ERROR) { - /** - * Format and send the Archon command WCONFIGxxxxttt...ttt - * which writes the text ttt...ttt to configuration line xxx (hex) - * to controller memory. - */ - sscmd << "WCONFIG" - << std::uppercase << std::setfill('0') << std::setw(4) << std::hex - << this->parammap[paramname].line - << this->parammap[paramname].key - << "=" - << this->parammap[paramname].name - << "=" - << newvalue; - message.str(""); message << "sending archon_cmd(" << sscmd.str() << ")"; - logwrite(function, message.str()); - error=this->archon_cmd((char *)sscmd.str().c_str()); // send the WCONFIG command here - if ( error == NO_ERROR ) { - this->parammap[paramname].value = newvalue; // save newvalue in the STL map - changed = true; - - } else logwrite( function, "ERROR: sending WCONFIG command" ); - } - - return error; - } - - long Interface::write_parameter( const char *paramname, int newvalue, bool &changed ) { - std::stringstream newvaluestr; - newvaluestr << newvalue; - return( write_parameter(paramname, newvaluestr.str().c_str(), changed) ); - } - - long Interface::write_parameter( const char *paramname, const char *newvalue ) { - bool dontcare = false; - return( write_parameter(paramname, newvalue, dontcare) ); - } - - long Interface::write_parameter( const char *paramname, int newvalue ) { - bool dontcare = false; - std::stringstream newvaluestr; - newvaluestr << newvalue; - return( write_parameter(paramname, newvaluestr.str().c_str(), dontcare) ); - } - /**************** Archon::Interface::write_parameter ************************/ - - - /**************** Archon::Interface::get_configmap_value ********************/ - /** - * @fn get_configmap_value - * @brief get the VALUE from configmap for a givenn KEY and assign to a variable - * @param string key_in is the KEY - * @param T& value_out reference to variable to contain the VALUE - * @return ERROR or NO_ERROR - * - * This is a template class function so the &value_out reference can be any type. - * If the key_in KEY is not found then an error message is logged and ERROR is - * returned, otherwise the VALUE associated with key_in is assigned to &value_out, - * and NO_ERROR is returned. - * - */ - template - long Interface::get_configmap_value(std::string key_in, T& value_out) { - std::string function = "Archon::Interface::get_configmap_value"; - std::stringstream message; - - if ( this->configmap.find(key_in) != this->configmap.end() ) { - std::istringstream( this->configmap[key_in].value ) >> value_out; - #ifdef LOGLEVEL_DEBUG - message.str(""); message << "[DEBUG] key=" << key_in << " value=" << value_out << " line=" << this->configmap[key_in].line; - logwrite(function, message.str()); - #endif - return NO_ERROR; - - } else { - message.str(""); - message << "requested key: " << key_in << " not found in configmap"; - this->camera.log_error( function, message.str() ); - return ERROR; - } - } - /**************** Archon::Interface::get_configmap_value ********************/ - - - /**************** Archon::Interface::add_filename_key ***********************/ - /** - * @fn add_filename_key - * @brief adds the current filename to the systemkeys database - * @param none - * @return none - * - */ - void Interface::add_filename_key() { - std::stringstream keystr; - int loc = this->camera_info.fits_name.find_last_of( '/' ); - std::string filename; - filename = this->camera_info.fits_name.substr( loc+1 ); - keystr << "FILENAME=" << filename << "// this filename"; - this->systemkeys.addkey( keystr.str() ); - } - /**************** Archon::Interface::add_filename_key ***********************/ - - - /**************** Archon::Interface::expose *********************************/ - /** - * @fn expose - * @brief initiate an exposure - * @param nseq_in string, if set becomes the number of sequences - * @return ERROR or NO_ERROR - * - * This function does the following before returning successful completion: - * 1) trigger an Archon exposure by setting the EXPOSE parameter = nseq_in - * 2) wait for exposure delay - * 3) wait for readout into Archon frame buffer - * 4) read frame buffer from Archon to host - * 5) write frame to disk - * - * Note that this assumes that the Archon ACF has been programmed to automatically - * read out the detector into the frame buffer after an exposure. - * - */ - long Interface::expose(std::string nseq_in) { - std::string function = "Archon::Interface::expose"; - std::stringstream message; - long error = NO_ERROR; - std::string nseqstr; - int nseq; - - std::string mode = this->camera_info.current_observing_mode; // local copy for convenience - - if ( ! this->modeselected ) { - this->camera.log_error( function, "no mode selected" ); - return ERROR; + void Interface::add_filename_key() { + std::stringstream keystr; + int loc = this->camera_info.fits_name.find_last_of('/'); + std::string filename; + filename = this->camera_info.fits_name.substr(loc + 1); + keystr << "FILENAME=" << filename << "// this filename"; + this->systemkeys.addkey(keystr.str()); } - // When switching from cubeamps=true to cubeamps=false, - // simply reset the mode to the current mode in order to - // reset the image size. - // - // This will need to be revisited once ROI is implemented. // TODO - // - if ( !this->camera.cubeamps() && ( this->lastcubeamps != this->camera.cubeamps() ) ) { - message.str(""); message << "detected change in cubeamps -- resetting camera mode to " << mode; - logwrite( function, message.str() ); - this->set_camera_mode( mode ); - } + /**************** Archon::Interface::add_filename_key ***********************/ - // exposeparam is set by the configuration file - // check to make sure it was set, or else expose won't work - // - if (this->exposeparam.empty()) { - message.str(""); message << "EXPOSE_PARAM not defined in configuration file " << this->config.filename; - this->camera.log_error( function, message.str() ); - return ERROR; - } - // If the exposure time or longexposure mode were never set then read them from the Archon. - // This ensures that, if the client doesn't set these values then the server will have the - // same default values that the ACF has, rather than hope that the ACF programmer picks - // their defaults to match mine. - // - if ( this->camera_info.exposure_time == -1 ) { - logwrite( function, "NOTICE:exptime has not been set--will read from Archon" ); - this->camera.async.enqueue( "NOTICE:exptime has not been set--will read from Archon" ); - - // read the Archon configuration memory - // - std::string etime; - if ( read_parameter( "exptime", etime ) != NO_ERROR ) { logwrite( function, "ERROR: reading \"exptime\" parameter from Archon" ); return ERROR; } - - // Tell the server these values - // - std::string retval; - if ( this->exptime( etime, retval ) != NO_ERROR ) { logwrite( function, "ERROR: setting exptime" ); return ERROR; } - } - if ( this->camera_info.exposure_factor == -1 || - this->camera_info.exposure_unit.empty() ) { - logwrite( function, "NOTICE:longexposure has not been set--will read from Archon" ); - this->camera.async.enqueue( "NOTICE:longexposure has not been set--will read from Archon" ); - - // read the Archon configuration memory - // - std::string lexp; - if ( read_parameter( "longexposure", lexp ) != NO_ERROR ) { logwrite( function, "ERROR: reading \"longexposure\" parameter from Archon" ); return ERROR; } - - // Tell the server these values - // - std::string retval; - if ( this->longexposure( lexp, retval ) != NO_ERROR ) { logwrite( function, "ERROR: setting longexposure" ); return ERROR; } - } + /**************** Archon::Interface::expose *********************************/ + /** + * @fn expose + * @brief initiate an exposure + * @param nseq_in string, if set becomes the number of sequences + * @return ERROR or NO_ERROR + * + * This function does the following before returning successful completion: + * 1) trigger an Archon exposure by setting the EXPOSE parameter = nseq_in + * 2) wait for exposure delay + * 3) wait for readout into Archon frame buffer + * 4) read frame buffer from Archon to host + * 5) write frame to disk + * + * Note that this assumes that the Archon ACF has been programmed to automatically + * read out the detector into the frame buffer after an exposure. + * + */ + long Interface::expose(std::string nseq_in) { + std::string function = "Archon::Interface::expose"; + std::stringstream message; + long error = NO_ERROR; + std::string nseqstr; + int nseq; - // If nseq_in is not supplied then set nseq to 1. - // Add any pre-exposures onto the number of sequences. - // - if ( nseq_in.empty() ) { - nseq = 1 + this->camera_info.num_pre_exposures; - nseqstr = std::to_string( nseq ); - - } else { // sequence argument passed in - try { - nseq = std::stoi( nseq_in ) + this->camera_info.num_pre_exposures; // test that nseq_in is an integer - nseqstr = std::to_string( nseq ); // before trying to use it - - } catch (std::invalid_argument &) { - message.str(""); message << "unable to convert sequences: " << nseq_in << " to integer"; - this->camera.log_error( function, message.str() ); - return ERROR; - - } catch (std::out_of_range &) { - message.str(""); message << "sequences " << nseq_in << " outside integer range"; - this->camera.log_error( function, message.str() ); - return ERROR; - } - } + std::string mode = this->camera_info.current_observing_mode; // local copy for convenience - // Always initialize the extension number because someone could - // set datacube true and then send "expose" without a number. - // - this->camera_info.extension = 0; + if (!this->modeselected) { + this->camera.log_error(function, "no mode selected"); + return ERROR; + } - error = this->get_frame_status(); // TODO is this needed here? + // When switching from cubeamps=true to cubeamps=false, + // simply reset the mode to the current mode in order to + // reset the image size. + // + // This will need to be revisited once ROI is implemented. // TODO + // + if (!this->camera.cubeamps() && (this->lastcubeamps != this->camera.cubeamps())) { + message.str(""); + message << "detected change in cubeamps -- resetting camera mode to " << mode; + logwrite(function, message.str()); + this->set_camera_mode(mode); + } - if (error != NO_ERROR) { - logwrite( function, "ERROR: unable to get frame status" ); - return ERROR; - } - this->lastframe = this->frame.bufframen[this->frame.index]; // save the last frame number acquired (wait_for_readout will need this) + // exposeparam is set by the configuration file + // check to make sure it was set, or else expose won't work + // + if (this->exposeparam.empty()) { + message.str(""); + message << "EXPOSE_PARAM not defined in configuration file " << this->config.filename; + this->camera.log_error(function, message.str()); + return ERROR; + } - // initiate the exposure here - // - error = this->prep_parameter(this->exposeparam, nseqstr); - if (error == NO_ERROR) error = this->load_parameter(this->exposeparam, nseqstr); - if ( error != NO_ERROR ) { - logwrite( function, "ERROR: could not initiate exposure" ); - return error; - } + // If the exposure time or longexposure mode were never set then read them from the Archon. + // This ensures that, if the client doesn't set these values then the server will have the + // same default values that the ACF has, rather than hope that the ACF programmer picks + // their defaults to match mine. + // + if (this->camera_info.exposure_time == -1) { + logwrite(function, "NOTICE:exptime has not been set--will read from Archon"); + this->camera.async.enqueue("NOTICE:exptime has not been set--will read from Archon"); - // get system time and Archon's timer after exposure starts - // start_timer is used to determine when the exposure has ended, in wait_for_exposure() - // - this->camera_info.start_time = get_timestamp(); // current system time formatted as YYYY-MM-DDTHH:MM:SS.sss - if ( this->get_timer(&this->start_timer) != NO_ERROR ) { // Archon internal timer (one tick=10 nsec) - logwrite( function, "ERROR: could not get start time" ); - return ERROR; - } - this->camera.set_fitstime(this->camera_info.start_time); // sets camera.fitstime (YYYYMMDDHHMMSS) used for filename - error=this->camera.get_fitsname(this->camera_info.fits_name); // assemble the FITS filename - if ( error != NO_ERROR ) { - logwrite( function, "ERROR: couldn't validate fits filename" ); - return error; - } + // read the Archon configuration memory + // + std::string etime; + if (read_parameter("exptime", etime) != NO_ERROR) { + logwrite(function, "ERROR: reading \"exptime\" parameter from Archon"); + return ERROR; + } + + // Tell the server these values + // + std::string retval; + if (this->exptime(etime, retval) != NO_ERROR) { + logwrite(function, "ERROR: setting exptime"); + return ERROR; + } + } + if (this->camera_info.exposure_factor == -1 || + this->camera_info.exposure_unit.empty()) { + logwrite(function, "NOTICE:longexposure has not been set--will read from Archon"); + this->camera.async.enqueue("NOTICE:longexposure has not been set--will read from Archon"); - this->add_filename_key(); // add filename to system keys database + // read the Archon configuration memory + // + std::string lexp; + if (read_parameter("longexposure", lexp) != NO_ERROR) { + logwrite(function, "ERROR: reading \"longexposure\" parameter from Archon"); + return ERROR; + } - logwrite(function, "exposure started"); + // Tell the server these values + // + std::string retval; + if (this->longexposure(lexp, retval) != NO_ERROR) { + logwrite(function, "ERROR: setting longexposure"); + return ERROR; + } + } - this->camera_info.systemkeys.keydb = this->systemkeys.keydb; // copy the systemkeys database object into camera_info + // If nseq_in is not supplied then set nseq to 1. + // Add any pre-exposures onto the number of sequences. + // + if (nseq_in.empty()) { + nseq = 1 + this->camera_info.num_pre_exposures; + nseqstr = std::to_string(nseq); + } else { + // sequence argument passed in + try { + nseq = std::stoi(nseq_in) + this->camera_info.num_pre_exposures; // test that nseq_in is an integer + nseqstr = std::to_string(nseq); // before trying to use it + } catch (std::invalid_argument &) { + message.str(""); + message << "unable to convert sequences: " << nseq_in << " to integer"; + this->camera.log_error(function, message.str()); + return ERROR; + } catch (std::out_of_range &) { + message.str(""); + message << "sequences " << nseq_in << " outside integer range"; + this->camera.log_error(function, message.str()); + return ERROR; + } + } - if (this->camera.writekeys_when=="before") this->copy_keydb(); // copy the ACF and userkeys database into camera_info + // Always initialize the extension number because someone could + // set datacube true and then send "expose" without a number. + // + this->camera_info.extension = 0; - // If mode is not "RAW" but RAWENABLE is set then we're going to require a multi-extension data cube, - // one extension for the image and a separate extension for raw data. - // - if ( (mode != "RAW") && (this->modemap[mode].rawenable) ) { - if ( !this->camera.datacube() ) { // if datacube not already set then it must be overridden here - this->camera.async.enqueue( "NOTICE:override datacube true" ); // let everyone know - logwrite( function, "NOTICE:override datacube true" ); - this->camera.datacube(true); - } - this->camera_info.extension = 0; - } + error = this->get_frame_status(); // TODO is this needed here? - // Save the datacube state in camera_info so that the FITS writer can know about it - // - this->camera_info.iscube = this->camera.datacube(); + if (error != NO_ERROR) { + logwrite(function, "ERROR: unable to get frame status"); + return ERROR; + } + this->lastframe = this->frame.bufframen[this->frame.index]; + // save the last frame number acquired (wait_for_readout will need this) - // Open the FITS file now for cubes - // - if ( this->camera.datacube() && !this->camera.cubeamps() ) { - #ifdef LOGLEVEL_DEBUG - logwrite( function, "[DEBUG] opening fits file for multi-exposure sequence data cube" ); - #endif - error = this->fits_file.open_file(this->camera.writekeys_when == "before", this->camera_info ); - if ( error != NO_ERROR ) { - this->camera.log_error( function, "couldn't open fits file" ); - return error; - } - } + // initiate the exposure here + // + error = this->prep_parameter(this->exposeparam, nseqstr); + if (error == NO_ERROR) error = this->load_parameter(this->exposeparam, nseqstr); + if (error != NO_ERROR) { + logwrite(function, "ERROR: could not initiate exposure"); + return error; + } -// //TODO only use camera_info -- don't use fits_info -- is this OK? TO BE CONFIRMED -// this->fits_info = this->camera_info; // copy the camera_info class, to be given to fits writer //TODO + // get system time and Archon's timer after exposure starts + // start_timer is used to determine when the exposure has ended, in wait_for_exposure() + // + this->camera_info.start_time = get_timestamp(); // current system time formatted as YYYY-MM-DDTHH:MM:SS.sss + if (this->get_timer(&this->start_timer) != NO_ERROR) { + // Archon internal timer (one tick=10 nsec) + logwrite(function, "ERROR: could not get start time"); + return ERROR; + } + this->camera.set_fitstime(this->camera_info.start_time); + // sets camera.fitstime (YYYYMMDDHHMMSS) used for filename + error = this->camera.get_fitsname(this->camera_info.fits_name); // assemble the FITS filename + if (error != NO_ERROR) { + logwrite(function, "ERROR: couldn't validate fits filename"); + return error; + } -// this->lastframe = this->frame.bufframen[this->frame.index]; // save the last frame number acquired (wait_for_readout will need this) + this->add_filename_key(); // add filename to system keys database - if (nseq > 1) { - message.str(""); message << "starting sequence of " << nseq << " frames. lastframe=" << this->lastframe; - logwrite(function, message.str()); - } + logwrite(function, "exposure started"); - // If not RAW mode then wait for Archon frame buffer to be ready, - // then read the latest ready frame buffer to the host. If this - // is a squence, then loop over all expected frames. - // - if ( mode != "RAW" ) { // If not raw mode then - int expcount = 0; // counter used only for tracking pre-exposures + this->camera_info.systemkeys.keydb = this->systemkeys.keydb; + // copy the systemkeys database object into camera_info - // - // -- MAIN SEQUENCE LOOP -- - // - while (nseq-- > 0) { + if (this->camera.writekeys_when == "before") this->copy_keydb(); + // copy the ACF and userkeys database into camera_info - // Wait for any pre-exposures, first the exposure delay then the readout, - // but then continue to the next because pre-exposures are not read from - // the Archon's buffer. + // If mode is not "RAW" but RAWENABLE is set then we're going to require a multi-extension data cube, + // one extension for the image and a separate extension for raw data. // - if ( ++expcount <= this->camera_info.num_pre_exposures ) { + if ((mode != "RAW") && (this->modemap[mode].rawenable)) { + if (!this->camera.datacube()) { + // if datacube not already set then it must be overridden here + this->camera.async.enqueue("NOTICE:override datacube true"); // let everyone know + logwrite(function, "NOTICE:override datacube true"); + this->camera.datacube(true); + } + this->camera_info.extension = 0; + } - message.str(""); message << "pre-exposure " << expcount << " of " << this->camera_info.num_pre_exposures; - logwrite( function, message.str() ); + // Save the datacube state in camera_info so that the FITS writer can know about it + // + this->camera_info.iscube = this->camera.datacube(); - if ( this->camera_info.exposure_time != 0 ) { // wait for the exposure delay to complete (if there is one) - error = this->wait_for_exposure(); - if ( error != NO_ERROR ) { - logwrite( function, "ERROR: waiting for pre-exposure" ); - return error; + // Open the FITS file now for cubes + // + if (this->camera.datacube() && !this->camera.cubeamps()) { +#ifdef LOGLEVEL_DEBUG + logwrite( function, "[DEBUG] opening fits file for multi-exposure sequence data cube" ); +#endif + error = this->fits_file.open_file(this->camera.writekeys_when == "before", this->camera_info); + if (error != NO_ERROR) { + this->camera.log_error(function, "couldn't open fits file"); + return error; } - } + } - error = this->wait_for_readout(); // Wait for the readout into frame buffer, - if ( error != NO_ERROR ) { - logwrite( function, "ERROR: waiting for pre-exposure readout" ); - return error; - } + // //TODO only use camera_info -- don't use fits_info -- is this OK? TO BE CONFIRMED + // this->fits_info = this->camera_info; // copy the camera_info class, to be given to fits writer //TODO - continue; + // this->lastframe = this->frame.bufframen[this->frame.index]; // save the last frame number acquired (wait_for_readout will need this) + + if (nseq > 1) { + message.str(""); + message << "starting sequence of " << nseq << " frames. lastframe=" << this->lastframe; + logwrite(function, message.str()); } - // Open a new FITS file for each frame when not using datacubes + // If not RAW mode then wait for Archon frame buffer to be ready, + // then read the latest ready frame buffer to the host. If this + // is a squence, then loop over all expected frames. // - #ifdef LOGLEVEL_DEBUG + if (mode != "RAW") { + // If not raw mode then + int expcount = 0; // counter used only for tracking pre-exposures + + // + // -- MAIN SEQUENCE LOOP -- + // + while (nseq-- > 0) { + // Wait for any pre-exposures, first the exposure delay then the readout, + // but then continue to the next because pre-exposures are not read from + // the Archon's buffer. + // + if (++expcount <= this->camera_info.num_pre_exposures) { + message.str(""); + message << "pre-exposure " << expcount << " of " << this->camera_info.num_pre_exposures; + logwrite(function, message.str()); + + if (this->camera_info.exposure_time != 0) { + // wait for the exposure delay to complete (if there is one) + error = this->wait_for_exposure(); + if (error != NO_ERROR) { + logwrite(function, "ERROR: waiting for pre-exposure"); + return error; + } + } + + error = this->wait_for_readout(); // Wait for the readout into frame buffer, + if (error != NO_ERROR) { + logwrite(function, "ERROR: waiting for pre-exposure readout"); + return error; + } + + continue; + } + + // Open a new FITS file for each frame when not using datacubes + // +#ifdef LOGLEVEL_DEBUG message.str(""); message << "[DEBUG] datacube=" << this->camera.datacube() << " cubeamps=" << this->camera.cubeamps(); logwrite( function, message.str() ); - #endif - if ( !this->camera.datacube() || this->camera.cubeamps() ) { - this->camera_info.start_time = get_timestamp(); // current system time formatted as YYYY-MM-DDTHH:MM:SS.sss - if ( this->get_timer(&this->start_timer) != NO_ERROR ) { // Archon internal timer (one tick=10 nsec) - logwrite( function, "ERROR: could not get start time" ); - return ERROR; - } - this->camera.set_fitstime(this->camera_info.start_time); // sets camera.fitstime (YYYYMMDDHHMMSS) used for filename - error=this->camera.get_fitsname(this->camera_info.fits_name); // Assemble the FITS filename - if ( error != NO_ERROR ) { - logwrite( function, "ERROR: couldn't validate fits filename" ); - return error; - } - this->add_filename_key(); // add filename to system keys database +#endif + if (!this->camera.datacube() || this->camera.cubeamps()) { + this->camera_info.start_time = get_timestamp(); + // current system time formatted as YYYY-MM-DDTHH:MM:SS.sss + if (this->get_timer(&this->start_timer) != NO_ERROR) { + // Archon internal timer (one tick=10 nsec) + logwrite(function, "ERROR: could not get start time"); + return ERROR; + } + this->camera.set_fitstime(this->camera_info.start_time); + // sets camera.fitstime (YYYYMMDDHHMMSS) used for filename + error = this->camera.get_fitsname(this->camera_info.fits_name); // Assemble the FITS filename + if (error != NO_ERROR) { + logwrite(function, "ERROR: couldn't validate fits filename"); + return error; + } + this->add_filename_key(); // add filename to system keys database - #ifdef LOGLEVEL_DEBUG +#ifdef LOGLEVEL_DEBUG logwrite( function, "[DEBUG] reset extension=0 and opening new fits file" ); - #endif - // reset the extension number and open the fits file - // - this->camera_info.extension = 0; - error = this->fits_file.open_file( - this->camera.writekeys_when == "before", this->camera_info ); - if ( error != NO_ERROR ) { - this->camera.log_error( function, "couldn't open fits file" ); - return error; - } - } +#endif + // reset the extension number and open the fits file + // + this->camera_info.extension = 0; + error = this->fits_file.open_file( + this->camera.writekeys_when == "before", this->camera_info); + if (error != NO_ERROR) { + this->camera.log_error(function, "couldn't open fits file"); + return error; + } + } - if ( this->camera_info.exposure_time != 0 ) { // wait for the exposure delay to complete (if there is one) - error = this->wait_for_exposure(); - if ( error != NO_ERROR ) { - logwrite( function, "ERROR: waiting for exposure" ); - return error; - } - } + if (this->camera_info.exposure_time != 0) { + // wait for the exposure delay to complete (if there is one) + error = this->wait_for_exposure(); + if (error != NO_ERROR) { + logwrite(function, "ERROR: waiting for exposure"); + return error; + } + } - if (this->camera.writekeys_when=="after") this->copy_keydb(); // copy the ACF and userkeys database into camera_info + if (this->camera.writekeys_when == "after") this->copy_keydb(); + // copy the ACF and userkeys database into camera_info - error = this->wait_for_readout(); // Wait for the readout into frame buffer, + error = this->wait_for_readout(); // Wait for the readout into frame buffer, - if ( error != NO_ERROR ) { - logwrite( function, "ERROR: waiting for readout" ); - this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info ); - return error; - } + if (error != NO_ERROR) { + logwrite(function, "ERROR: waiting for readout"); + this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info); + return error; + } - error = read_frame(); // then read the frame buffer to host (and write file) when frame ready. - if ( error != NO_ERROR ) { - logwrite( function, "ERROR: reading frame buffer" ); - this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info ); - return error; - } + error = read_frame(); // then read the frame buffer to host (and write file) when frame ready. + if (error != NO_ERROR) { + logwrite(function, "ERROR: reading frame buffer"); + this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info); + return error; + } - // For non-sequence multiple exposures, including cubeamps, close the fits file here - // - if ( !this->camera.datacube() || this->camera.cubeamps() ) { // Error or not, close the file. - #ifdef LOGLEVEL_DEBUG + // For non-sequence multiple exposures, including cubeamps, close the fits file here + // + if (!this->camera.datacube() || this->camera.cubeamps()) { + // Error or not, close the file. +#ifdef LOGLEVEL_DEBUG logwrite( function, "[DEBUG] closing fits file (1)" ); - #endif - this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info ); // close the file when not using datacubes - this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" +#endif + this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info); + // close the file when not using datacubes + this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" - // ASYNC status message on completion of each file - // - message.str(""); message << "FILE:" << this->camera_info.fits_name << " COMPLETE"; - this->camera.async.enqueue( message.str() ); - logwrite( function, message.str() ); - } + // ASYNC status message on completion of each file + // + message.str(""); + message << "FILE:" << this->camera_info.fits_name << " COMPLETE"; + this->camera.async.enqueue(message.str()); + logwrite(function, message.str()); + } - if (error != NO_ERROR) break; // should be impossible but don't try additional sequences if there were errors + if (error != NO_ERROR) break; + // should be impossible but don't try additional sequences if there were errors + } // end of sequence loop, while (nseq-- > 0) + } else if (mode == "RAW") { + error = this->get_frame_status(); // Get the current frame buffer status + if (error != NO_ERROR) { + logwrite(function, "ERROR: unable to get frame status"); + return ERROR; + } + error = this->camera.get_fitsname(this->camera_info.fits_name); // Assemble the FITS filename + if (error != NO_ERROR) { + logwrite(function, "ERROR: couldn't validate fits filename"); + return error; + } + this->add_filename_key(); // add filename to system keys database - } // end of sequence loop, while (nseq-- > 0) + this->camera_info.systemkeys.keydb = this->systemkeys.keydb; + // copy the systemkeys database into camera_info - } else if ( mode == "RAW") { - error = this->get_frame_status(); // Get the current frame buffer status - if (error != NO_ERROR) { - logwrite( function, "ERROR: unable to get frame status" ); - return ERROR; - } - error = this->camera.get_fitsname( this->camera_info.fits_name ); // Assemble the FITS filename - if ( error != NO_ERROR ) { - logwrite( function, "ERROR: couldn't validate fits filename" ); - return error; - } - this->add_filename_key(); // add filename to system keys database + this->copy_keydb(); // copy the ACF and userkeys databases into camera_info - this->camera_info.systemkeys.keydb = this->systemkeys.keydb; // copy the systemkeys database into camera_info + error = this->fits_file.open_file(this->camera.writekeys_when == "before", this->camera_info); + if (error != NO_ERROR) { + this->camera.log_error(function, "couldn't open fits file"); + return error; + } + error = read_frame(); // For raw mode just read immediately + this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info); + this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" + } - this->copy_keydb(); // copy the ACF and userkeys databases into camera_info + // for multi-exposure (non-cubeamp) cubes, close the FITS file now that they've all been written + // + if (this->camera.datacube() && !this->camera.cubeamps()) { +#ifdef LOGLEVEL_DEBUG + logwrite( function, "[DEBUG] closing fits file (2)" ); +#endif + this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info); + this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" - error = this->fits_file.open_file(this->camera.writekeys_when == "before", this->camera_info ); - if ( error != NO_ERROR ) { - this->camera.log_error( function, "couldn't open fits file" ); - return error; - } - error = read_frame(); // For raw mode just read immediately - this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info ); - this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" - } + // ASYNC status message on completion of each file + // + message.str(""); + message << "FILE:" << this->camera_info.fits_name << " " << (error == NO_ERROR ? "COMPLETE" : "ERROR"); + this->camera.async.enqueue(message.str()); + error == NO_ERROR ? logwrite(function, message.str()) : this->camera.log_error(function, message.str()); + } - // for multi-exposure (non-cubeamp) cubes, close the FITS file now that they've all been written - // - if ( this->camera.datacube() && !this->camera.cubeamps() ) { - #ifdef LOGLEVEL_DEBUG - logwrite( function, "[DEBUG] closing fits file (2)" ); - #endif - this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info ); - this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" - - // ASYNC status message on completion of each file - // - message.str(""); message << "FILE:" << this->camera_info.fits_name << " " << ( error==NO_ERROR ? "COMPLETE" : "ERROR" ); - this->camera.async.enqueue( message.str() ); - error == NO_ERROR ? logwrite( function, message.str() ) : this->camera.log_error( function, message.str() ); + // remember the cubeamps setting used for the last completed exposure + // TODO revisit once region-of-interest is implemented + // + this->lastcubeamps = this->camera.cubeamps(); + + return (error); } - // remember the cubeamps setting used for the last completed exposure - // TODO revisit once region-of-interest is implemented - // - this->lastcubeamps = this->camera.cubeamps(); - - return (error); - } - /**************** Archon::Interface::expose *********************************/ - - - /**************** Archon::Interface::hsetup ********************************/ - /** - * @fn hsetup - * @brief setup archon for h2rg - * @param NONE - * @return ERROR or NO_ERROR - * - * NOTE: this assumes LVDS is module 10 - * This function does the following: - * 1) Pulse low on MainResetB - * 2) sets output to Pad B and HIGHOHM - * - */ + /**************** Archon::Interface::expose *********************************/ + + + /**************** Archon::Interface::hsetup ********************************/ + /** + * @fn hsetup + * @brief setup archon for h2rg + * @param NONE + * @return ERROR or NO_ERROR + * + * NOTE: this assumes LVDS is module 10 + * This function does the following: + * 1) Pulse low on MainResetB + * 2) sets output to Pad B and HIGHOHM + * + */ long Interface::hsetup() { std::string function = "Archon::Interface::hsetup"; std::stringstream message; @@ -3920,23 +4153,25 @@ namespace Archon { // H2RG manual says to pull this value low for 100 ns // however, currently it is pulled low for ~1000 usec this->set_parameter("H2RGMainReset", 1); - usleep(1500); // to be sure we are done with the reset + usleep(1500); // to be sure we are done with the reset this->set_parameter("H2RGMainReset", 0); - usleep(1000); // to be sure we are done with the reset + usleep(1000); // to be sure we are done with the reset // Enable output to Pad B and HIGHOHM - error = this->inreg("10 1 16402"); // 0100 000000010010 + error = this->inreg("10 1 16402"); // 0100 000000010010 if (error == NO_ERROR) error = this->inreg("10 0 1"); // send to detector if (error == NO_ERROR) error = this->inreg("10 0 0"); // reset to 0 if (error != NO_ERROR) { - message.str(""); message << "enabling output to Pad B and HIGHOHM"; - this->camera.log_error( function, message.str() ); + message.str(""); + message << "enabling output to Pad B and HIGHOHM"; + this->camera.log_error(function, message.str()); return ERROR; } return (error); } + /**************** Archon::Interface::hsetup *******************************/ /**************** Archon::Interface::hroi ******************************/ @@ -3962,65 +4197,73 @@ namespace Archon { // If geom_in is not supplied then set geometry to full frame. // - if ( !geom_in.empty() ) { // geometry arguments passed in + if (!geom_in.empty()) { + // geometry arguments passed in Tokenize(geom_in, tokens, " "); if (tokens.size() != 4) { - message.str(""); message << "param expected 4 arguments (vstart, vstop, hstart, hstop) but got " << tokens.size(); - this->camera.log_error( function, message.str() ); + message.str(""); + message << "param expected 4 arguments (vstart, vstop, hstart, hstop) but got " << tokens.size(); + this->camera.log_error(function, message.str()); return ERROR; } try { - vstart = std::stoi( tokens[0] ); // test that inputs are integers - vstop = std::stoi( tokens[1] ); - hstart = std::stoi( tokens[2] ); - hstop = std::stoi( tokens[3]); - + vstart = std::stoi(tokens[0]); // test that inputs are integers + vstop = std::stoi(tokens[1]); + hstart = std::stoi(tokens[2]); + hstop = std::stoi(tokens[3]); } catch (std::invalid_argument &) { - message.str(""); message << "unable to convert geometry values: " << geom_in << " to integer"; - this->camera.log_error( function, message.str() ); + message.str(""); + message << "unable to convert geometry values: " << geom_in << " to integer"; + this->camera.log_error(function, message.str()); return ERROR; - } catch (std::out_of_range &) { - message.str(""); message << "geometry values " << geom_in << " outside integer range"; - this->camera.log_error( function, message.str() ); + message.str(""); + message << "geometry values " << geom_in << " outside integer range"; + this->camera.log_error(function, message.str()); return ERROR; } // Validate values are within detector - if ( vstart < 0 || vstop > 2047 || hstart < 0 || hstop > 2047) { - message.str(""); message << "geometry values " << geom_in << " outside pixel range"; - this->camera.log_error( function, message.str()); + if (vstart < 0 || vstop > 2047 || hstart < 0 || hstop > 2047) { + message.str(""); + message << "geometry values " << geom_in << " outside pixel range"; + this->camera.log_error(function, message.str()); return ERROR; } // Validate values have proper ordering if (vstart >= vstop || hstart >= hstop) { - message.str(""); message << "geometry values " << geom_in << " are not correctly ordered"; - this->camera.log_error( function, message.str()); + message.str(""); + message << "geometry values " << geom_in << " are not correctly ordered"; + this->camera.log_error(function, message.str()); return ERROR; } // Set detector registers and record limits // vstart 1000 000000000000 = 32768 - cmd.str("") ; cmd << "10 1 " << (32768 + vstart); + cmd.str(""); + cmd << "10 1 " << (32768 + vstart); error = this->inreg(cmd.str()); if (error == NO_ERROR) error = this->inreg("10 0 1"); // send to detector if (error == NO_ERROR) error = this->inreg("10 0 0"); // reset to 0 if (error == NO_ERROR) this->win_vstart = vstart; // set y lo lim // vstop 1001 000000000000 = 36864 - cmd.str("") ; cmd << "10 1 " << (36864 + vstop); + cmd.str(""); + cmd << "10 1 " << (36864 + vstop); if (error == NO_ERROR) error = this->inreg(cmd.str()); if (error == NO_ERROR) error = this->inreg("10 0 1"); // send to detector if (error == NO_ERROR) error = this->inreg("10 0 0"); // reset to 0 if (error == NO_ERROR) this->win_vstop = vstop; // set y hi lim // hstart 1010 000000000000 = 40960 - cmd.str("") ; cmd << "10 1 " << (40960 + hstart); + cmd.str(""); + cmd << "10 1 " << (40960 + hstart); if (error == NO_ERROR) error = this->inreg(cmd.str()); if (error == NO_ERROR) error = this->inreg("10 0 1"); // send to detector if (error == NO_ERROR) error = this->inreg("10 0 0"); // reset to 0 if (error == NO_ERROR) this->win_hstart = hstart; // set x lo lim // hstop 1011 000000000000 = 45056 - cmd.str("") ; cmd << "10 1 " << (45056 + hstop); + cmd.str(""); + cmd << "10 1 " << (45056 + hstop); if (error == NO_ERROR) error = this->inreg(cmd.str()); if (error == NO_ERROR) error = this->inreg("10 0 1"); // send to detector if (error == NO_ERROR) error = this->inreg("10 0 0"); // reset to 0 @@ -4064,22 +4307,24 @@ namespace Archon { this->camera_info.set_axes(); } - - } // end if geom passed in + } // end if geom passed in // prepare the return value // - message.str(""); message << this->win_vstart << " " << this->win_vstop << " " << this->win_hstart << " " << this->win_hstop; + message.str(""); + message << this->win_vstart << " " << this->win_vstop << " " << this->win_hstart << " " << this->win_hstop; retstring = message.str(); if (error != NO_ERROR) { - message.str(""); message << "setting window geometry to " << retstring; - this->camera.log_error( function, message.str() ); + message.str(""); + message << "setting window geometry to " << retstring; + this->camera.log_error(function, message.str()); return ERROR; } return (error); } + /**************** Archon::Interface::hroi *********************************/ /**************** Archon::Interface::hwindow ******************************/ @@ -4106,11 +4351,12 @@ namespace Archon { // If something is passed then try to use it to set the window state // - if ( !state_in.empty() ) { + if (!state_in.empty()) { try { - std::transform( state_in.begin(), state_in.end(), state_in.begin(), ::toupper ); // make uppercase + std::transform(state_in.begin(), state_in.end(), state_in.begin(), ::toupper); // make uppercase - if ( state_in == "FALSE" || state_in == "0" ) { // leave window mode + if (state_in == "FALSE" || state_in == "0") { + // leave window mode this->is_window = false; // Set detector out of window mode error = this->inreg("10 1 28684"); // 0111 000000001100 @@ -4132,19 +4378,19 @@ namespace Archon { // Now set CDS cmd.str(""); cmd << "PIXELCOUNT " << this->modemap[nowin_mode].geometry.pixelcount; - error = this->cds( cmd.str(), dontcare ); + error = this->cds(cmd.str(), dontcare); cmd.str(""); cmd << "LINECOUNT " << this->modemap[nowin_mode].geometry.linecount; - error = this->cds( cmd.str(), dontcare ); + error = this->cds(cmd.str(), dontcare); // Issue Abort to complete window mode exit cmd.str(""); if (error == NO_ERROR) { cmd << "Abort 1 "; - error = this->set_parameter( cmd.str() ); + error = this->set_parameter(cmd.str()); } - - } else if ( state_in == "TRUE" || state_in == "1" ) { // enter window mode + } else if (state_in == "TRUE" || state_in == "1") { + // enter window mode this->is_window = true; // Set detector into window mode error = this->inreg("10 1 28687"); // 0111 000000001111 @@ -4171,21 +4417,21 @@ namespace Archon { int cols = (this->win_hstop - this->win_hstart) + 1; if (error == NO_ERROR) { cmd << "H2RG_win_columns " << cols; - error = this->set_parameter( cmd.str() ); + error = this->set_parameter(cmd.str()); } cmd.str(""); if (error == NO_ERROR) { cmd << "H2RG_win_rows " << rows; - error = this->set_parameter( cmd.str() ); + error = this->set_parameter(cmd.str()); } // Now set CDS cmd.str(""); cmd << "PIXELCOUNT " << cols; - error = this->cds( cmd.str(), dontcare ); + error = this->cds(cmd.str(), dontcare); cmd.str(""); cmd << "LINECOUNT " << rows; - error = this->cds( cmd.str(), dontcare ); + error = this->cds(cmd.str(), dontcare); // update modemap, in case someone asks again std::string mode = this->camera_info.current_observing_mode; @@ -4201,30 +4447,32 @@ namespace Archon { this->camera_info.detector_pixels[1] = rows; this->camera_info.set_axes(); - } else { - message.str(""); message << "window state " << state_in << " is invalid. Expecting {true,false,0,1}"; - this->camera.log_error( function, message.str() ); + message.str(""); + message << "window state " << state_in << " is invalid. Expecting {true,false,0,1}"; + this->camera.log_error(function, message.str()); return ERROR; } - } catch (...) { - message.str(""); message << "unknown exception converting window state " << state_in << " to uppercase"; - this->camera.log_error( function, message.str() ); + message.str(""); + message << "unknown exception converting window state " << state_in << " to uppercase"; + this->camera.log_error(function, message.str()); return ERROR; } } - state_out = ( this->is_window ? "true" : "false" ); + state_out = (this->is_window ? "true" : "false"); if (error != NO_ERROR) { - message.str(""); message << "setting window state to " << state_in; - this->camera.log_error( function, message.str() ); + message.str(""); + message << "setting window state to " << state_in; + this->camera.log_error(function, message.str()); return ERROR; } return (error); } + /**************** Archon::Interface::hwindow *******************************/ /**************** Archon::Interface::autofetch ******************************/ @@ -4251,11 +4499,12 @@ namespace Archon { // If something is passed then try to use it to set the autofetch state // - if ( !state_in.empty() ) { + if (!state_in.empty()) { try { - std::transform( state_in.begin(), state_in.end(), state_in.begin(), ::toupper ); // make uppercase + std::transform(state_in.begin(), state_in.end(), state_in.begin(), ::toupper); // make uppercase - if ( state_in == "FALSE" || state_in == "0" ) { // leave autofetch mode + if (state_in == "FALSE" || state_in == "0") { + // leave autofetch mode this->is_autofetch = false; // Set detector out of autofetch mode // Now send the AUTOFETCHx command @@ -4272,8 +4521,8 @@ namespace Archon { } logwrite(function, message.str()); - - } else if ( state_in == "TRUE" || state_in == "1" ) { // enter window mode + } else if (state_in == "TRUE" || state_in == "1") { + // enter window mode this->is_autofetch = true; // Set detector into autofetch mode // Now send the AUTOFETCHx command @@ -4291,28 +4540,31 @@ namespace Archon { logwrite(function, message.str()); } else { - message.str(""); message << "autofetch state " << state_in << " is invalid. Expecting {true,false,0,1}"; - this->camera.log_error( function, message.str() ); + message.str(""); + message << "autofetch state " << state_in << " is invalid. Expecting {true,false,0,1}"; + this->camera.log_error(function, message.str()); return ERROR; } - } catch (...) { - message.str(""); message << "unknown exception converting autofetch state " << state_in << " to uppercase"; - this->camera.log_error( function, message.str() ); + message.str(""); + message << "unknown exception converting autofetch state " << state_in << " to uppercase"; + this->camera.log_error(function, message.str()); return ERROR; } } - state_out = ( this->is_autofetch ? "true" : "false" ); + state_out = (this->is_autofetch ? "true" : "false"); if (error != NO_ERROR) { - message.str(""); message << "setting autofetch state to " << state_in; - this->camera.log_error( function, message.str() ); + message.str(""); + message << "setting autofetch state to " << state_in; + this->camera.log_error(function, message.str()); return ERROR; } return (error); } + /**************** Archon::Interface::autofetch *******************************/ /**************** Archon::Interface::hexpose ******************************/ @@ -4340,18 +4592,19 @@ namespace Archon { std::string nseqstr; int nseq, finalframe, nread, currentindex; - std::string mode = this->camera_info.current_observing_mode; // local copy for convenience + std::string mode = this->camera_info.current_observing_mode; // local copy for convenience - if ( ! this->modeselected ) { - this->camera.log_error( function, "no mode selected" ); + if (!this->modeselected) { + this->camera.log_error(function, "no mode selected"); return ERROR; } // exposeparam is set by the configuration file // check to make sure it was set, or else expose won't work if (this->exposeparam.empty()) { - message.str(""); message << "EXPOSE_PARAM not defined in configuration file " << this->config.filename; - this->camera.log_error( function, message.str() ); + message.str(""); + message << "EXPOSE_PARAM not defined in configuration file " << this->config.filename; + this->camera.log_error(function, message.str()); return ERROR; } @@ -4359,54 +4612,66 @@ namespace Archon { // This ensures that, if the client doesn't set these values then the server will have the // same default values that the ACF has, rather than hope that the ACF programmer picks // their defaults to match mine. - if ( this->camera_info.exposure_time == -1 ) { - logwrite( function, "NOTICE:exptime has not been set--will read from Archon" ); - this->camera.async.enqueue( "NOTICE:exptime has not been set--will read from Archon" ); + if (this->camera_info.exposure_time == -1) { + logwrite(function, "NOTICE:exptime has not been set--will read from Archon"); + this->camera.async.enqueue("NOTICE:exptime has not been set--will read from Archon"); // read the Archon configuration memory // std::string etime; - if ( read_parameter( "exptime", etime ) != NO_ERROR ) { logwrite( function, "ERROR: reading \"exptime\" parameter from Archon" ); return ERROR; } + if (read_parameter("exptime", etime) != NO_ERROR) { + logwrite(function, "ERROR: reading \"exptime\" parameter from Archon"); + return ERROR; + } // Tell the server these values // std::string retval; - if ( this->exptime( etime, retval ) != NO_ERROR ) { logwrite( function, "ERROR: setting exptime" ); return ERROR; } + if (this->exptime(etime, retval) != NO_ERROR) { + logwrite(function, "ERROR: setting exptime"); + return ERROR; + } } - if ( this->camera_info.exposure_factor == -1 || - this->camera_info.exposure_unit.empty() ) { - logwrite( function, "NOTICE:longexposure has not been set--will read from Archon" ); - this->camera.async.enqueue( "NOTICE:longexposure has not been set--will read from Archon" ); + if (this->camera_info.exposure_factor == -1 || + this->camera_info.exposure_unit.empty()) { + logwrite(function, "NOTICE:longexposure has not been set--will read from Archon"); + this->camera.async.enqueue("NOTICE:longexposure has not been set--will read from Archon"); // read the Archon configuration memory // std::string lexp; - if ( read_parameter( "longexposure", lexp ) != NO_ERROR ) { logwrite( function, "ERROR: reading \"longexposure\" parameter from Archon" ); return ERROR; } + if (read_parameter("longexposure", lexp) != NO_ERROR) { + logwrite(function, "ERROR: reading \"longexposure\" parameter from Archon"); + return ERROR; + } // Tell the server these values // std::string retval; - if ( this->longexposure( lexp, retval ) != NO_ERROR ) { logwrite( function, "ERROR: setting longexposure" ); return ERROR; } + if (this->longexposure(lexp, retval) != NO_ERROR) { + logwrite(function, "ERROR: setting longexposure"); + return ERROR; + } } // If nseq_in is not supplied then set nseq to 1. - if ( nseq_in.empty() ) { + if (nseq_in.empty()) { nseq = 1; - nseqstr = std::to_string( nseq ); - - } else { // sequence argument passed in + nseqstr = std::to_string(nseq); + } else { + // sequence argument passed in try { - nseq = std::stoi( nseq_in ) + this->camera_info.num_pre_exposures; // test that nseq_in is an integer - nseqstr = std::to_string( nseq ); // before trying to use it - + nseq = std::stoi(nseq_in) + this->camera_info.num_pre_exposures; // test that nseq_in is an integer + nseqstr = std::to_string(nseq); // before trying to use it } catch (std::invalid_argument &) { - message.str(""); message << "unable to convert sequences: " << nseq_in << " to integer"; - this->camera.log_error( function, message.str() ); + message.str(""); + message << "unable to convert sequences: " << nseq_in << " to integer"; + this->camera.log_error(function, message.str()); return ERROR; - } catch (std::out_of_range &) { - message.str(""); message << "sequences " << nseq_in << " outside integer range"; - this->camera.log_error( function, message.str() ); + message.str(""); + message << "sequences " << nseq_in << " outside integer range"; + this->camera.log_error(function, message.str()); return ERROR; } } @@ -4420,7 +4685,7 @@ namespace Archon { currentindex = this->frame.index; if (error != NO_ERROR) { - logwrite( function, "ERROR: unable to get frame status" ); + logwrite(function, "ERROR: unable to get frame status"); return ERROR; } // save the last frame number acquired (wait_for_readout will need this) @@ -4430,7 +4695,9 @@ namespace Archon { finalframe = this->lastframe + nseq; if (nseq > 1) { - message.str(""); message << "starting sequence of " << nseq << " frames. lastframe=" << this->lastframe << " last buffer=" << currentindex+1; + message.str(""); + message << "starting sequence of " << nseq << " frames. lastframe=" << this->lastframe << " last buffer=" << + currentindex + 1; logwrite(function, message.str()); } @@ -4438,7 +4705,7 @@ namespace Archon { this->camera_info.frame_type = Camera::FRAME_IMAGE; error = this->prepare_image_buffer(); if (error == ERROR) { - logwrite( function, "ERROR: unable to allocate an image buffer" ); + logwrite(function, "ERROR: unable to allocate an image buffer"); return ERROR; } @@ -4446,8 +4713,8 @@ namespace Archon { logwrite(function, "exposure starting"); error = this->prep_parameter(this->exposeparam, nseqstr); if (error == NO_ERROR) error = this->load_parameter(this->exposeparam, nseqstr); - if ( error != NO_ERROR ) { - logwrite( function, "ERROR: could not initiate exposure" ); + if (error != NO_ERROR) { + logwrite(function, "ERROR: could not initiate exposure"); return error; } @@ -4466,3454 +4733,3654 @@ namespace Archon { // // -- MAIN SEQUENCE LOOP -- - nread = 0; // Keep track of how many we actually read - int ns = nseq; // Iterate with ns, to preserve original request + nread = 0; // Keep track of how many we actually read + int ns = nseq; // Iterate with ns, to preserve original request while (ns-- > 0 && this->lastframe < finalframe) { - // if ( !this->camera.datacube() || this->camera.cubeamps() ) { // this->camera_info.start_time = get_timestamp(); // current system time formatted as YYYY-MM-DDTHH:MM:SS.sss // if ( this->get_timer(&this->start_timer) != NO_ERROR ) { // Archon internal timer (one tick=10 nsec) // logwrite( function, "ERROR: could not get start time" ); // return ERROR; // } - // this->add_filename_key(); // add filename to system keys database + // this->add_filename_key(); // add filename to system keys database // } // wait for the exposure delay to complete (if there is one) - if ( this->camera_info.exposure_time != 0 ) { + if (this->camera_info.exposure_time != 0) { error = this->wait_for_exposure(); - if ( error != NO_ERROR ) { - logwrite( function, "ERROR: waiting for exposure" ); + if (error != NO_ERROR) { + logwrite(function, "ERROR: waiting for exposure"); return error; } } // Wait for the readout into frame buffer, error = this->hwait_for_readout(); - if ( error != NO_ERROR ) { - logwrite( function, "ERROR: waiting for readout" ); + if (error != NO_ERROR) { + logwrite(function, "ERROR: waiting for readout"); return error; } // then read the frame buffer to host (and write file) when frame ready. error = hread_frame(); - if ( error != NO_ERROR ) { - logwrite( function, "ERROR: reading frame buffer" ); + if (error != NO_ERROR) { + logwrite(function, "ERROR: reading frame buffer"); return error; } // ASYNC status message on completion of each readout nread++; - message.str(""); message << "READOUT COMPLETE (" << nread << " of " << nseq << " read)"; - this->camera.async.enqueue( message.str() ); - logwrite( function, message.str() ); - - if (error != NO_ERROR) break; // should be impossible but don't try additional sequences if there were errors + message.str(""); + message << "READOUT COMPLETE (" << nread << " of " << nseq << " read)"; + this->camera.async.enqueue(message.str()); + logwrite(function, message.str()); - } // end of sequence loop, while (ns-- > 0 && this->lastframe < finalframe) + if (error != NO_ERROR) break; + // should be impossible but don't try additional sequences if there were errors + } // end of sequence loop, while (ns-- > 0 && this->lastframe < finalframe) // ASYNC status message on completion of each sequence - message.str(""); message << "READOUT SEQUENCE " << ( error==NO_ERROR ? "COMPLETE" : "ERROR" ) << " (" << nread << " of " << nseq << " read)"; - this->camera.async.enqueue( message.str() ); - error == NO_ERROR ? logwrite( function, message.str() ) : this->camera.log_error( function, message.str() ); + message.str(""); + message << "READOUT SEQUENCE " << (error == NO_ERROR ? "COMPLETE" : "ERROR") << " (" << nread << " of " << nseq + << " read)"; + this->camera.async.enqueue(message.str()); + error == NO_ERROR ? logwrite(function, message.str()) : this->camera.log_error(function, message.str()); error = get_frame_status(); - if ( error != NO_ERROR ) { - logwrite( function, "ERROR: getting final frame status" ); + if (error != NO_ERROR) { + logwrite(function, "ERROR: getting final frame status"); return error; } - message.str(""); message << "Last frame read " << this->frame.frame << " from buffer " << this->frame.index + 1; - logwrite( function, message.str()); + message.str(""); + message << "Last frame read " << this->frame.frame << " from buffer " << this->frame.index + 1; + logwrite(function, message.str()); return (error); } + /**************** Archon::Interface::hexpose *********************************/ - /**************** Archon::Interface::video *********************************/ + /**************** Archon::Interface::video *********************************/ + /** + * @fn video + * @brief initiate a video exposure + * @param nseq_in string, if set becomes the number of sequences + * @return ERROR or NO_ERROR + * + * This function does the following before returning successful completion: + * 1) trigger an Archon exposure by setting the EXPOSE parameter = nseq_in + * 2) wait for exposure delay + * 3) wait for readout into Archon frame buffer + * 4) read frame buffer from Archon to host + * 5) do NOT write frame to disk + * + * Note that this assumes that the Archon ACF has been programmed to automatically + * read out the detector into the frame buffer after an exposure. + * + */ + + long Interface::video() { + std::string function = "Archon::Interface::video"; + std::stringstream message; + long error = NO_ERROR; + std::string nseqstr; + int nseq; + + std::string mode = this->camera_info.current_observing_mode; // local copy for convenience + + if (!this->modeselected) { + this->camera.log_error(function, "no mode selected"); + return ERROR; + } + + // When switching from cubeamps=true to cubeamps=false, + // simply reset the mode to the current mode in order to + // reset the image size. + // + // This will need to be revisited once ROI is implemented. // TODO + // + if (!this->camera.cubeamps() && (this->lastcubeamps != this->camera.cubeamps())) { + message.str(""); + message << "detected change in cubeamps -- resetting camera mode to " << mode; + logwrite(function, message.str()); + this->set_camera_mode(mode); + } + + // exposeparam is set by the configuration file + // check to make sure it was set, or else expose won't work + // + if (this->exposeparam.empty()) { + message.str(""); + message << "EXPOSE_PARAM not defined in configuration file " << this->config.filename; + this->camera.log_error(function, message.str()); + return ERROR; + } + + // If the exposure time or longexposure mode were never set then read them from the Archon. + // This ensures that, if the client doesn't set these values then the server will have the + // same default values that the ACF has, rather than hope that the ACF programmer picks + // their defaults to match mine. + // + if (this->camera_info.exposure_time == -1) { + logwrite(function, "NOTICE:exptime has not been set--will read from Archon"); + this->camera.async.enqueue("NOTICE:exptime has not been set--will read from Archon"); + + // read the Archon configuration memory + // + std::string etime; + if (read_parameter("exptime", etime) != NO_ERROR) { + logwrite(function, "ERROR: reading \"exptime\" parameter from Archon"); + return ERROR; + } + + // Tell the server these values + // + std::string retval; + if (this->exptime(etime, retval) != NO_ERROR) { + logwrite(function, "ERROR: setting exptime"); + return ERROR; + } + } + if (this->camera_info.exposure_factor == -1 || + this->camera_info.exposure_unit.empty()) { + logwrite(function, "NOTICE:longexposure has not been set--will read from Archon"); + this->camera.async.enqueue("NOTICE:longexposure has not been set--will read from Archon"); + + // read the Archon configuration memory + // + std::string lexp; + if (read_parameter("longexposure", lexp) != NO_ERROR) { + logwrite(function, "ERROR: reading \"longexposure\" parameter from Archon"); + return ERROR; + } + + // Tell the server these values + // + std::string retval; + if (this->longexposure(lexp, retval) != NO_ERROR) { + logwrite(function, "ERROR: setting longexposure"); + return ERROR; + } + } + + // If nseq_in is not supplied then set nseq to 1. + // Add any pre-exposures onto the number of sequences. + // + + nseq = 1 + this->camera_info.num_pre_exposures; + + // Always initialize the extension number because someone could + // set datacube true and then send "expose" without a number. + // + this->camera_info.extension = 0; + + error = this->get_frame_status(); // TODO is this needed here? + + if (error != NO_ERROR) { + logwrite(function, "ERROR: unable to get frame status"); + return ERROR; + } + this->lastframe = this->frame.bufframen[this->frame.index]; + // save the last frame number acquired (wait_for_readout will need this) + + // initiate the exposure here + // + error = this->prep_parameter(this->exposeparam, nseqstr); + if (error == NO_ERROR) error = this->load_parameter(this->exposeparam, nseqstr); + if (error != NO_ERROR) { + logwrite(function, "ERROR: could not initiate exposure"); + return error; + } + + // get system time and Archon's timer after exposure starts + // start_timer is used to determine when the exposure has ended, in wait_for_exposure() + // + this->camera_info.start_time = get_timestamp(); // current system time formatted as YYYY-MM-DDTHH:MM:SS.sss + if (this->get_timer(&this->start_timer) != NO_ERROR) { + // Archon internal timer (one tick=10 nsec) + logwrite(function, "ERROR: could not get start time"); + return ERROR; + } + this->camera.set_fitstime(this->camera_info.start_time); + // sets camera.fitstime (YYYYMMDDHHMMSS) used for filename + error = this->camera.get_fitsname(this->camera_info.fits_name); // assemble the FITS filename + if (error != NO_ERROR) { + logwrite(function, "ERROR: couldn't validate fits filename"); + return error; + } + + this->add_filename_key(); // add filename to system keys database + + logwrite(function, "exposure started"); + + this->camera_info.systemkeys.keydb = this->systemkeys.keydb; + // copy the systemkeys database object into camera_info + + if (this->camera.writekeys_when == "before") this->copy_keydb(); + // copy the ACF and userkeys database into camera_info + + // If mode is not "RAW" but RAWENABLE is set then we're going to require a multi-extension data cube, + // one extension for the image and a separate extension for raw data. + // + if ((mode != "RAW") && (this->modemap[mode].rawenable)) { + if (!this->camera.datacube()) { + // if datacube not already set then it must be overridden here + this->camera.async.enqueue("NOTICE:override datacube true"); // let everyone know + logwrite(function, "NOTICE:override datacube true"); + this->camera.datacube(true); + } + this->camera_info.extension = 0; + } + + // Save the datacube state in camera_info so that the FITS writer can know about it + // + this->camera_info.iscube = this->camera.datacube(); + + // Open the FITS file now for cubes + // + if (this->camera.datacube() && !this->camera.cubeamps()) { +#ifdef LOGLEVEL_DEBUG + logwrite( function, "[DEBUG] opening fits file for multi-exposure sequence data cube" ); +#endif + error = this->fits_file.open_file( + this->camera.writekeys_when == "before", this->camera_info); + if (error != NO_ERROR) { + this->camera.log_error(function, "couldn't open fits file"); + return error; + } + } + + // //TODO only use camera_info -- don't use fits_info -- is this OK? TO BE CONFIRMED + // this->fits_info = this->camera_info; // copy the camera_info class, to be given to fits writer //TODO + + // this->lastframe = this->frame.bufframen[this->frame.index]; // save the last frame number acquired (wait_for_readout will need this) + + if (nseq > 1) { + message.str(""); + message << "starting sequence of " << nseq << " frames. lastframe=" << this->lastframe; + logwrite(function, message.str()); + } + + // If not RAW mode then wait for Archon frame buffer to be ready, + // then read the latest ready frame buffer to the host. If this + // is a squence, then loop over all expected frames. + // + if (mode != "RAW") { + // If not raw mode then + int expcount = 0; // counter used only for tracking pre-exposures + + // + // -- MAIN SEQUENCE LOOP -- + // + while (nseq-- > 0) { + // Wait for any pre-exposures, first the exposure delay then the readout, + // but then continue to the next because pre-exposures are not read from + // the Archon's buffer. + // + if (++expcount <= this->camera_info.num_pre_exposures) { + message.str(""); + message << "pre-exposure " << expcount << " of " << this->camera_info.num_pre_exposures; + logwrite(function, message.str()); + + if (this->camera_info.exposure_time != 0) { + // wait for the exposure delay to complete (if there is one) + error = this->wait_for_exposure(); + if (error != NO_ERROR) { + logwrite(function, "ERROR: waiting for pre-exposure"); + return error; + } + } + + error = this->wait_for_readout(); // Wait for the readout into frame buffer, + if (error != NO_ERROR) { + logwrite(function, "ERROR: waiting for pre-exposure readout"); + return error; + } + + continue; + } + + // Open a new FITS file for each frame when not using datacubes + // +#ifdef LOGLEVEL_DEBUG + message.str(""); message << "[DEBUG] datacube=" << this->camera.datacube() << " cubeamps=" << this->camera.cubeamps(); + logwrite( function, message.str() ); +#endif + if (!this->camera.datacube() || this->camera.cubeamps()) { + this->camera_info.start_time = get_timestamp(); + // current system time formatted as YYYY-MM-DDTHH:MM:SS.sss + if (this->get_timer(&this->start_timer) != NO_ERROR) { + // Archon internal timer (one tick=10 nsec) + logwrite(function, "ERROR: could not get start time"); + return ERROR; + } + this->camera.set_fitstime(this->camera_info.start_time); + // sets camera.fitstime (YYYYMMDDHHMMSS) used for filename + error = this->camera.get_fitsname(this->camera_info.fits_name); // Assemble the FITS filename + if (error != NO_ERROR) { + logwrite(function, "ERROR: couldn't validate fits filename"); + return error; + } + this->add_filename_key(); // add filename to system keys database + +#ifdef LOGLEVEL_DEBUG + logwrite( function, "[DEBUG] reset extension=0 and opening new fits file" ); +#endif + // reset the extension number and open the fits file + // + this->camera_info.extension = 0; + error = this->fits_file.open_file( + this->camera.writekeys_when == "before", this->camera_info); + if (error != NO_ERROR) { + this->camera.log_error(function, "couldn't open fits file"); + return error; + } + } + + if (this->camera_info.exposure_time != 0) { + // wait for the exposure delay to complete (if there is one) + error = this->wait_for_exposure(); + if (error != NO_ERROR) { + logwrite(function, "ERROR: waiting for exposure"); + return error; + } + } + + if (this->camera.writekeys_when == "after") this->copy_keydb(); + // copy the ACF and userkeys database into camera_info + + error = this->wait_for_readout(); // Wait for the readout into frame buffer, + + if (error != NO_ERROR) { + logwrite(function, "ERROR: waiting for readout"); + this->fits_file.close_file( + this->camera.writekeys_when == "after", this->camera_info); + return error; + } + + error = read_frame(); // then read the frame buffer to host (and write file) when frame ready. + if (error != NO_ERROR) { + logwrite(function, "ERROR: reading frame buffer"); + this->fits_file.close_file( + this->camera.writekeys_when == "after", this->camera_info); + return error; + } + + // For non-sequence multiple exposures, including cubeamps, close the fits file here + // + if (!this->camera.datacube() || this->camera.cubeamps()) { + // Error or not, close the file. +#ifdef LOGLEVEL_DEBUG + logwrite( function, "[DEBUG] closing fits file (1)" ); +#endif + this->fits_file.close_file( + this->camera.writekeys_when == "after", + this->camera_info); // close the file when not using datacubes + this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" + + // ASYNC status message on completion of each file + // + message.str(""); + message << "FILE:" << this->camera_info.fits_name << " COMPLETE"; + this->camera.async.enqueue(message.str()); + logwrite(function, message.str()); + } + + if (error != NO_ERROR) break; + // should be impossible but don't try additional sequences if there were errors + } // end of sequence loop, while (nseq-- > 0) + } else if (mode == "RAW") { + error = this->get_frame_status(); // Get the current frame buffer status + if (error != NO_ERROR) { + logwrite(function, "ERROR: unable to get frame status"); + return ERROR; + } + error = this->camera.get_fitsname(this->camera_info.fits_name); // Assemble the FITS filename + if (error != NO_ERROR) { + logwrite(function, "ERROR: couldn't validate fits filename"); + return error; + } + this->add_filename_key(); // add filename to system keys database + + this->camera_info.systemkeys.keydb = this->systemkeys.keydb; + // copy the systemkeys database into camera_info + + this->copy_keydb(); // copy the ACF and userkeys databases into camera_info + + error = this->fits_file.open_file( + this->camera.writekeys_when == "before", this->camera_info); + if (error != NO_ERROR) { + this->camera.log_error(function, "couldn't open fits file"); + return error; + } + error = read_frame(); // For raw mode just read immediately + this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info); + this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" + } + + // for multi-exposure (non-cubeamp) cubes, close the FITS file now that they've all been written + // + if (this->camera.datacube() && !this->camera.cubeamps()) { +#ifdef LOGLEVEL_DEBUG + logwrite( function, "[DEBUG] closing fits file (2)" ); +#endif + this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info); + this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" + + // ASYNC status message on completion of each file + // + message.str(""); + message << "FILE:" << this->camera_info.fits_name << " " << (error == NO_ERROR ? "COMPLETE" : "ERROR"); + this->camera.async.enqueue(message.str()); + error == NO_ERROR ? logwrite(function, message.str()) : this->camera.log_error(function, message.str()); + } + + // remember the cubeamps setting used for the last completed exposure + // TODO revisit once region-of-interest is implemented + // + this->lastcubeamps = this->camera.cubeamps(); + + return (error); + } + + /**************** Archon::Interface::video *********************************/ + + + /**************** Archon::Interface::wait_for_exposure **********************/ /** - * @fn video - * @brief initiate a video exposure - * @param nseq_in string, if set becomes the number of sequences + * @fn wait_for_exposure + * @brief creates a wait until the exposure delay has completed + * @param none * @return ERROR or NO_ERROR * - * This function does the following before returning successful completion: - * 1) trigger an Archon exposure by setting the EXPOSE parameter = nseq_in - * 2) wait for exposure delay - * 3) wait for readout into Archon frame buffer - * 4) read frame buffer from Archon to host - * 5) do NOT write frame to disk + * This is not the actual exposure delay, nor does it accurately time the exposure + * delay. This function merely creates a reasonably accurate wait on the host to + * allow time for the Archon to complete its exposure delay. This is done by using + * the exposure time given to the Archon and by using the Archon's internal timer, + * which is queried here. There is no sense in polling the Archon's timer for the + * entire exposure time, so this function waits internally for about 90% of the + * exposure time, then only starts polling the Archon for the remaining time. * - * Note that this assumes that the Archon ACF has been programmed to automatically - * read out the detector into the frame buffer after an exposure. + * A prediction is made of what the Archon's timer will be at the end, in order + * to provide an estimate of completion. * */ + long Interface::wait_for_exposure() { + std::string function = "Archon::Interface::wait_for_exposure"; + std::stringstream message; + long error = NO_ERROR; - long Interface::video() { - std::string function = "Archon::Interface::video"; - std::stringstream message; - long error = NO_ERROR; - std::string nseqstr; - int nseq; - - std::string mode = this->camera_info.current_observing_mode; // local copy for convenience - - if ( ! this->modeselected ) { - this->camera.log_error( function, "no mode selected" ); - return ERROR; - } + int exposure_timeout_time; // Time to wait for the exposure delay to time out + unsigned long int timer, increment = 0; - // When switching from cubeamps=true to cubeamps=false, - // simply reset the mode to the current mode in order to - // reset the image size. - // - // This will need to be revisited once ROI is implemented. // TODO - // - if ( !this->camera.cubeamps() && ( this->lastcubeamps != this->camera.cubeamps() ) ) { - message.str(""); message << "detected change in cubeamps -- resetting camera mode to " << mode; - logwrite( function, message.str() ); - this->set_camera_mode( mode ); - } + // For long exposures, waittime is 1 second less than the exposure time. + // For short exposures, waittime is an integral number of msec below 90% of the exposure time + // and will be used to keep track of elapsed time, for timeout errors. + // + double waittime; + if (this->is_longexposure) { + waittime = this->camera_info.exposure_time - 1; + } else { + waittime = floor(0.9 * this->camera_info.exposure_time / this->camera_info.exposure_factor); + // in units of this->camera_info.exposure_unit + } - // exposeparam is set by the configuration file - // check to make sure it was set, or else expose won't work - // - if (this->exposeparam.empty()) { - message.str(""); message << "EXPOSE_PARAM not defined in configuration file " << this->config.filename; - this->camera.log_error( function, message.str() ); - return ERROR; - } + // Wait, (don't sleep) for the above waittime. + // This is a period that could be aborted by setting the this->abort flag. //TODO not yet implemented? + // All that is happening here is a wait -- there is no Archon polling going on here. + // + double start_time = get_clock_time(); // get the current clock time from host (in seconds) + double now = start_time; - // If the exposure time or longexposure mode were never set then read them from the Archon. - // This ensures that, if the client doesn't set these values then the server will have the - // same default values that the ACF has, rather than hope that the ACF programmer picks - // their defaults to match mine. - // - if ( this->camera_info.exposure_time == -1 ) { - logwrite( function, "NOTICE:exptime has not been set--will read from Archon" ); - this->camera.async.enqueue( "NOTICE:exptime has not been set--will read from Archon" ); - - // read the Archon configuration memory - // - std::string etime; - if ( read_parameter( "exptime", etime ) != NO_ERROR ) { logwrite( function, "ERROR: reading \"exptime\" parameter from Archon" ); return ERROR; } - - // Tell the server these values - // - std::string retval; - if ( this->exptime( etime, retval ) != NO_ERROR ) { logwrite( function, "ERROR: setting exptime" ); return ERROR; } - } - if ( this->camera_info.exposure_factor == -1 || - this->camera_info.exposure_unit.empty() ) { - logwrite( function, "NOTICE:longexposure has not been set--will read from Archon" ); - this->camera.async.enqueue( "NOTICE:longexposure has not been set--will read from Archon" ); - - // read the Archon configuration memory - // - std::string lexp; - if ( read_parameter( "longexposure", lexp ) != NO_ERROR ) { logwrite( function, "ERROR: reading \"longexposure\" parameter from Archon" ); return ERROR; } - - // Tell the server these values - // - std::string retval; - if ( this->longexposure( lexp, retval ) != NO_ERROR ) { logwrite( function, "ERROR: setting longexposure" ); return ERROR; } - } + // Prediction is the predicted finish_timer, used to compute exposure time progress, + // and is computed as start_time + exposure_time in Archon ticks. + // Each Archon tick is 10 nsec (1e8 sec). Divide by exposure_factor (=1 for sec, =1000 for msec). + // + unsigned long int prediction = this->start_timer + this->camera_info.exposure_time * 1e8 / this->camera_info. + exposure_factor; + + std::cerr << "exposure progress: "; + while ((now - (waittime + start_time) < 0) && !this->abort) { + // sleep 10 msec = 1e6 Archon ticks + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + increment += 1000000; + now = get_clock_time(); + this->camera_info.exposure_progress = (double) increment / (double) (prediction - this->start_timer); + if (this->camera_info.exposure_progress < 0 || this->camera_info.exposure_progress > 1) + this->camera_info.exposure_progress = 1; + std::cerr << std::setw(3) << (int) (this->camera_info.exposure_progress * 100) << "\b\b\b"; + + // ASYNC status message reports the elapsed time in the chosen unit + // + message.str(""); + message << "EXPOSURE:" << (int) (this->camera_info.exposure_time - ( + this->camera_info.exposure_progress * this->camera_info. + exposure_time)); + this->camera.async.enqueue(message.str()); + } - // If nseq_in is not supplied then set nseq to 1. - // Add any pre-exposures onto the number of sequences. - // + if (this->abort) { + std::cerr << "\n"; + logwrite(function, "exposure aborted"); + return NO_ERROR; + } - nseq = 1 + this->camera_info.num_pre_exposures; + // Set the time-out value in ms. If the exposure time is less than a second, set + // the timeout to 1 second. Otherwise, set it to the exposure time plus + // 1 second. + // + if (this->camera_info.exposure_time / this->camera_info.exposure_factor < 1) { + exposure_timeout_time = 1000; //ms + } else { + exposure_timeout_time = (this->camera_info.exposure_time / this->camera_info.exposure_factor) * 1000 + 1000; + } - // Always initialize the extension number because someone could - // set datacube true and then send "expose" without a number. - // - this->camera_info.extension = 0; + // Now start polling the Archon for the last remaining portion of the exposure delay + // + bool done = false; + while (!done && !this->abort) { + // Poll Archon's internal timer + // + if ((error = this->get_timer(&timer)) == ERROR) { + std::cerr << "\n"; + logwrite(function, "ERROR: could not get Archon timer"); + break; + } - error = this->get_frame_status(); // TODO is this needed here? + // update progress + // + this->camera_info.exposure_progress = + (double) (timer - this->start_timer) / (double) (prediction - this->start_timer); + if (this->camera_info.exposure_progress < 0 || this->camera_info.exposure_progress > 1) + this->camera_info.exposure_progress = 1; - if (error != NO_ERROR) { - logwrite( function, "ERROR: unable to get frame status" ); - return ERROR; - } - this->lastframe = this->frame.bufframen[this->frame.index]; // save the last frame number acquired (wait_for_readout will need this) - - // initiate the exposure here - // - error = this->prep_parameter(this->exposeparam, nseqstr); - if (error == NO_ERROR) error = this->load_parameter(this->exposeparam, nseqstr); - if ( error != NO_ERROR ) { - logwrite( function, "ERROR: could not initiate exposure" ); - return error; - } + // ASYNC status message reports the elapsed time in the chosen unit + // + message.str(""); + message << "EXPOSURE:" << (int) (this->camera_info.exposure_time - ( + this->camera_info.exposure_progress * this->camera_info. + exposure_time)); + this->camera.async.enqueue(message.str()); - // get system time and Archon's timer after exposure starts - // start_timer is used to determine when the exposure has ended, in wait_for_exposure() - // - this->camera_info.start_time = get_timestamp(); // current system time formatted as YYYY-MM-DDTHH:MM:SS.sss - if ( this->get_timer(&this->start_timer) != NO_ERROR ) { // Archon internal timer (one tick=10 nsec) - logwrite( function, "ERROR: could not get start time" ); - return ERROR; - } - this->camera.set_fitstime(this->camera_info.start_time); // sets camera.fitstime (YYYYMMDDHHMMSS) used for filename - error=this->camera.get_fitsname(this->camera_info.fits_name); // assemble the FITS filename - if ( error != NO_ERROR ) { - logwrite( function, "ERROR: couldn't validate fits filename" ); - return error; - } + std::cerr << std::setw(3) << (int) (this->camera_info.exposure_progress * 100) << "\b\b\b"; + // send to stderr in case anyone is watching - this->add_filename_key(); // add filename to system keys database + // Archon timer ticks are in 10 nsec (1e-8) so when comparing to exposure_time, + // multiply exposure_time by 1e8/exposure_factor, where exposure_factor=1 or =1000 for exposure_unit sec or msec. + // + if ((timer - this->start_timer) >= (this->camera_info.exposure_time * 1e8 / this->camera_info. + exposure_factor)) { + this->finish_timer = timer; + done = true; + break; + } - logwrite(function, "exposure started"); + // a little pause to slow down the requests to Archon + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + // Added protection against infinite loops, probably will never be invoked + // because an Archon error getting the timer would exit the loop. + // exposure_timeout_time is in msec, and it's a little more than 1 msec to get + // through this loop. If this is decremented each time through then it should + // never hit zero before the exposure is finished, unless there is a serious + // problem. + // + if (--exposure_timeout_time < 0) { + std::cerr << "\n"; + error = ERROR; + this->camera.log_error(function, "timeout waiting for exposure"); + break; + } + } // end while (done == false && this->abort == false) - this->camera_info.systemkeys.keydb = this->systemkeys.keydb; // copy the systemkeys database object into camera_info + std::cerr << "\n"; - if (this->camera.writekeys_when=="before") this->copy_keydb(); // copy the ACF and userkeys database into camera_info + if (this->abort) { + logwrite(function, "exposure aborted"); + } - // If mode is not "RAW" but RAWENABLE is set then we're going to require a multi-extension data cube, - // one extension for the image and a separate extension for raw data. - // - if ( (mode != "RAW") && (this->modemap[mode].rawenable) ) { - if ( !this->camera.datacube() ) { // if datacube not already set then it must be overridden here - this->camera.async.enqueue( "NOTICE:override datacube true" ); // let everyone know - logwrite( function, "NOTICE:override datacube true" ); - this->camera.datacube(true); - } - this->camera_info.extension = 0; - } + return error; + } - // Save the datacube state in camera_info so that the FITS writer can know about it - // - this->camera_info.iscube = this->camera.datacube(); + /**************** Archon::Interface::wait_for_exposure **********************/ - // Open the FITS file now for cubes - // - if ( this->camera.datacube() && !this->camera.cubeamps() ) { - #ifdef LOGLEVEL_DEBUG - logwrite( function, "[DEBUG] opening fits file for multi-exposure sequence data cube" ); - #endif - error = this->fits_file.open_file( - this->camera.writekeys_when == "before", this->camera_info ); - if ( error != NO_ERROR ) { - this->camera.log_error( function, "couldn't open fits file" ); - return error; - } - } - // //TODO only use camera_info -- don't use fits_info -- is this OK? TO BE CONFIRMED - // this->fits_info = this->camera_info; // copy the camera_info class, to be given to fits writer //TODO + /**************** Archon::Interface::wait_for_readout ***********************/ + /** + * @fn wait_for_readout + * @brief creates a wait until the next frame buffer is ready + * @param none + * @return ERROR or NO_ERROR + * + * This function polls the Archon frame status until a new frame is ready. + * + */ + long Interface::wait_for_readout() { + std::string function = "Archon::Interface::wait_for_readout"; + std::stringstream message; + long error = NO_ERROR; + int currentframe = this->lastframe; + int busycount = 0; + bool done = false; - // this->lastframe = this->frame.bufframen[this->frame.index]; // save the last frame number acquired (wait_for_readout will need this) + message.str(""); + message << "waiting for new frame: current frame=" << this->lastframe << " current buffer=" << this->frame.index + + 1; + logwrite(function, message.str()); - if (nseq > 1) { - message.str(""); message << "starting sequence of " << nseq << " frames. lastframe=" << this->lastframe; - logwrite(function, message.str()); - } + // waittime is 10% over the specified readout time + // and will be used to keep track of timeout errors + // + double waittime; + try { + waittime = this->camera.readout_time.at(0) * 1.1; // this is in msec + } catch (std::out_of_range &) { + message.str(""); + message << "readout time for Archon not found from config file"; + this->camera.log_error(function, message.str()); + return ERROR; + } - // If not RAW mode then wait for Archon frame buffer to be ready, - // then read the latest ready frame buffer to the host. If this - // is a squence, then loop over all expected frames. - // - if ( mode != "RAW" ) { // If not raw mode then - int expcount = 0; // counter used only for tracking pre-exposures - - // - // -- MAIN SEQUENCE LOOP -- - // - while (nseq-- > 0) { - - // Wait for any pre-exposures, first the exposure delay then the readout, - // but then continue to the next because pre-exposures are not read from - // the Archon's buffer. - // - if ( ++expcount <= this->camera_info.num_pre_exposures ) { - - message.str(""); message << "pre-exposure " << expcount << " of " << this->camera_info.num_pre_exposures; - logwrite( function, message.str() ); - - if ( this->camera_info.exposure_time != 0 ) { // wait for the exposure delay to complete (if there is one) - error = this->wait_for_exposure(); - if ( error != NO_ERROR ) { - logwrite( function, "ERROR: waiting for pre-exposure" ); - return error; - } - } - - error = this->wait_for_readout(); // Wait for the readout into frame buffer, - if ( error != NO_ERROR ) { - logwrite( function, "ERROR: waiting for pre-exposure readout" ); - return error; - } - - continue; - } + double clock_now = get_clock_time(); // get_clock_time returns seconds + double clock_timeout = clock_now + waittime / 1000.; // must receive frame by this time - // Open a new FITS file for each frame when not using datacubes - // - #ifdef LOGLEVEL_DEBUG - message.str(""); message << "[DEBUG] datacube=" << this->camera.datacube() << " cubeamps=" << this->camera.cubeamps(); - logwrite( function, message.str() ); - #endif - if ( !this->camera.datacube() || this->camera.cubeamps() ) { - this->camera_info.start_time = get_timestamp(); // current system time formatted as YYYY-MM-DDTHH:MM:SS.sss - if ( this->get_timer(&this->start_timer) != NO_ERROR ) { // Archon internal timer (one tick=10 nsec) - logwrite( function, "ERROR: could not get start time" ); - return ERROR; - } - this->camera.set_fitstime(this->camera_info.start_time); // sets camera.fitstime (YYYYMMDDHHMMSS) used for filename - error=this->camera.get_fitsname(this->camera_info.fits_name); // Assemble the FITS filename - if ( error != NO_ERROR ) { - logwrite( function, "ERROR: couldn't validate fits filename" ); - return error; - } - this->add_filename_key(); // add filename to system keys database - - #ifdef LOGLEVEL_DEBUG - logwrite( function, "[DEBUG] reset extension=0 and opening new fits file" ); - #endif - // reset the extension number and open the fits file - // - this->camera_info.extension = 0; - error = this->fits_file.open_file( - this->camera.writekeys_when == "before", this->camera_info ); - if ( error != NO_ERROR ) { - this->camera.log_error( function, "couldn't open fits file" ); - return error; - } - } + // Poll frame status until current frame is not the last frame and the buffer is ready to read. + // The last frame was recorded before the readout was triggered in get_frame(). + // + while (!done && !this->abort) { + if (this->is_longexposure) usleep(10000); // reduces polling frequency + error = this->get_frame_status(); - if ( this->camera_info.exposure_time != 0 ) { // wait for the exposure delay to complete (if there is one) - error = this->wait_for_exposure(); - if ( error != NO_ERROR ) { - logwrite( function, "ERROR: waiting for exposure" ); - return error; - } - } + // If Archon is busy then ignore it, keep trying for up to ~ 3 second + // (300 attempts, ~10000us between attempts) + // + if (error == BUSY) { + if (++busycount > 300) { + done = true; + this->camera.log_error( + function, "received BUSY from Archon too many times trying to get frame status"); + break; + } else continue; + } else busycount = 0; + + if (error == ERROR) { + done = true; + logwrite(function, "ERROR: unable to get frame status"); + break; + } - if (this->camera.writekeys_when=="after") this->copy_keydb(); // copy the ACF and userkeys database into camera_info + // get current frame number and check the status of its buffer + currentframe = this->frame.bufframen[this->frame.index]; + if ((currentframe != this->lastframe) && (this->frame.bufcomplete[this->frame.index] == 1)) { + done = true; + error = NO_ERROR; + break; + } - error = this->wait_for_readout(); // Wait for the readout into frame buffer, + // If the frame isn't done by the predicted time then + // enough time has passed to trigger a timeout error. + // + if (clock_now > clock_timeout) { + done = true; + error = ERROR; + message.str(""); + message << "timeout waiting for new frame exceeded " << waittime << ". lastframe = " << this->lastframe; + this->camera.log_error(function, message.str()); + break; + } + clock_now = get_clock_time(); - if ( error != NO_ERROR ) { - logwrite( function, "ERROR: waiting for readout" ); - this->fits_file.close_file( - this->camera.writekeys_when == "after", this->camera_info ); - return error; - } + // ASYNC status message reports the number of lines read so far, + // which is buflines not from this->frame.index but from the NEXT index... + // + message.str(""); + message << "LINECOUNT:" << this->frame.buflines[this->frame.next_index]; +#ifdef LOGLEVEL_DEBUG + message << " [DEBUG] "; + message << " index=" << this->frame.index << " next_index=" << this->frame.next_index << " | "; + for ( int i=0; i < Archon::nbufs; i++ ) { message << " " << this->frame.buflines[ i ]; } +#endif + this->camera.async.enqueue(message.str()); + } // end while (!done && !this->abort) - error = read_frame(); // then read the frame buffer to host (and write file) when frame ready. - if ( error != NO_ERROR ) { - logwrite( function, "ERROR: reading frame buffer" ); - this->fits_file.close_file( - this->camera.writekeys_when == "after", this->camera_info ); - return error; - } + // After exiting while loop, one update to ensure accurate ASYNC message + // reporting of LINECOUNT. + // + if (error == NO_ERROR) { + error = this->get_frame_status(); + if (error != NO_ERROR) { + logwrite(function, "ERROR: unable to get frame status"); + return error; + } + message.str(""); + message << "LINECOUNT:" << this->frame.buflines[this->frame.index]; + this->camera.async.enqueue(message.str()); + } - // For non-sequence multiple exposures, including cubeamps, close the fits file here - // - if ( !this->camera.datacube() || this->camera.cubeamps() ) { // Error or not, close the file. - #ifdef LOGLEVEL_DEBUG - logwrite( function, "[DEBUG] closing fits file (1)" ); - #endif - this->fits_file.close_file( - this->camera.writekeys_when == "after", this->camera_info ); // close the file when not using datacubes - this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" - - // ASYNC status message on completion of each file - // - message.str(""); message << "FILE:" << this->camera_info.fits_name << " COMPLETE"; - this->camera.async.enqueue( message.str() ); - logwrite( function, message.str() ); - } + if (error != NO_ERROR) { + return error; + } - if (error != NO_ERROR) break; // should be impossible but don't try additional sequences if there were errors - - } // end of sequence loop, while (nseq-- > 0) - - } else if ( mode == "RAW") { - error = this->get_frame_status(); // Get the current frame buffer status - if (error != NO_ERROR) { - logwrite( function, "ERROR: unable to get frame status" ); - return ERROR; - } - error = this->camera.get_fitsname( this->camera_info.fits_name ); // Assemble the FITS filename - if ( error != NO_ERROR ) { - logwrite( function, "ERROR: couldn't validate fits filename" ); - return error; - } - this->add_filename_key(); // add filename to system keys database - - this->camera_info.systemkeys.keydb = this->systemkeys.keydb; // copy the systemkeys database into camera_info - - this->copy_keydb(); // copy the ACF and userkeys databases into camera_info - - error = this->fits_file.open_file( - this->camera.writekeys_when == "before", this->camera_info ); - if ( error != NO_ERROR ) { - this->camera.log_error( function, "couldn't open fits file" ); - return error; - } - error = read_frame(); // For raw mode just read immediately - this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info ); - this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" - } +#ifdef LOGLEVEL_DEBUG + message.str(""); + message << "[DEBUG] lastframe=" << this->lastframe + << " currentframe=" << currentframe + << " bufcomplete=" << this->frame.bufcomplete[this->frame.index]; + logwrite(function, message.str()); +#endif + this->lastframe = currentframe; - // for multi-exposure (non-cubeamp) cubes, close the FITS file now that they've all been written - // - if ( this->camera.datacube() && !this->camera.cubeamps() ) { - #ifdef LOGLEVEL_DEBUG - logwrite( function, "[DEBUG] closing fits file (2)" ); - #endif - this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info ); - this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" - - // ASYNC status message on completion of each file - // - message.str(""); message << "FILE:" << this->camera_info.fits_name << " " << ( error==NO_ERROR ? "COMPLETE" : "ERROR" ); - this->camera.async.enqueue( message.str() ); - error == NO_ERROR ? logwrite( function, message.str() ) : this->camera.log_error( function, message.str() ); - } + // On success, write the value to the log and return + // + if (!this->abort) { + message.str(""); + message << "received currentframe: " << currentframe << " from buffer " << this->frame.index + 1; + logwrite(function, message.str()); + return NO_ERROR; + } else if (this->abort) { + // If the wait was stopped, log a message and return NO_ERROR + logwrite(function, "wait for readout stopped by external signal"); + return NO_ERROR; + } else { + // Throw an error for any other errors (should be impossible) + this->camera.log_error(function, "waiting for readout"); + return error; + } + } - // remember the cubeamps setting used for the last completed exposure - // TODO revisit once region-of-interest is implemented - // - this->lastcubeamps = this->camera.cubeamps(); + /**************** Archon::Interface::wait_for_readout ***********************/ - return (error); - } - /**************** Archon::Interface::video *********************************/ + /**************** Archon::Interface::hwait_for_readout ***********************/ + /** + * @fn hwait_for_readout + * @brief creates a wait until the next frame buffer is ready + * @param none + * @return ERROR or NO_ERROR + * + * This function polls the Archon frame status until a new frame is ready. + * + */ + long Interface::hwait_for_readout() { + std::string function = "Archon::Interface::hwait_for_readout"; + std::stringstream message; + long error = NO_ERROR; + int currentframe = this->lastframe + 1; - /**************** Archon::Interface::wait_for_exposure **********************/ - /** - * @fn wait_for_exposure - * @brief creates a wait until the exposure delay has completed - * @param none - * @return ERROR or NO_ERROR - * - * This is not the actual exposure delay, nor does it accurately time the exposure - * delay. This function merely creates a reasonably accurate wait on the host to - * allow time for the Archon to complete its exposure delay. This is done by using - * the exposure time given to the Archon and by using the Archon's internal timer, - * which is queried here. There is no sense in polling the Archon's timer for the - * entire exposure time, so this function waits internally for about 90% of the - * exposure time, then only starts polling the Archon for the remaining time. - * - * A prediction is made of what the Archon's timer will be at the end, in order - * to provide an estimate of completion. - * - */ - long Interface::wait_for_exposure() { - std::string function = "Archon::Interface::wait_for_exposure"; - std::stringstream message; - long error = NO_ERROR; - - int exposure_timeout_time; // Time to wait for the exposure delay to time out - unsigned long int timer, increment=0; - - // For long exposures, waittime is 1 second less than the exposure time. - // For short exposures, waittime is an integral number of msec below 90% of the exposure time - // and will be used to keep track of elapsed time, for timeout errors. - // - double waittime; - if ( this->is_longexposure ) { - waittime = this->camera_info.exposure_time - 1; + message.str(""); + message << "waiting for new frame: current frame=" << this->lastframe << " current buffer=" << this->frame.index + + 1; + logwrite(function, message.str()); - } else { - waittime = floor( 0.9 * this->camera_info.exposure_time / this->camera_info.exposure_factor ); // in units of this->camera_info.exposure_unit - } + usleep(700); // tune for size of window - // Wait, (don't sleep) for the above waittime. - // This is a period that could be aborted by setting the this->abort flag. //TODO not yet implemented? - // All that is happening here is a wait -- there is no Archon polling going on here. - // - double start_time = get_clock_time(); // get the current clock time from host (in seconds) - double now = start_time; + this->frame.index += 1; - // Prediction is the predicted finish_timer, used to compute exposure time progress, - // and is computed as start_time + exposure_time in Archon ticks. - // Each Archon tick is 10 nsec (1e8 sec). Divide by exposure_factor (=1 for sec, =1000 for msec). - // - unsigned long int prediction = this->start_timer + this->camera_info.exposure_time * 1e8 / this->camera_info.exposure_factor; - - std::cerr << "exposure progress: "; - while ((now - (waittime + start_time) < 0) && !this->abort) { - // sleep 10 msec = 1e6 Archon ticks - std::this_thread::sleep_for( std::chrono::milliseconds( 10 )); - increment += 1000000; - now = get_clock_time(); - this->camera_info.exposure_progress = (double)increment / (double)(prediction - this->start_timer); - if (this->camera_info.exposure_progress < 0 || this->camera_info.exposure_progress > 1) this->camera_info.exposure_progress=1; - std::cerr << std::setw(3) << (int)(this->camera_info.exposure_progress*100) << "\b\b\b"; - - // ASYNC status message reports the elapsed time in the chosen unit - // - message.str(""); message << "EXPOSURE:" << (int)(this->camera_info.exposure_time - (this->camera_info.exposure_progress * this->camera_info.exposure_time)); - this->camera.async.enqueue( message.str() ); - } + // Wrap frame.index + if (this->frame.index >= (int) this->frame.bufframen.size()) { + this->frame.index = 0; + } - if (this->abort) { - std::cerr << "\n"; - logwrite(function, "exposure aborted"); - return NO_ERROR; - } + this->frame.bufframen[this->frame.index] = currentframe; + this->frame.frame = currentframe; - // Set the time-out value in ms. If the exposure time is less than a second, set - // the timeout to 1 second. Otherwise, set it to the exposure time plus - // 1 second. - // - if ( this->camera_info.exposure_time / this->camera_info.exposure_factor < 1 ) { - exposure_timeout_time = 1000; //ms +#ifdef LOGLEVEL_DEBUG + message.str(""); + message << "[DEBUG] lastframe=" << this->lastframe + << " currentframe=" << currentframe + << " bufcomplete=" << this->frame.bufcomplete[this->frame.index]; + logwrite(function, message.str()); +#endif + this->lastframe = currentframe; - } else { - exposure_timeout_time = (this->camera_info.exposure_time / this->camera_info.exposure_factor) * 1000 + 1000; + // On success, write the value to the log and return + // + if (!this->abort) { + message.str(""); + message << "received currentframe: " << currentframe << " from buffer " << this->frame.index + 1; + logwrite(function, message.str()); + return NO_ERROR; + } else if (this->abort) { + // If the wait was stopped, log a message and return NO_ERROR + logwrite(function, "wait for readout stopped by external signal"); + return NO_ERROR; + } else { + // Throw an error for any other errors (should be impossible) + this->camera.log_error(function, "waiting for readout"); + return error; + } } - // Now start polling the Archon for the last remaining portion of the exposure delay - // - bool done = false; - while (!done && !this->abort) { - // Poll Archon's internal timer - // - if ( (error=this->get_timer(&timer)) == ERROR ) { - std::cerr << "\n"; - logwrite( function, "ERROR: could not get Archon timer" ); - break; - } - - // update progress - // - this->camera_info.exposure_progress = (double)(timer - this->start_timer) / (double)(prediction - this->start_timer); - if (this->camera_info.exposure_progress < 0 || this->camera_info.exposure_progress > 1) this->camera_info.exposure_progress=1; - - // ASYNC status message reports the elapsed time in the chosen unit - // - message.str(""); message << "EXPOSURE:" << (int)(this->camera_info.exposure_time - (this->camera_info.exposure_progress * this->camera_info.exposure_time)); - this->camera.async.enqueue( message.str() ); - - std::cerr << std::setw(3) << (int)(this->camera_info.exposure_progress*100) << "\b\b\b"; // send to stderr in case anyone is watching - - // Archon timer ticks are in 10 nsec (1e-8) so when comparing to exposure_time, - // multiply exposure_time by 1e8/exposure_factor, where exposure_factor=1 or =1000 for exposure_unit sec or msec. - // - if ( (timer - this->start_timer) >= ( this->camera_info.exposure_time * 1e8 / this->camera_info.exposure_factor ) ) { - this->finish_timer = timer; - done = true; - break; - } + /**************** Archon::Interface::hwait_for_readout ***********************/ - // a little pause to slow down the requests to Archon - std::this_thread::sleep_for( std::chrono::milliseconds( 1 )); - // Added protection against infinite loops, probably will never be invoked - // because an Archon error getting the timer would exit the loop. - // exposure_timeout_time is in msec, and it's a little more than 1 msec to get - // through this loop. If this is decremented each time through then it should - // never hit zero before the exposure is finished, unless there is a serious - // problem. - // - if (--exposure_timeout_time < 0) { - std::cerr << "\n"; - error = ERROR; - this->camera.log_error( function, "timeout waiting for exposure" ); - break; - } - } // end while (done == false && this->abort == false) - std::cerr << "\n"; + /**************** Archon::Interface::get_parameter **************************/ + /** + * @fn get_parameter + * @brief get parameter using read_parameter() + * @param string + * @return ERROR or NO_ERROR + * + */ + long Interface::get_parameter(std::string parameter, std::string &retstring) { + std::string function = "Archon::Interface::get_parameter"; - if (this->abort) { - logwrite(function, "exposure aborted"); + return this->read_parameter(parameter, retstring); } - return error; - } - /**************** Archon::Interface::wait_for_exposure **********************/ - - - /**************** Archon::Interface::wait_for_readout ***********************/ - /** - * @fn wait_for_readout - * @brief creates a wait until the next frame buffer is ready - * @param none - * @return ERROR or NO_ERROR - * - * This function polls the Archon frame status until a new frame is ready. - * - */ - long Interface::wait_for_readout() { - std::string function = "Archon::Interface::wait_for_readout"; - std::stringstream message; - long error = NO_ERROR; - int currentframe=this->lastframe; - int busycount=0; - bool done = false; + /**************** Archon::Interface::get_parameter **************************/ - message.str(""); - message << "waiting for new frame: current frame=" << this->lastframe << " current buffer=" << this->frame.index+1; - logwrite(function, message.str()); - // waittime is 10% over the specified readout time - // and will be used to keep track of timeout errors - // - double waittime; - try { - waittime = this->camera.readout_time.at(0) * 1.1; // this is in msec - - } catch(std::out_of_range &) { - message.str(""); message << "readout time for Archon not found from config file"; - this->camera.log_error( function, message.str() ); - return ERROR; + /**************** Archon::Interface::set_parameter **************************/ + /** + * @fn set_parameter + * @brief set an Archon parameter + * @param string + * @return ERROR or NO_ERROR + * + * This function calls "prep_parameter()" and "load_parameter()" + * + */ + long Interface::set_parameter(std::string parameter, long value) { + std::stringstream paramstring; + paramstring << parameter << " " << value; + return (set_parameter(paramstring.str())); } - double clock_now = get_clock_time(); // get_clock_time returns seconds - double clock_timeout = clock_now + waittime/1000.; // must receive frame by this time - - // Poll frame status until current frame is not the last frame and the buffer is ready to read. - // The last frame was recorded before the readout was triggered in get_frame(). - // - while (!done && !this->abort) { - - if (this->is_longexposure) usleep( 10000 ); // reduces polling frequency - error = this->get_frame_status(); - - // If Archon is busy then ignore it, keep trying for up to ~ 3 second - // (300 attempts, ~10000us between attempts) - // - if (error == BUSY) { - if ( ++busycount > 300 ) { - done = true; - this->camera.log_error( function, "received BUSY from Archon too many times trying to get frame status" ); - break; + long Interface::set_parameter(std::string parameter) { + std::string function = "Archon::Interface::set_parameter"; + std::stringstream message; + long ret = ERROR; + std::vector tokens; - } else continue; + Tokenize(parameter, tokens, " "); - } else busycount=0; + if (tokens.size() != 2) { + message.str(""); + message << "param expected 2 arguments (paramname and value) but got " << tokens.size(); + this->camera.log_error(function, message.str()); + ret = ERROR; + } else { + ret = this->prep_parameter(tokens[0], tokens[1]); + if (ret == NO_ERROR) ret = this->load_parameter(tokens[0], tokens[1]); + } + return ret; + } - if (error == ERROR) { - done = true; - logwrite( function, "ERROR: unable to get frame status" ); - break; - } + /**************** Archon::Interface::set_parameter **************************/ - // get current frame number and check the status of its buffer - currentframe = this->frame.bufframen[this->frame.index]; - if ( (currentframe != this->lastframe) && (this->frame.bufcomplete[this->frame.index]==1) ) { - done = true; - error = NO_ERROR; - break; - } - // If the frame isn't done by the predicted time then - // enough time has passed to trigger a timeout error. - // - if (clock_now > clock_timeout) { - done = true; - error = ERROR; - message.str(""); message << "timeout waiting for new frame exceeded " << waittime << ". lastframe = " << this->lastframe; - this->camera.log_error( function, message.str() ); - break; - } - clock_now = get_clock_time(); + /**************** Archon::Interface::exptime ********************************/ + /** + * @fn exptime + * @brief set/get the exposure time + * @param string + * @return ERROR or NO_ERROR + * + * This function calls "set_parameter()" and "get_parameter()" using + * the "exptime" parameter (which must already be defined in the ACF file). + * + */ + long Interface::exptime(std::string exptime_in, std::string &retstring) { + std::string function = "Archon::Interface::exptime"; + std::stringstream message; + long ret = NO_ERROR; + int32_t exp_time = -1; - // ASYNC status message reports the number of lines read so far, - // which is buflines not from this->frame.index but from the NEXT index... - // - message.str(""); message << "LINECOUNT:" << this->frame.buflines[ this->frame.next_index ]; - #ifdef LOGLEVEL_DEBUG - message << " [DEBUG] "; - message << " index=" << this->frame.index << " next_index=" << this->frame.next_index << " | "; - for ( int i=0; i < Archon::nbufs; i++ ) { message << " " << this->frame.buflines[ i ]; } - #endif - this->camera.async.enqueue( message.str() ); + if (!exptime_in.empty()) { + // Convert to integer to check the value + // + try { + exp_time = std::stoi(exptime_in); + } catch (std::invalid_argument &) { + message.str(""); + message << "converting exposure time: " << exptime_in << " to integer"; + this->camera.log_error(function, message.str()); + return ERROR; + } catch (std::out_of_range &) { + message.str(""); + message << "requested exposure time: " << exptime_in << " outside integer range"; + this->camera.log_error(function, message.str()); + return ERROR; + } - } // end while (!done && !this->abort) + // Archon allows only 20 bit parameters + // + if (exp_time < 0 || exp_time > 0xFFFFF) { + message.str(""); + message << "requested exposure time: " << exptime_in << " out of range {0:1048575}"; + this->camera.log_error(function, message.str()); + return ERROR; + } - // After exiting while loop, one update to ensure accurate ASYNC message - // reporting of LINECOUNT. - // - if ( error == NO_ERROR ) { - error = this->get_frame_status(); - if ( error != NO_ERROR ) { - logwrite( function, "ERROR: unable to get frame status" ); - return error; - } - message.str(""); message << "LINECOUNT:" << this->frame.buflines[ this->frame.index ]; - this->camera.async.enqueue( message.str() ); - } + // Now that the value is OK set the parameter on the Archon + // + std::stringstream cmd; + cmd << "exptime " << exptime_in; + ret = this->set_parameter(cmd.str()); - if ( error != NO_ERROR ) { - return error; - } + // If parameter was set OK then save the new value + // + if (ret == NO_ERROR) this->camera_info.exposure_time = exp_time; + } - #ifdef LOGLEVEL_DEBUG - message.str(""); - message << "[DEBUG] lastframe=" << this->lastframe - << " currentframe=" << currentframe - << " bufcomplete=" << this->frame.bufcomplete[this->frame.index]; - logwrite(function, message.str()); - #endif - this->lastframe = currentframe; + // add exposure time to system keys db + // + message.str(""); + message << "EXPTIME=" << this->camera_info.exposure_time << " // exposure time in " << ( + this->is_longexposure ? "sec" : "msec"); + this->systemkeys.addkey(message.str()); - // On success, write the value to the log and return - // - if (!this->abort) { - message.str(""); - message << "received currentframe: " << currentframe << " from buffer " << this->frame.index+1; - logwrite(function, message.str()); - return NO_ERROR; + // prepare the return value + // + message.str(""); + message << this->camera_info.exposure_time << (this->is_longexposure ? " sec" : " msec"); + retstring = message.str(); - } else if (this->abort) { - // If the wait was stopped, log a message and return NO_ERROR - logwrite(function, "wait for readout stopped by external signal"); - return NO_ERROR; + message.str(""); + message << "exposure time is " << retstring; + logwrite(function, message.str()); - } else { - // Throw an error for any other errors (should be impossible) - this->camera.log_error( function, "waiting for readout" ); - return error; + return ret; } - } - /**************** Archon::Interface::wait_for_readout ***********************/ + /**************** Archon::Interface::exptime ********************************/ - /**************** Archon::Interface::hwait_for_readout ***********************/ + + /** Camera::Camera::shutter *************************************************/ /** - * @fn hwait_for_readout - * @brief creates a wait until the next frame buffer is ready - * @param none + * @fn shutter + * @brief set or get the shutter enable state + * @param std::string shutter_in + * @param std::string& shutter_out * @return ERROR or NO_ERROR * - * This function polls the Archon frame status until a new frame is ready. - * */ - long Interface::hwait_for_readout() { - std::string function = "Archon::Interface::hwait_for_readout"; + long Interface::shutter(std::string shutter_in, std::string &shutter_out) { + std::string function = "Archon::Interface::shutter"; std::stringstream message; long error = NO_ERROR; - int currentframe=this->lastframe + 1; + int level = 0, force = 0; // trigout level and force for activate - message.str(""); - message << "waiting for new frame: current frame=" << this->lastframe << " current buffer=" << this->frame.index+1; - logwrite(function, message.str()); + if (this->shutenableparam.empty()) { + this->camera.log_error(function, "SHUTENABLE_PARAM is not defined in configuration file"); + return ERROR; + } - usleep( 700 ); // tune for size of window + if (!shutter_in.empty()) { + try { + bool shutten; // shutter enable state read from command + bool ability = false; // are we going to change the ability (enable/disable)? + bool activate = false; // are we going to activate the shutter (open/close)? + std::string activate_str; + bool dontcare; + std::transform(shutter_in.begin(), shutter_in.end(), shutter_in.begin(), ::tolower); // make lowercase + if (shutter_in == "disable") { + ability = true; + shutten = false; + } else if (shutter_in == "enable") { + ability = true; + shutten = true; + } else if (shutter_in == "open") { + activate = true; + force = 1; + level = 1; + activate_str = "open"; + } else if (shutter_in == "close") { + activate = true; + force = 1; + level = 0; + activate_str = "closed"; + } else if (shutter_in == "reset") { + activate = true; + force = 0; + level = 0; + activate_str = ""; + // shutter back to normal operation; + // shutter force level keyword has no context so remove it from the system keys db + // + this->systemkeys.EraseKeys("SHUTFORC"); + } else { + message.str(""); + message << shutter_in << " is invalid. Expecting { enable | disable | open | close | reset }"; + this->camera.log_error(function, message.str()); + error = ERROR; + } + // if changing the ability (enable/disable) then do that now + if (error == NO_ERROR && ability) { + std::stringstream cmd; + cmd << this->shutenableparam << " " << ( + shutten ? this->shutenable_enable : this->shutenable_disable); + error = this->set_parameter(cmd.str()); - this->frame.index += 1; + // If parameter was set OK then save the new value + if (error == NO_ERROR) this->camera_info.shutterenable = shutten; + } + // if changing the activation (open/close/reset) then do that now + if (error == NO_ERROR && activate) { + if (this->configmap.find("TRIGOUTFORCE") != this->configmap.end()) { + error = this->write_config_key("TRIGOUTFORCE", force, dontcare); + } else { + this->camera.log_error(function, "TRIGOUTFORCE not found in configmap"); + error = ERROR; + } - // Wrap frame.index - if (this->frame.index >= (int)this->frame.bufframen.size()) { - this->frame.index = 0; + if (this->configmap.find("TRIGOUTLEVEL") != this->configmap.end()) { + if (error == NO_ERROR) error = this->write_config_key("TRIGOUTLEVEL", level, dontcare); + } else { + this->camera.log_error(function, "TRIGOUTLEVEL not found in configmap"); + error = ERROR; + } + if (error == NO_ERROR) error = this->archon_cmd(APPLYSYSTEM); + if (error == NO_ERROR) { this->camera_info.shutteractivate = activate_str; } + } + } catch (...) { + message.str(""); + message << "converting requested shutter state: " << shutter_in << " to lowercase"; + this->camera.log_error(function, message.str()); + return ERROR; + } } - this->frame.bufframen[ this->frame.index ] = currentframe; - this->frame.frame = currentframe; + // set the return value and report the state now, either setting or getting + // + shutter_out = this->camera_info.shutterenable ? "enabled" : "disabled"; + + // if the shutteractivate string is not empty then use it for the return string, instead + // + if (!this->camera_info.shutteractivate.empty()) shutter_out = this->camera_info.shutteractivate; -#ifdef LOGLEVEL_DEBUG message.str(""); - message << "[DEBUG] lastframe=" << this->lastframe - << " currentframe=" << currentframe - << " bufcomplete=" << this->frame.bufcomplete[this->frame.index]; - logwrite(function, message.str()); -#endif - this->lastframe = currentframe; + message << "shutter is " << shutter_out; + logwrite(function, message.str()); - // On success, write the value to the log and return + // If the shutter was forced open or closed then add that to the system keys db // - if (!this->abort) { + if (force) { message.str(""); - message << "received currentframe: " << currentframe << " from buffer " << this->frame.index+1; - logwrite(function, message.str()); - return NO_ERROR; - - } else if (this->abort) { - // If the wait was stopped, log a message and return NO_ERROR - logwrite(function, "wait for readout stopped by external signal"); - return NO_ERROR; - - } else { - // Throw an error for any other errors (should be impossible) - this->camera.log_error( function, "waiting for readout" ); - return error; + message << "SHUTFORC=" << level << "// shutter force level"; + this->systemkeys.addkey(message.str()); } - } - /**************** Archon::Interface::hwait_for_readout ***********************/ - - - /**************** Archon::Interface::get_parameter **************************/ - /** - * @fn get_parameter - * @brief get parameter using read_parameter() - * @param string - * @return ERROR or NO_ERROR - * - */ - long Interface::get_parameter(std::string parameter, std::string &retstring) { - std::string function = "Archon::Interface::get_parameter"; - - return this->read_parameter(parameter, retstring); - } - /**************** Archon::Interface::get_parameter **************************/ - - - /**************** Archon::Interface::set_parameter **************************/ - /** - * @fn set_parameter - * @brief set an Archon parameter - * @param string - * @return ERROR or NO_ERROR - * - * This function calls "prep_parameter()" and "load_parameter()" - * - */ - long Interface::set_parameter( std::string parameter, long value ) { - std::stringstream paramstring; - paramstring << parameter << " " << value; - return( set_parameter( paramstring.str() ) ); - } - long Interface::set_parameter(std::string parameter) { - std::string function = "Archon::Interface::set_parameter"; - std::stringstream message; - long ret=ERROR; - std::vector tokens; - - Tokenize(parameter, tokens, " "); - - if (tokens.size() != 2) { - message.str(""); message << "param expected 2 arguments (paramname and value) but got " << tokens.size(); - this->camera.log_error( function, message.str() ); - ret=ERROR; - - } else { - ret = this->prep_parameter(tokens[0], tokens[1]); - if (ret == NO_ERROR) ret = this->load_parameter(tokens[0], tokens[1]); - } - return ret; - } - /**************** Archon::Interface::set_parameter **************************/ - - - /**************** Archon::Interface::exptime ********************************/ - /** - * @fn exptime - * @brief set/get the exposure time - * @param string - * @return ERROR or NO_ERROR - * - * This function calls "set_parameter()" and "get_parameter()" using - * the "exptime" parameter (which must already be defined in the ACF file). - * - */ - long Interface::exptime(std::string exptime_in, std::string &retstring) { - std::string function = "Archon::Interface::exptime"; - std::stringstream message; - long ret=NO_ERROR; - int32_t exp_time = -1; - - if ( !exptime_in.empty() ) { - // Convert to integer to check the value - // - try { - exp_time = std::stoi( exptime_in ); - - } catch (std::invalid_argument &) { - message.str(""); message << "converting exposure time: " << exptime_in << " to integer"; - this->camera.log_error( function, message.str() ); - return ERROR; - - } catch (std::out_of_range &) { - message.str(""); message << "requested exposure time: " << exptime_in << " outside integer range"; - this->camera.log_error( function, message.str() ); - return ERROR; - } - // Archon allows only 20 bit parameters - // - if ( exp_time < 0 || exp_time > 0xFFFFF ) { - message.str(""); message << "requested exposure time: " << exptime_in << " out of range {0:1048575}"; - this->camera.log_error( function, message.str() ); - return ERROR; - } - - // Now that the value is OK set the parameter on the Archon - // - std::stringstream cmd; - cmd << "exptime " << exptime_in; - ret = this->set_parameter( cmd.str() ); + // Add the shutter enable keyword to the system keys db + // + message.str(""); + message << "SHUTTEN=" << (this->camera_info.shutterenable ? "T" : "F") << "// shutter was enabled"; + this->systemkeys.addkey(message.str()); - // If parameter was set OK then save the new value - // - if ( ret==NO_ERROR ) this->camera_info.exposure_time = exp_time; + return error; } - // add exposure time to system keys db - // - message.str(""); message << "EXPTIME=" << this->camera_info.exposure_time << " // exposure time in " << ( this->is_longexposure ? "sec" : "msec" ); - this->systemkeys.addkey( message.str() ); + /** Camera::Camera::shutter *************************************************/ - // prepare the return value - // - message.str(""); message << this->camera_info.exposure_time << ( this->is_longexposure ? " sec" : " msec" ); - retstring = message.str(); - message.str(""); message << "exposure time is " << retstring; - logwrite(function, message.str()); + /**************** Archon::Interface::hdrshift *******************************/ + /** + * @fn hdrshift + * @brief set/get number of hdrshift bits + * @param bits_in + * @param &bits_out + * @return ERROR or NO_ERROR + * + * This function sets (or gets) this->n_hdrshift. + * + * In HDR mode (i.e. SAMPLEMODE=1, 32 bits per pixel) this->write_frame() + * will right-shift the Archon data buffer by this->n_hdrshift. + * + */ + long Interface::hdrshift(std::string bits_in, std::string &bits_out) { + std::string function = "Archon::Interface::hdrshift"; + std::stringstream message; + int hdrshift_req = -1; - return ret; - } - /**************** Archon::Interface::exptime ********************************/ - - - /** Camera::Camera::shutter *************************************************/ - /** - * @fn shutter - * @brief set or get the shutter enable state - * @param std::string shutter_in - * @param std::string& shutter_out - * @return ERROR or NO_ERROR - * - */ - long Interface::shutter(std::string shutter_in, std::string& shutter_out) { - std::string function = "Archon::Interface::shutter"; - std::stringstream message; - long error = NO_ERROR; - int level=0, force=0; // trigout level and force for activate - - if ( this->shutenableparam.empty() ) { - this->camera.log_error( function, "SHUTENABLE_PARAM is not defined in configuration file" ); - return ERROR; - } + // If something is passed then try to use it to set the number of hdrshifts + // + if (!bits_in.empty()) { + try { + hdrshift_req = std::stoi(bits_in); + } catch (std::invalid_argument &) { + message.str(""); + message << "converting hdrshift: " << bits_in << " to integer"; + this->camera.log_error(function, message.str()); + return ERROR; + } catch (std::out_of_range &) { + message.str(""); + message << "hdrshift: " << bits_in << " is outside integer range"; + this->camera.log_error(function, message.str()); + return ERROR; + } + } - if ( !shutter_in.empty() ) { - try { - bool shutten; // shutter enable state read from command - bool ability=false; // are we going to change the ability (enable/disable)? - bool activate=false; // are we going to activate the shutter (open/close)? - std::string activate_str; - bool dontcare; - std::transform( shutter_in.begin(), shutter_in.end(), shutter_in.begin(), ::tolower ); // make lowercase - if ( shutter_in == "disable" ) { - ability = true; - shutten = false; - - } else if ( shutter_in == "enable" ) { - ability = true; - shutten = true; - - } else if ( shutter_in == "open" ) { - activate = true; - force = 1; - level = 1; - activate_str = "open"; - - } else if ( shutter_in == "close" ) { - activate = true; - force = 1; - level = 0; - activate_str = "closed"; - - } else if ( shutter_in == "reset" ) { - activate = true; - force = 0; - level = 0; - activate_str = ""; - // shutter back to normal operation; - // shutter force level keyword has no context so remove it from the system keys db - // - this->systemkeys.EraseKeys( "SHUTFORC" ); + if (hdrshift_req < 0 || hdrshift_req > 31) { + this->camera.log_error(function, "hdrshift outside range {0:31}"); + return ERROR; + } else this->n_hdrshift = hdrshift_req; - } else { - message.str(""); message << shutter_in << " is invalid. Expecting { enable | disable | open | close | reset }"; - this->camera.log_error( function, message.str() ); - error = ERROR; - } - // if changing the ability (enable/disable) then do that now - if ( error == NO_ERROR && ability ) { - std::stringstream cmd; - cmd << this->shutenableparam << " " << ( shutten ? this->shutenable_enable : this->shutenable_disable ); - error = this->set_parameter( cmd.str() ); - - // If parameter was set OK then save the new value - if ( error == NO_ERROR ) this->camera_info.shutterenable = shutten; - } - // if changing the activation (open/close/reset) then do that now - if ( error == NO_ERROR && activate ) { - if ( this->configmap.find( "TRIGOUTFORCE" ) != this->configmap.end() ) { - error = this->write_config_key( "TRIGOUTFORCE", force, dontcare ); - - } else { - this->camera.log_error( function, "TRIGOUTFORCE not found in configmap" ); - error = ERROR; - } + // error or not, the number of bits reported now will be whatever was last successfully set + // + bits_out = std::to_string(this->n_hdrshift); - if ( this->configmap.find( "TRIGOUTLEVEL" ) != this->configmap.end() ) { - if ( error == NO_ERROR) error = this->write_config_key( "TRIGOUTLEVEL", level, dontcare ); + // add to system keyword database + // + std::stringstream keystr; + keystr << "HDRSHIFT=" << this->n_hdrshift << "// number of HDR right-shift bits"; + this->systemkeys.addkey(keystr.str()); - } else { - this->camera.log_error( function, "TRIGOUTLEVEL not found in configmap" ); - error = ERROR; - } - if ( error == NO_ERROR ) error = this->archon_cmd( APPLYSYSTEM ); - if ( error == NO_ERROR ) { this->camera_info.shutteractivate = activate_str; } - } - - } catch (...) { - message.str(""); message << "converting requested shutter state: " << shutter_in << " to lowercase"; - this->camera.log_error( function, message.str() ); - return ERROR; - } + return NO_ERROR; } - // set the return value and report the state now, either setting or getting - // - shutter_out = this->camera_info.shutterenable ? "enabled" : "disabled"; - - // if the shutteractivate string is not empty then use it for the return string, instead - // - if ( !this->camera_info.shutteractivate.empty() ) shutter_out = this->camera_info.shutteractivate; - - message.str(""); - message << "shutter is " << shutter_out; - logwrite( function, message.str() ); - - // If the shutter was forced open or closed then add that to the system keys db - // - if ( force ) { - message.str(""); message << "SHUTFORC=" << level << "// shutter force level"; - this->systemkeys.addkey( message.str() ); - } + /**************** Archon::Interface::hdrshift *******************************/ - // Add the shutter enable keyword to the system keys db - // - message.str(""); message << "SHUTTEN=" << ( this->camera_info.shutterenable ? "T" : "F" ) << "// shutter was enabled"; - this->systemkeys.addkey( message.str() ); - - return error; - } - /** Camera::Camera::shutter *************************************************/ - - - /**************** Archon::Interface::hdrshift *******************************/ - /** - * @fn hdrshift - * @brief set/get number of hdrshift bits - * @param bits_in - * @param &bits_out - * @return ERROR or NO_ERROR - * - * This function sets (or gets) this->n_hdrshift. - * - * In HDR mode (i.e. SAMPLEMODE=1, 32 bits per pixel) this->write_frame() - * will right-shift the Archon data buffer by this->n_hdrshift. - * - */ - long Interface::hdrshift(std::string bits_in, std::string &bits_out) { - std::string function = "Archon::Interface::hdrshift"; - std::stringstream message; - int hdrshift_req=-1; - - // If something is passed then try to use it to set the number of hdrshifts - // - if ( !bits_in.empty() ) { - try { - hdrshift_req = std::stoi( bits_in ); - - } catch ( std::invalid_argument & ) { - message.str(""); message << "converting hdrshift: " << bits_in << " to integer"; - this->camera.log_error( function, message.str() ); - return ERROR; - - } catch ( std::out_of_range & ) { - message.str(""); message << "hdrshift: " << bits_in << " is outside integer range"; - this->camera.log_error( function, message.str() ); - return ERROR; - } - } - if ( hdrshift_req < 0 || hdrshift_req > 31 ) { - this->camera.log_error( function, "hdrshift outside range {0:31}" ); - return ERROR; + /**************** Archon::Interface::trigin *********************************/ + /** + * @fn trigin + * @brief setup Archon for external triggering of exposures + * @param state_in + * @return ERROR or NO_ERROR + * + * This function sets three Archon parameters, as defined in + * the configuration file for: + * TRIGIN_EXPOSE_PARAM + * TRIGIN_UNTIMED_PARAM + * TRIGIN_READOUT_PARAM + * + * Calling trigin() should be considered analagous to calling expose() + * with the exception that the actual exposure is expected to be triggered + * in the Archon ACF by a TRIGIN pulse. + * + * This trigin() function then assumes that the exposure was started and + * then waits for the exposure delay, waits for readout, reads the frame + * buffer, then writes the frame to disk. In the case of "untimed" then + * it doesn't wait for the exposure delay but returns immediately, + * requiring a second call to trigin() with the argument "readout" in + * order to enter the sequence of wait for readout, read frame buffer, + * write frame to disk. + * + */ + long Interface::trigin(std::string state_in) { + std::string function = "Archon::Interface::trigin"; + std::string newstate; + std::stringstream message; + std::vector tokens; + long error = NO_ERROR; - } else this->n_hdrshift = hdrshift_req; + std::string mode = this->camera_info.current_observing_mode; // local copy for convenience - // error or not, the number of bits reported now will be whatever was last successfully set - // - bits_out = std::to_string( this->n_hdrshift ); + if (!this->modeselected) { + this->camera.log_error(function, "no mode selected"); + return ERROR; + } - // add to system keyword database - // - std::stringstream keystr; - keystr << "HDRSHIFT=" << this->n_hdrshift << "// number of HDR right-shift bits"; - this->systemkeys.addkey( keystr.str() ); - - return NO_ERROR; - } - /**************** Archon::Interface::hdrshift *******************************/ - - - /**************** Archon::Interface::trigin *********************************/ - /** - * @fn trigin - * @brief setup Archon for external triggering of exposures - * @param state_in - * @return ERROR or NO_ERROR - * - * This function sets three Archon parameters, as defined in - * the configuration file for: - * TRIGIN_EXPOSE_PARAM - * TRIGIN_UNTIMED_PARAM - * TRIGIN_READOUT_PARAM - * - * Calling trigin() should be considered analagous to calling expose() - * with the exception that the actual exposure is expected to be triggered - * in the Archon ACF by a TRIGIN pulse. - * - * This trigin() function then assumes that the exposure was started and - * then waits for the exposure delay, waits for readout, reads the frame - * buffer, then writes the frame to disk. In the case of "untimed" then - * it doesn't wait for the exposure delay but returns immediately, - * requiring a second call to trigin() with the argument "readout" in - * order to enter the sequence of wait for readout, read frame buffer, - * write frame to disk. - * - */ - long Interface::trigin( std::string state_in ) { - std::string function = "Archon::Interface::trigin"; - std::string newstate; - std::stringstream message; - std::vector tokens; - long error = NO_ERROR; - - std::string mode = this->camera_info.current_observing_mode; // local copy for convenience - - if ( ! this->modeselected ) { - this->camera.log_error( function, "no mode selected" ); - return ERROR; - } + std::transform(state_in.begin(), state_in.end(), state_in.begin(), ::tolower); // make lowercase - std::transform( state_in.begin(), state_in.end(), state_in.begin(), ::tolower ); // make lowercase + Tokenize(state_in, tokens, " "); // break into tokens - Tokenize( state_in, tokens, " " ); // break into tokens + // Must have 1 or 2 arguments only + // + if (tokens.empty() || tokens.size() > 2) { + message.str(""); + message << "requested trigin state: " << state_in << + " is invalid. Expected { expose [N] | untimed | readout | disable }"; + this->camera.log_error(function, message.str()); + error = ERROR; + } - // Must have 1 or 2 arguments only - // - if ( tokens.empty() || tokens.size() > 2 ) { - message.str(""); message << "requested trigin state: " << state_in << " is invalid. Expected { expose [N] | untimed | readout | disable }"; - this->camera.log_error( function, message.str() ); - error = ERROR; - } + // If the basic checks have failed then get out now, no point in continuing + // + if (error != NO_ERROR) return error; - // If the basic checks have failed then get out now, no point in continuing - // - if ( error != NO_ERROR ) return error; + // Now look at the arguments, expecting { expose [N] | untimed | readout } + // and set the Archon parameters appropriately. The assumption is that + // after setting these parameters, the Archon will receive a TRIGIN which + // resets the timing core so that the ACF will take appropriate action + // based on the settings of these parameters. + // + try { + // Ultimately, only will only write the parameter *IF* the parameter has been defined in the config file. + // In other words, make no requirement on having all trigger parameters (TRIGIN_EXPOSE_PARAM, + // TRIGIN_UNTIMED_PARAM, TRIGIN_READOUT_PARAM) defined. But if the "trigin expose" command is sent then + // obviously the TRIGIN_EXPOSE_PARAM must be defined, and so on. + // + if (tokens.at(0) == "expose") { + // timed exposures can take an optional argument + // At minimum, expose requires that TRIGIN_EXPOSE_PARAM be defined + // + if (this->trigin_exposeparam.empty()) { + message.str(""); + message << "TRIGIN_EXPOSE_PARAM not defined in configuration file " << this->config.filename; + this->camera.log_error(function, message.str()); + return ERROR; + } + if (tokens.size() == 1) { + // no size given so = 1 + this->trigin_expose = 1; + this->trigin_untimed = this->trigin_untimed_disable; + this->trigin_readout = this->trigin_readout_disable; + } else { + // size given so try to set trigin_expose to requested value + int expnum = std::stoi(tokens.at(1)); + if (expnum < 1) { + this->camera.log_error(function, "number of timed exposures must be greater than zero"); + error = ERROR; + } else { + this->trigin_expose = expnum; + this->trigin_untimed = this->trigin_untimed_disable; + this->trigin_readout = this->trigin_readout_disable; + } + } + } else if (tokens.at(0) == "untimed") { + // untimed exposures + // At minimum, untimed requires that TRIGIN_UNTIMED_PARAM be defined + if (this->trigin_untimedparam.empty()) { + message.str(""); + message << "TRIGIN_UNTIMED_PARAM not defined in configuration file " << this->config.filename; + this->camera.log_error(function, message.str()); + return ERROR; + } + this->trigin_expose = 0; + this->trigin_untimed = this->trigin_untimed_enable; + this->trigin_readout = this->trigin_readout_disable; + } else if (tokens.at(0) == "readout") { + // readout is used at the end of untimed exposures + // At minimum, readout requires that TRIGIN_READOUT_PARAM be defined + if (this->trigin_readoutparam.empty()) { + message.str(""); + message << "TRIGIN_READOUT_PARAM not defined in configuration file " << this->config.filename; + this->camera.log_error(function, message.str()); + return ERROR; + } + this->trigin_expose = 0; + this->trigin_untimed = this->trigin_untimed_disable; + this->trigin_readout = this->trigin_readout_enable; + } else if (tokens.at(0) == "disable") { + // used to disable untimed exposures + this->trigin_expose = 0; + this->trigin_untimed = this->trigin_untimed_disable; + this->trigin_readout = this->trigin_readout_disable; + } else { + message.str(""); + message << "requested trigin state: " << state_in << + " is invalid. Expected { expose [N] | untimed | readout | disable }"; + this->camera.log_error(function, message.str()); + error = ERROR; + } - // Now look at the arguments, expecting { expose [N] | untimed | readout } - // and set the Archon parameters appropriately. The assumption is that - // after setting these parameters, the Archon will receive a TRIGIN which - // resets the timing core so that the ACF will take appropriate action - // based on the settings of these parameters. - // - try { - - // Ultimately, only will only write the parameter *IF* the parameter has been defined in the config file. - // In other words, make no requirement on having all trigger parameters (TRIGIN_EXPOSE_PARAM, - // TRIGIN_UNTIMED_PARAM, TRIGIN_READOUT_PARAM) defined. But if the "trigin expose" command is sent then - // obviously the TRIGIN_EXPOSE_PARAM must be defined, and so on. - // - if ( tokens.at(0) == "expose" ) { // timed exposures can take an optional argument - // At minimum, expose requires that TRIGIN_EXPOSE_PARAM be defined - // - if ( this->trigin_exposeparam.empty() ) { - message.str(""); message << "TRIGIN_EXPOSE_PARAM not defined in configuration file " << this->config.filename; - this->camera.log_error( function, message.str() ); - return ERROR; - } - if ( tokens.size() == 1 ) { // no size given so = 1 - this->trigin_expose = 1; - this->trigin_untimed = this->trigin_untimed_disable; - this->trigin_readout = this->trigin_readout_disable; - - } else { // size given so try to set trigin_expose to requested value - int expnum = std::stoi( tokens.at(1) ); - if ( expnum < 1 ) { - this->camera.log_error( function, "number of timed exposures must be greater than zero" ); + // The first token is taken to be the state name. + // Only after the parameters have been successfully written will we set this->trigin_state = newstate + // + newstate = tokens.at(0); + } catch (std::invalid_argument &) { + message.str(""); + message << "converting number of timed external-trigger exposures: " << state_in << " to integer"; + this->camera.log_error(function, message.str()); error = ERROR; + } catch (std::out_of_range &) { + message.str(""); + message << "parsing trigin string: " << state_in; + this->camera.log_error(function, message.str()); + error = ERROR; + } catch (...) { + message.str(""); + message << "unknown exception parsing trigin string: " << state_in; + this->camera.log_error(function, message.str()); + error = ERROR; + } - } else { - this->trigin_expose = expnum; - this->trigin_untimed = this->trigin_untimed_disable; - this->trigin_readout = this->trigin_readout_disable; - } - } - - } else if ( tokens.at(0) == "untimed" ) { // untimed exposures - // At minimum, untimed requires that TRIGIN_UNTIMED_PARAM be defined - if ( this->trigin_untimedparam.empty() ) { - message.str(""); message << "TRIGIN_UNTIMED_PARAM not defined in configuration file " << this->config.filename; - this->camera.log_error( function, message.str() ); - return ERROR; - } - this->trigin_expose = 0; - this->trigin_untimed = this->trigin_untimed_enable; - this->trigin_readout = this->trigin_readout_disable; - - } else if ( tokens.at(0) == "readout" ) { // readout is used at the end of untimed exposures - // At minimum, readout requires that TRIGIN_READOUT_PARAM be defined - if ( this->trigin_readoutparam.empty() ) { - message.str(""); message << "TRIGIN_READOUT_PARAM not defined in configuration file " << this->config.filename; - this->camera.log_error( function, message.str() ); - return ERROR; - } - this->trigin_expose = 0; - this->trigin_untimed = this->trigin_untimed_disable; - this->trigin_readout = this->trigin_readout_enable; - - } else if ( tokens.at(0) == "disable" ) { // used to disable untimed exposures - this->trigin_expose = 0; - this->trigin_untimed = this->trigin_untimed_disable; - this->trigin_readout = this->trigin_readout_disable; - - } else { - message.str(""); message << "requested trigin state: " << state_in << " is invalid. Expected { expose [N] | untimed | readout | disable }"; - this->camera.log_error( function, message.str() ); - error = ERROR; - } - - // The first token is taken to be the state name. - // Only after the parameters have been successfully written will we set this->trigin_state = newstate - // - newstate = tokens.at(0); - } catch ( std::invalid_argument & ) { - message.str(""); message << "converting number of timed external-trigger exposures: " << state_in << " to integer"; - this->camera.log_error( function, message.str() ); - error = ERROR; - - } catch ( std::out_of_range & ) { - message.str(""); message << "parsing trigin string: " << state_in; - this->camera.log_error( function, message.str() ); - error = ERROR; - - } catch (...) { - message.str(""); message << "unknown exception parsing trigin string: " << state_in; - this->camera.log_error( function, message.str() ); - error = ERROR; - } - - // Get out now if any internal errors from above - // - if ( error != NO_ERROR ) return error; + // Get out now if any internal errors from above + // + if (error != NO_ERROR) return error; - // Build up a vector of the parameters and their values that have been defined. - // - typedef struct { - std::string param; - int value; - } triginfo_t; + // Build up a vector of the parameters and their values that have been defined. + // + typedef struct { + std::string param; + int value; + } triginfo_t; - std::vector trigger; + std::vector trigger; - // If the parameter has been defined then push that parameter and its value into the vector. - // Check this for each of the three trigin parameters. - // + // If the parameter has been defined then push that parameter and its value into the vector. + // Check this for each of the three trigin parameters. + // - if ( !this->trigin_exposeparam.empty() ) { // TRIGIN_EXPOSE - triginfo_t expose; - expose.param = this->trigin_exposeparam; - expose.value = this->trigin_expose; - trigger.push_back( expose ); - #ifdef LOGLEVEL_DEBUG + if (!this->trigin_exposeparam.empty()) { + // TRIGIN_EXPOSE + triginfo_t expose; + expose.param = this->trigin_exposeparam; + expose.value = this->trigin_expose; + trigger.push_back(expose); +#ifdef LOGLEVEL_DEBUG message.str(""); message << "[DEBUG] EXPOSE param=" << this->trigin_exposeparam << " value=" << this->trigin_expose; logwrite( function, message.str() ); - #endif - } +#endif + } - if ( !this->trigin_untimedparam.empty() ) { // TRIGIN_UNTIMED - triginfo_t untimed; - untimed.param = this->trigin_untimedparam; - untimed.value = this->trigin_untimed; - trigger.push_back( untimed ); - #ifdef LOGLEVEL_DEBUG + if (!this->trigin_untimedparam.empty()) { + // TRIGIN_UNTIMED + triginfo_t untimed; + untimed.param = this->trigin_untimedparam; + untimed.value = this->trigin_untimed; + trigger.push_back(untimed); +#ifdef LOGLEVEL_DEBUG message.str(""); message << "[DEBUG] UNTIMED param=" << this->trigin_untimedparam << " value=" << this->trigin_untimed; logwrite( function, message.str() ); - #endif - } +#endif + } - if ( !this->trigin_readoutparam.empty() ) { // TRIGIN_READOUT - triginfo_t readout; - readout.param = this->trigin_readoutparam; - readout.value = this->trigin_readout; - trigger.push_back( readout ); - #ifdef LOGLEVEL_DEBUG + if (!this->trigin_readoutparam.empty()) { + // TRIGIN_READOUT + triginfo_t readout; + readout.param = this->trigin_readoutparam; + readout.value = this->trigin_readout; + trigger.push_back(readout); +#ifdef LOGLEVEL_DEBUG message.str(""); message << "[DEBUG] READOUT param=" << this->trigin_readoutparam << " value=" << this->trigin_readout; logwrite( function, message.str() ); - #endif - } +#endif + } - // Now write those parameters to the Archon - // - for ( const auto &trig : trigger ) { - if ( error == NO_ERROR ) error = this->set_parameter( trig.param, trig.value ); - } + // Now write those parameters to the Archon + // + for (const auto &trig: trigger) { + if (error == NO_ERROR) error = this->set_parameter(trig.param, trig.value); + } - // Now that the parameters have been successfully written we are in the new state - // - if ( error == NO_ERROR ) this->trigin_state = newstate; + // Now that the parameters have been successfully written we are in the new state + // + if (error == NO_ERROR) this->trigin_state = newstate; - // Save the last frame number acquired -- wait_for_readout() will need this later - // - if ( error == NO_ERROR) error = this->get_frame_status(); - if ( error == NO_ERROR) this->lastframe = this->frame.bufframen[this->frame.index]; + // Save the last frame number acquired -- wait_for_readout() will need this later + // + if (error == NO_ERROR) error = this->get_frame_status(); + if (error == NO_ERROR) this->lastframe = this->frame.bufframen[this->frame.index]; - #ifdef LOGLEVEL_DEBUG +#ifdef LOGLEVEL_DEBUG message.str(""); message << "[DEBUG] lastframe=" << this->lastframe; logwrite( function, message.str() ); - #endif - - // For "untimed" prepare camera_info and open the FITS file - // - if ( error == NO_ERROR && this->trigin_state == "untimed" ) { - this->camera_info.start_time = get_timestamp(); // current system time formatted as YYYY-MM-DDTHH:MM:SS.sss - this->get_timer(&this->start_timer); // Archon internal timer (one tick=10 nsec) - this->camera.set_fitstime(this->camera_info.start_time); // sets camera.fitstime (YYYYMMDDHHMMSS) used for filename - error=this->camera.get_fitsname(this->camera_info.fits_name); // Assemble the FITS filename - this->add_filename_key(); // add filename to system keys database - this->camera_info.systemkeys.keydb = this->systemkeys.keydb; // copy the systemkeys database into camera_info - if (this->camera.writekeys_when=="before") this->copy_keydb(); // copy the ACF and userkeys database into camera_info - error = this->fits_file.open_file(this->camera.writekeys_when == "before", this->camera_info ); - logwrite( function, "untimed exposure trigger enabled" ); - return error; - } - - // For "disable" there's nothing more to do right now (except close any open FITS file) - // - if ( error == NO_ERROR && this->trigin_state == "disable" ) { - if ( this->fits_file.isopen() ) { // "untimed" could have opened a FITS file - this->fits_file.close_file( false, this->camera_info ); // close the FITS file container - std::remove( this->camera_info.fits_name.c_str() ); // remove the (empty) file it created - } - logwrite( function, "untimed exposure trigger disabled" ); - return error; - } - - // Don't continue if Archon parameters were not written properly - // - if ( error != NO_ERROR ) return error; - - // ------------------------------------------------------------------------ - // Now the rest is somewhat like the "expose" function... - // ------------------------------------------------------------------------ - - // Always initialize the extension number because someone could - // set datacube true and then send "expose" without a number. - // - this->camera_info.extension = 0; - - // ------------------------------------------------------------------------ - // "readout" - // The assumption here is that the exposure has started and this command is - // sent to prepare for the end of exposure. The exposure is nearly complete. - // The next TRIGIN will end the exposure and begin the readout, so now we - // begin waiting for the new frame to appear in the Archon frame buffer. - // ------------------------------------------------------------------------ - // - if ( this->trigin_state == "readout" ) { - - // Save the datacube state in camera_info so that the FITS writer can know about it - // - this->camera_info.iscube = this->camera.datacube(); +#endif - this->camera_info.systemkeys.keydb = this->systemkeys.keydb; // copy systemkeys database object into camera_info + // For "untimed" prepare camera_info and open the FITS file + // + if (error == NO_ERROR && this->trigin_state == "untimed") { + this->camera_info.start_time = get_timestamp(); // current system time formatted as YYYY-MM-DDTHH:MM:SS.sss + this->get_timer(&this->start_timer); // Archon internal timer (one tick=10 nsec) + this->camera.set_fitstime(this->camera_info.start_time); + // sets camera.fitstime (YYYYMMDDHHMMSS) used for filename + error = this->camera.get_fitsname(this->camera_info.fits_name); // Assemble the FITS filename + this->add_filename_key(); // add filename to system keys database + this->camera_info.systemkeys.keydb = this->systemkeys.keydb; + // copy the systemkeys database into camera_info + if (this->camera.writekeys_when == "before") this->copy_keydb(); + // copy the ACF and userkeys database into camera_info + error = this->fits_file.open_file(this->camera.writekeys_when == "before", this->camera_info); + logwrite(function, "untimed exposure trigger enabled"); + return error; + } - if (this->camera.writekeys_when=="after") this->copy_keydb(); // copy the ACF and userkeys database into camera_info + // For "disable" there's nothing more to do right now (except close any open FITS file) + // + if (error == NO_ERROR && this->trigin_state == "disable") { + if (this->fits_file.isopen()) { + // "untimed" could have opened a FITS file + this->fits_file.close_file(false, this->camera_info); // close the FITS file container + std::remove(this->camera_info.fits_name.c_str()); // remove the (empty) file it created + } + logwrite(function, "untimed exposure trigger disabled"); + return error; + } - if (error==NO_ERROR) error = this->wait_for_readout(); // Wait for the readout into frame buffer, - if (error==NO_ERROR) error = read_frame(); // then read the frame buffer to host (and write file) when frame ready. + // Don't continue if Archon parameters were not written properly + // + if (error != NO_ERROR) return error; - this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info ); // close the file when not using datacubes - this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" + // ------------------------------------------------------------------------ + // Now the rest is somewhat like the "expose" function... + // ------------------------------------------------------------------------ - // ASYNC status message on completion of each file - // - message.str(""); message << "FILE:" << this->camera_info.fits_name << " " << ( error==NO_ERROR ? "COMPLETE" : "ERROR" ); - this->camera.async.enqueue( message.str() ); - error == NO_ERROR ? logwrite( function, message.str() ) : this->camera.log_error( function, message.str() ); + // Always initialize the extension number because someone could + // set datacube true and then send "expose" without a number. + // + this->camera_info.extension = 0; - } else if ( this->trigin_state == "expose" ) { // ------------------------------------------------------------------------ - // "expose" - // The assumption here is that the exposure hasn't started yet. The next - // TRIGIN will initiate the complete exposure sequence in the Archon, - // exposure delay followed by sensor readout, so now we are waiting for that - // sequence to start. + // "readout" + // The assumption here is that the exposure has started and this command is + // sent to prepare for the end of exposure. The exposure is nearly complete. + // The next TRIGIN will end the exposure and begin the readout, so now we + // begin waiting for the new frame to appear in the Archon frame buffer. // ------------------------------------------------------------------------ // + if (this->trigin_state == "readout") { + // Save the datacube state in camera_info so that the FITS writer can know about it + // + this->camera_info.iscube = this->camera.datacube(); - // get system time and Archon's timer after exposure starts - // start_timer is used to determine when the exposure has ended, in wait_for_exposure() - // - this->camera_info.start_time = get_timestamp(); // current system time formatted as YYYY-MM-DDTHH:MM:SS.sss - error = this->get_timer(&this->start_timer); // Archon internal timer (one tick=10 nsec) - - this->camera.set_fitstime(this->camera_info.start_time); // sets camera.fitstime (YYYYMMDDHHMMSS) used for filename - error=this->camera.get_fitsname(this->camera_info.fits_name); // assemble the FITS filename - this->add_filename_key(); // add filename to system keys database - - this->camera_info.systemkeys.keydb = this->systemkeys.keydb; // copy the systemkeys database into camera_info - if (this->camera.writekeys_when=="before") this->copy_keydb();// copy the ACF and userkeys database into camera_info + this->camera_info.systemkeys.keydb = this->systemkeys.keydb; + // copy systemkeys database object into camera_info - // If mode is not "RAW" but RAWENABLE is set then we're going to require a multi-extension data cube, - // one extension for the image and a separate extension for raw data. - // - if ( (error == NO_ERROR) && (mode != "RAW") && (this->modemap[mode].rawenable) ) { - if ( !this->camera.datacube() ) { // if datacube not already set then it must be overridden here - this->camera.async.enqueue( "NOTICE:override datacube true" ); // let everyone know - logwrite( function, "NOTICE:override datacube true" ); - this->camera.datacube(true); - } - this->camera_info.extension = 0; - } + if (this->camera.writekeys_when == "after") this->copy_keydb(); + // copy the ACF and userkeys database into camera_info - // Save the datacube state in camera_info so that the FITS writer can know about it - // - this->camera_info.iscube = this->camera.datacube(); + if (error == NO_ERROR) error = this->wait_for_readout(); // Wait for the readout into frame buffer, + if (error == NO_ERROR) error = read_frame(); + // then read the frame buffer to host (and write file) when frame ready. - // Open the FITS file now for cubes - // - if ( this->camera.datacube() ) { - error = this->fits_file.open_file( - this->camera.writekeys_when == "before", this->camera_info ); - if ( error != NO_ERROR ) { - this->camera.log_error( function, "couldn't open fits file" ); - return error; - } - } + this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info); + // close the file when not using datacubes + this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" - if (this->trigin_expose > 1) { - message.str(""); message << "starting sequence of " << this->trigin_expose << " frames. lastframe=" << this->lastframe; - logwrite(function, message.str()); - } + // ASYNC status message on completion of each file + // + message.str(""); + message << "FILE:" << this->camera_info.fits_name << " " << (error == NO_ERROR ? "COMPLETE" : "ERROR"); + this->camera.async.enqueue(message.str()); + error == NO_ERROR ? logwrite(function, message.str()) : this->camera.log_error(function, message.str()); + } else if (this->trigin_state == "expose") { + // ------------------------------------------------------------------------ + // "expose" + // The assumption here is that the exposure hasn't started yet. The next + // TRIGIN will initiate the complete exposure sequence in the Archon, + // exposure delay followed by sensor readout, so now we are waiting for that + // sequence to start. + // ------------------------------------------------------------------------ + // - // If not RAW mode then wait for Archon frame buffer to be ready, - // then read the latest ready frame buffer to the host. If this - // is a squence, then loop over all expected frames. - // - if ( (error == NO_ERROR) && (mode != "RAW") ) { // If not raw mode then - while ( this->trigin_expose-- > 0 ) { - - // Open a new FITS file for each frame when not using datacubes - // - if ( !this->camera.datacube() ) { - this->camera_info.start_time = get_timestamp(); // current system time formatted as YYYY-MM-DDTHH:MM:SS.sss - this->get_timer(&this->start_timer); // Archon internal timer (one tick=10 nsec) - this->camera.set_fitstime(this->camera_info.start_time); // sets camera.fitstime (YYYYMMDDHHMMSS) used for filename - error=this->camera.get_fitsname(this->camera_info.fits_name); // Assemble the FITS filename - if ( error != NO_ERROR ) { - this->camera.log_error( function, "couldn't validate fits filename" ); - return error; - } - this->add_filename_key(); // add filename to system keys database - error = this->fits_file.open_file( - this->camera.writekeys_when == "before", this->camera_info ); - if ( error != NO_ERROR ) { - this->camera.log_error( function, "couldn't open fits file" ); - return error; - } - } + // get system time and Archon's timer after exposure starts + // start_timer is used to determine when the exposure has ended, in wait_for_exposure() + // + this->camera_info.start_time = get_timestamp(); // current system time formatted as YYYY-MM-DDTHH:MM:SS.sss + error = this->get_timer(&this->start_timer); // Archon internal timer (one tick=10 nsec) - if (error==NO_ERROR && this->camera_info.exposure_time != 0) { // wait for the exposure delay to complete (if there is one) - error = this->wait_for_exposure(); - } + this->camera.set_fitstime(this->camera_info.start_time); + // sets camera.fitstime (YYYYMMDDHHMMSS) used for filename + error = this->camera.get_fitsname(this->camera_info.fits_name); // assemble the FITS filename + this->add_filename_key(); // add filename to system keys database - if (error==NO_ERROR) error = this->wait_for_readout(); // Wait for the readout into frame buffer, - if (error==NO_ERROR) error = read_frame(); // then read the frame buffer to host (and write file) when frame ready. + this->camera_info.systemkeys.keydb = this->systemkeys.keydb; + // copy the systemkeys database into camera_info + if (this->camera.writekeys_when == "before") this->copy_keydb(); + // copy the ACF and userkeys database into camera_info - if ( !this->camera.datacube() ) { // Error or not, close the file. - this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info ); // close the file when not using datacubes - this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" + // If mode is not "RAW" but RAWENABLE is set then we're going to require a multi-extension data cube, + // one extension for the image and a separate extension for raw data. + // + if ((error == NO_ERROR) && (mode != "RAW") && (this->modemap[mode].rawenable)) { + if (!this->camera.datacube()) { + // if datacube not already set then it must be overridden here + this->camera.async.enqueue("NOTICE:override datacube true"); // let everyone know + logwrite(function, "NOTICE:override datacube true"); + this->camera.datacube(true); + } + this->camera_info.extension = 0; + } - // ASYNC status message on completion of each file + // Save the datacube state in camera_info so that the FITS writer can know about it // - message.str(""); message << "FILE:" << this->camera_info.fits_name << " " << ( error==NO_ERROR ? "COMPLETE" : "ERROR" ); - this->camera.async.enqueue( message.str() ); - error == NO_ERROR ? logwrite( function, message.str() ) : this->camera.log_error( function, message.str() ); - } - - if (error != NO_ERROR) break; // don't try additional sequences if there were errors - } - - } else if ( (error == NO_ERROR) && (mode == "RAW") ) { - error = this->get_frame_status(); // Get the current frame buffer status - if (error==NO_ERROR) error = this->camera.get_fitsname( this->camera_info.fits_name ); // Assemble the FITS filename - this->add_filename_key(); // add filename to system keys database - if (error==NO_ERROR) error = this->fits_file.open_file( - this->camera.writekeys_when == "before", this->camera_info ); - if (error==NO_ERROR) error = read_frame(); // For raw mode just read immediately - this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info ); - this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" - } - // for cubes, close the FITS file now that they've all been written - // - if ( this->camera.datacube() ) { - this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info ); - this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" + this->camera_info.iscube = this->camera.datacube(); - // ASYNC status message on completion of each file - // - message.str(""); message << "FILE:" << this->camera_info.fits_name << " " << ( error==NO_ERROR ? "COMPLETE" : "ERROR" ); - this->camera.async.enqueue( message.str() ); - error == NO_ERROR ? logwrite( function, message.str() ) : this->camera.log_error( function, message.str() ); - } - // end if ( this->trigin_state == "expose" ) + // Open the FITS file now for cubes + // + if (this->camera.datacube()) { + error = this->fits_file.open_file( + this->camera.writekeys_when == "before", this->camera_info); + if (error != NO_ERROR) { + this->camera.log_error(function, "couldn't open fits file"); + return error; + } + } - } else { - // unknown (should be impossible) - message.str(""); message << "unexpected error processing trigin state " << this->trigin_state; - this->camera.log_error( function, message.str() ); - error = ERROR; - } + if (this->trigin_expose > 1) { + message.str(""); + message << "starting sequence of " << this->trigin_expose << " frames. lastframe=" << this->lastframe; + logwrite(function, message.str()); + } - return error; - } - /**************** Archon::Interface::trigin *********************************/ + // If not RAW mode then wait for Archon frame buffer to be ready, + // then read the latest ready frame buffer to the host. If this + // is a squence, then loop over all expected frames. + // + if ((error == NO_ERROR) && (mode != "RAW")) { + // If not raw mode then + while (this->trigin_expose-- > 0) { + // Open a new FITS file for each frame when not using datacubes + // + if (!this->camera.datacube()) { + this->camera_info.start_time = get_timestamp(); + // current system time formatted as YYYY-MM-DDTHH:MM:SS.sss + this->get_timer(&this->start_timer); // Archon internal timer (one tick=10 nsec) + this->camera.set_fitstime(this->camera_info.start_time); + // sets camera.fitstime (YYYYMMDDHHMMSS) used for filename + error = this->camera.get_fitsname(this->camera_info.fits_name); // Assemble the FITS filename + if (error != NO_ERROR) { + this->camera.log_error(function, "couldn't validate fits filename"); + return error; + } + this->add_filename_key(); // add filename to system keys database + error = this->fits_file.open_file( + this->camera.writekeys_when == "before", this->camera_info); + if (error != NO_ERROR) { + this->camera.log_error(function, "couldn't open fits file"); + return error; + } + } + if (error == NO_ERROR && this->camera_info.exposure_time != 0) { + // wait for the exposure delay to complete (if there is one) + error = this->wait_for_exposure(); + } - /**************** Archon::Interface::copy_keydb *****************************/ - /** - * @fn copy_keydb - * @brief copy the ACF and user keyword databases into camera_info - * @param[in] none - * @return none - * - */ - void Interface::copy_keydb() { - std::string function = "Archon::Interface::copy_keydb"; + if (error == NO_ERROR) error = this->wait_for_readout(); // Wait for the readout into frame buffer, + if (error == NO_ERROR) error = read_frame(); + // then read the frame buffer to host (and write file) when frame ready. + + if (!this->camera.datacube()) { + // Error or not, close the file. + this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info); + // close the file when not using datacubes + this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" + + // ASYNC status message on completion of each file + // + message.str(""); + message << "FILE:" << this->camera_info.fits_name << " " << (error == NO_ERROR + ? "COMPLETE" + : "ERROR"); + this->camera.async.enqueue(message.str()); + error == NO_ERROR + ? logwrite(function, message.str()) + : this->camera.log_error(function, message.str()); + } - // copy the userkeys database object into camera_info - // - this->camera_info.userkeys.keydb = this->userkeys.keydb; + if (error != NO_ERROR) break; // don't try additional sequences if there were errors + } + } else if ((error == NO_ERROR) && (mode == "RAW")) { + error = this->get_frame_status(); // Get the current frame buffer status + if (error == NO_ERROR) error = this->camera.get_fitsname(this->camera_info.fits_name); + // Assemble the FITS filename + this->add_filename_key(); // add filename to system keys database + if (error == NO_ERROR) + error = this->fits_file.open_file( + this->camera.writekeys_when == "before", this->camera_info); + if (error == NO_ERROR) error = read_frame(); // For raw mode just read immediately + this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info); + this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" + } + // for cubes, close the FITS file now that they've all been written + // + if (this->camera.datacube()) { + this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info); + this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" + + // ASYNC status message on completion of each file + // + message.str(""); + message << "FILE:" << this->camera_info.fits_name << " " << (error == NO_ERROR ? "COMPLETE" : "ERROR"); + this->camera.async.enqueue(message.str()); + error == NO_ERROR ? logwrite(function, message.str()) : this->camera.log_error(function, message.str()); + } + // end if ( this->trigin_state == "expose" ) + } else { + // unknown (should be impossible) + message.str(""); + message << "unexpected error processing trigin state " << this->trigin_state; + this->camera.log_error(function, message.str()); + error = ERROR; + } - // add any keys from the ACF file (from modemap[mode].acfkeys) into the - // camera_info.userkeys object - // - std::string mode = this->camera_info.current_observing_mode; - Common::FitsKeys::fits_key_t::iterator keyit; - for ( keyit = this->modemap[mode].acfkeys.keydb.begin(); - keyit != this->modemap[mode].acfkeys.keydb.end(); - keyit++ ) { - this->camera_info.userkeys.keydb[keyit->second.keyword].keyword = keyit->second.keyword; - this->camera_info.userkeys.keydb[keyit->second.keyword].keytype = keyit->second.keytype; - this->camera_info.userkeys.keydb[keyit->second.keyword].keyvalue = keyit->second.keyvalue; - this->camera_info.userkeys.keydb[keyit->second.keyword].keycomment = keyit->second.keycomment; + return error; } - #ifdef LOGLEVEL_DEBUG - logwrite( function, "[DEBUG] copied userkeys db to camera_info" ); - #endif - } - /**************** Archon::Interface::copy_keydb *****************************/ - - - /**************** Archon::Interface::longexposure ***************************/ - /** - * @fn longexposure - * @brief set/get longexposure mode - * @param string - * @return ERROR or NO_ERROR - * - */ - long Interface::longexposure(std::string state_in, std::string &state_out) { - std::string function = "Archon::Interface::longexposure"; - std::stringstream message; - long error = NO_ERROR; - - // If something is passed then try to use it to set the longexposure state - // - if ( !state_in.empty() ) { - try { - std::transform( state_in.begin(), state_in.end(), state_in.begin(), ::toupper ); // make uppercase - if ( state_in == "FALSE" || state_in == "0" ) this->is_longexposure = false; - else - if ( state_in == "TRUE" || state_in == "1" ) this->is_longexposure = true; - else { - message.str(""); message << "longexposure state " << state_in << " is invalid. Expecting {true,false,0,1}"; - this->camera.log_error( function, message.str() ); - return ERROR; - } - - } catch (...) { - message.str(""); message << "unknown exception converting longexposure state " << state_in << " to uppercase"; - this->camera.log_error( function, message.str() ); - return ERROR; - } - } + /**************** Archon::Interface::trigin *********************************/ - // error or not, the state reported now will be whatever was last successfully set - // - this->camera_info.exposure_unit = ( this->is_longexposure ? "sec" : "msec" ); - this->camera_info.exposure_factor = ( this->is_longexposure ? 1 : 1000 ); - state_out = ( this->is_longexposure ? "true" : "false" ); - // if no error then set the parameter on the Archon - // - if ( error==NO_ERROR ) { - std::stringstream cmd; - cmd << "longexposure " << ( this->is_longexposure ? 1 : 0 ); - if ( error==NO_ERROR ) error = this->set_parameter( cmd.str() ); - } + /**************** Archon::Interface::copy_keydb *****************************/ + /** + * @fn copy_keydb + * @brief copy the ACF and user keyword databases into camera_info + * @param[in] none + * @return none + * + */ + void Interface::copy_keydb() { + std::string function = "Archon::Interface::copy_keydb"; - return error; - } - /**************** Archon::Interface::longexposure ***************************/ - - - /**************** Archon::Interface::heater *********************************/ - /** - * @fn heater - * @brief heater control, set/get state, target, PID, ramp - * @param args contains various allowable strings (see full descsription) - * @return ERROR or NO_ERROR - * - * valid args format, - * to set or get the enable state and target for heater A or B on the specified module: - * < A | B > [ ] - * possible args: 2 (get) or 3 or 4 - * - * to set or get the PID parameters for heater A or B on the specified module: - * < A | B > PID [

] - * possible args: 3 (get) or 6 (set) - * - * to set or get the ramp and ramprate for heater A or B on the specified module: - * < A | B > RAMP [ [ramprate] ] - * possible args: 3 (get) or 5 (set) - * - * to set or get the current limit for heater A or B on the specified module: - * < A | B > ILIM [ ] - * possible args: 3 (get) or 4 (set) - * - * to set or get the input sensor for heater A or B on the specified module: - * < A | B > INPUT [ A | B | C ] - * possible args: 3 (get) or 4 (set) - * - */ - long Interface::heater(std::string args, std::string &retstring) { - std::string function = "Archon::Interface::heater"; - std::stringstream message; - std::vector tokens; - int module; - std::string heaterid; //!< A|B - bool readonly=false; - float pid_p, pid_i, pid_d; //!< requested PID values - int ramprate; //!< requested ramp rate value - float target; //!< requested heater target value - std::vector heaterconfig; //!< vector of configuration lines to read or write - std::vector heatervalue; //!< vector of values associated with config lines (for write) - - // must have loaded firmware // TODO implement a command to read the configuration - // // memory from Archon, in order to remove this restriction. - // - if ( ! this->firmwareloaded ) { - this->camera.log_error( function, "firmware not loaded" ); - return ERROR; - } + // copy the userkeys database object into camera_info + // + this->camera_info.userkeys.keydb = this->userkeys.keydb; - std::transform( args.begin(), args.end(), args.begin(), ::toupper ); // make uppercase + // add any keys from the ACF file (from modemap[mode].acfkeys) into the + // camera_info.userkeys object + // + std::string mode = this->camera_info.current_observing_mode; + Common::FitsKeys::fits_key_t::iterator keyit; + for (keyit = this->modemap[mode].acfkeys.keydb.begin(); + keyit != this->modemap[mode].acfkeys.keydb.end(); + keyit++) { + this->camera_info.userkeys.keydb[keyit->second.keyword].keyword = keyit->second.keyword; + this->camera_info.userkeys.keydb[keyit->second.keyword].keytype = keyit->second.keytype; + this->camera_info.userkeys.keydb[keyit->second.keyword].keyvalue = keyit->second.keyvalue; + this->camera_info.userkeys.keydb[keyit->second.keyword].keycomment = keyit->second.keycomment; + } - // RAMP requires a minimum backplane version - // - int ret = compare_versions( this->backplaneversion, REV_RAMP ); - if ( ret < 0 ) { - if ( ret == -999 ) { - message << "comparing backplane version " << this->backplaneversion << " to " << REV_RAMP; - - } else { - message << "requires backplane version " << REV_RAMP << " or newer. (" - << this->backplaneversion << " detected)"; - } - this->camera.log_error( function, message.str() ); - return ERROR; +#ifdef LOGLEVEL_DEBUG + logwrite( function, "[DEBUG] copied userkeys db to camera_info" ); +#endif } - Tokenize(args, tokens, " "); - - // At minimum there must be two tokens, A|B - // - if ( tokens.size() < 2 ) { - this->camera.log_error( function, "expected at least two arguments: A|B" ); - return ERROR; - } + /**************** Archon::Interface::copy_keydb *****************************/ - // As long as there are at least two tokens, get the module and heaterid - // which will be common to everything. - // - try { - module = std::stoi( tokens[0] ); - heaterid = tokens[1]; - if ( heaterid != "A" && heaterid != "B" ) { - message.str(""); message << "invalid heater " << heaterid << ": expected A|B"; - this->camera.log_error( function, message.str() ); - return ERROR; - } - } catch (std::invalid_argument &) { - message.str(""); message << "converting heater " << tokens[0] << " to integer"; - this->camera.log_error( function, message.str() ); - return ERROR; + /**************** Archon::Interface::longexposure ***************************/ + /** + * @fn longexposure + * @brief set/get longexposure mode + * @param string + * @return ERROR or NO_ERROR + * + */ + long Interface::longexposure(std::string state_in, std::string &state_out) { + std::string function = "Archon::Interface::longexposure"; + std::stringstream message; + long error = NO_ERROR; - } catch (std::out_of_range &) { - message.str(""); message << "heater : " << tokens[0] << " is outside integer range"; - this->camera.log_error( function, message.str() ); - return ERROR; - } + // If something is passed then try to use it to set the longexposure state + // + if (!state_in.empty()) { + try { + std::transform(state_in.begin(), state_in.end(), state_in.begin(), ::toupper); // make uppercase + if (state_in == "FALSE" || state_in == "0") this->is_longexposure = false; + else if (state_in == "TRUE" || state_in == "1") this->is_longexposure = true; + else { + message.str(""); + message << "longexposure state " << state_in << " is invalid. Expecting {true,false,0,1}"; + this->camera.log_error(function, message.str()); + return ERROR; + } + } catch (...) { + message.str(""); + message << "unknown exception converting longexposure state " << state_in << " to uppercase"; + this->camera.log_error(function, message.str()); + return ERROR; + } + } - // check that requested module is valid - // - switch ( this->modtype[ module-1 ] ) { - case 0: - message.str(""); message << "module " << module << " not installed"; - this->camera.log_error( function, message.str() ); - return ERROR; - case 5: // Heater - case 11: // HeaterX - break; - default: - message.str(""); message << "module " << module << " not a heater board"; - this->camera.log_error( function, message.str() ); - return ERROR; - } + // error or not, the state reported now will be whatever was last successfully set + // + this->camera_info.exposure_unit = (this->is_longexposure ? "sec" : "msec"); + this->camera_info.exposure_factor = (this->is_longexposure ? 1 : 1000); + state_out = (this->is_longexposure ? "true" : "false"); - // Now that we've passed the basic tests (firmware OK, basic syntax OK, and requested module is a heater) - // go ahead and set up some needed variables. - // + // if no error then set the parameter on the Archon + // + if (error == NO_ERROR) { + std::stringstream cmd; + cmd << "longexposure " << (this->is_longexposure ? 1 : 0); + if (error == NO_ERROR) error = this->set_parameter(cmd.str()); + } - // heater target min/max depends on backplane version - // - ret = compare_versions( this->backplaneversion, REV_HEATERTARGET ); - if ( ret == -999 ) { - message.str(""); message << "comparing backplane version " << this->backplaneversion << " to " << REV_HEATERTARGET; - this->camera.log_error( function, message.str() ); - return ERROR; - - } else if ( ret == -1 ) { - this->heater_target_min = -150.0; - this->heater_target_max = 50.0; - - } else { - this->heater_target_min = -250.0; - this->heater_target_max = 50.0; + return error; } - heaterconfig.clear(); - heatervalue.clear(); - std::stringstream ss; - - // Any one heater command can require reading or writing multiple configuration lines. - // The code will look at each case and push each heater configuration line that needs - // to be read or written into a vector (heaterconfig). For those that need to be written, - // the value to write will be pushed into a separate vector (heatervalue). After these - // vectors have been built up, the code will loop through each element of the vectors, - // reading and/or writing each. + /**************** Archon::Interface::longexposure ***************************/ - // Start looking at the heater arguments passed, which have been tokenized. - // There can be only 2, 3, 4, 5, or 6 tokens allowed. - // If there are exactly two (2) tokens then we have received only: - // A|B - // which means we're reading the state and target. - // - if ( tokens.size() == 2 ) { // no args reads ENABLE, TARGET - readonly = true; - ss.str(""); ss << "MOD" << module << "/HEATER" << heaterid << "ENABLE"; heaterconfig.push_back( ss.str() ); - ss.str(""); ss << "MOD" << module << "/HEATER" << heaterid << "TARGET"; heaterconfig.push_back( ss.str() ); - - } else if ( tokens.size() == 3 ) { - // If there are three (3) tokens then the 3rd must be one of the following: - // ON | OFF (for ENABLE), , PID, RAMP, ILIM - if ( tokens[2] == "ON" ) { // ON - ss.str(""); ss << "MOD" << module << "/HEATER" << heaterid << "ENABLE"; heaterconfig.push_back( ss.str() ); - heatervalue.emplace_back("1" ); - - } else if ( tokens[2] == "OFF" ) { // OFF - ss.str(""); ss << "MOD" << module << "/HEATER" << heaterid << "ENABLE"; heaterconfig.push_back( ss.str() ); - heatervalue.emplace_back("0" ); - - } else if ( tokens[2] == "RAMP" ) { // RAMP - readonly = true; - ss.str(""); ss << "MOD" << module << "/HEATER" << heaterid << "RAMP"; heaterconfig.push_back( ss.str() ); - ss.str(""); ss << "MOD" << module << "/HEATER" << heaterid << "RAMPRATE"; heaterconfig.push_back( ss.str() ); - - } else if ( tokens[2] == "PID" ) { // PID - readonly = true; - ss.str(""); ss << "MOD" << module << "/HEATER" << heaterid << "P"; heaterconfig.push_back( ss.str() ); - ss.str(""); ss << "MOD" << module << "/HEATER" << heaterid << "I"; heaterconfig.push_back( ss.str() ); - ss.str(""); ss << "MOD" << module << "/HEATER" << heaterid << "D"; heaterconfig.push_back( ss.str() ); - - } else if ( tokens[2] == "ILIM" ) { // ILIM - readonly = true; - ss.str(""); ss << "MOD" << module << "/HEATER" << heaterid << "IL"; heaterconfig.push_back( ss.str() ); - - } else if ( tokens[2] == "INPUT" ) { // INPUT - readonly = true; - ss.str(""); ss << "MOD" << module << "/HEATER" << heaterid << "SENSOR"; heaterconfig.push_back( ss.str() ); - - } else { // - // first check that the requested target is a valid number and within range... + /**************** Archon::Interface::heater *********************************/ + /** + * @fn heater + * @brief heater control, set/get state, target, PID, ramp + * @param args contains various allowable strings (see full descsription) + * @return ERROR or NO_ERROR + * + * valid args format, + * to set or get the enable state and target for heater A or B on the specified module: + * < A | B > [ ] + * possible args: 2 (get) or 3 or 4 + * + * to set or get the PID parameters for heater A or B on the specified module: + * < A | B > PID [

] + * possible args: 3 (get) or 6 (set) + * + * to set or get the ramp and ramprate for heater A or B on the specified module: + * < A | B > RAMP [ [ramprate] ] + * possible args: 3 (get) or 5 (set) + * + * to set or get the current limit for heater A or B on the specified module: + * < A | B > ILIM [ ] + * possible args: 3 (get) or 4 (set) + * + * to set or get the input sensor for heater A or B on the specified module: + * < A | B > INPUT [ A | B | C ] + * possible args: 3 (get) or 4 (set) + * + */ + long Interface::heater(std::string args, std::string &retstring) { + std::string function = "Archon::Interface::heater"; + std::stringstream message; + std::vector tokens; + int module; + std::string heaterid; //!< A|B + bool readonly = false; + float pid_p, pid_i, pid_d; //!< requested PID values + int ramprate; //!< requested ramp rate value + float target; //!< requested heater target value + std::vector heaterconfig; //!< vector of configuration lines to read or write + std::vector heatervalue; //!< vector of values associated with config lines (for write) + + // must have loaded firmware // TODO implement a command to read the configuration + // // memory from Archon, in order to remove this restriction. // - try { - target = std::stof( tokens[2] ); - if ( target < this->heater_target_min || target > this->heater_target_max ) { - message.str(""); message << "requested heater target " << target << " outside range {" - << this->heater_target_min << ":" << this->heater_target_max << "}"; - this->camera.log_error( function, message.str() ); + if (!this->firmwareloaded) { + this->camera.log_error(function, "firmware not loaded"); return ERROR; - } - - } catch (std::invalid_argument &) { - message.str(""); message << "converting heater =" << tokens[2] << " to float"; - this->camera.log_error( function, message.str() ); - return ERROR; - - } catch (std::out_of_range &) { - message.str(""); message << "heater : " << tokens[2] << " outside range of float"; - this->camera.log_error( function, message.str() ); - return ERROR; } - // ...if target is OK then push into the config, value vectors - // - ss.str(""); ss << "MOD" << module << "/HEATER" << heaterid << "TARGET"; heaterconfig.push_back( ss.str() ); - heatervalue.push_back( tokens[2] ); - } - } else if ( tokens.size() == 4 ) { - // If there are four (4) tokens then the 4th must be one of the following: - // in < A | B > [ ] - // | ON | OFF in < A | B > RAMP [ [ramprate] ] - // in < A | B > ILIM [ ] - // A | B | C in < A | B > INPUT [ A | B | C ] + std::transform(args.begin(), args.end(), args.begin(), ::toupper); // make uppercase - if ( tokens[2] == "ON" ) { // ON - // first check that the requested target is a valid number and within range... + // RAMP requires a minimum backplane version // - try { - target = std::stof( tokens[3] ); - if ( target < this->heater_target_min || target > this->heater_target_max ) { - message.str(""); message << "requested heater target " << target << " outside range {" - << this->heater_target_min << ":" << this->heater_target_max << "}"; - this->camera.log_error( function, message.str() ); + int ret = compare_versions(this->backplaneversion, REV_RAMP); + if (ret < 0) { + if (ret == -999) { + message << "comparing backplane version " << this->backplaneversion << " to " << REV_RAMP; + } else { + message << "requires backplane version " << REV_RAMP << " or newer. (" + << this->backplaneversion << " detected)"; + } + this->camera.log_error(function, message.str()); return ERROR; - } - - } catch (std::invalid_argument &) { - message.str(""); message << "converting heater " << tokens[3] << " to float"; - this->camera.log_error( function, message.str() ); - return ERROR; - - } catch (std::out_of_range &) { - message.str(""); message << "heater : " << tokens[3] << " outside range of float"; - this->camera.log_error( function, message.str() ); - return ERROR; } - // ...if target is OK then push into the config, value vectors - // - ss.str(""); ss << "MOD" << module << "/HEATER" << heaterid << "ENABLE"; heaterconfig.push_back( ss.str() ); - heatervalue.emplace_back("1" ); - ss.str(""); ss << "MOD" << module << "/HEATER" << heaterid << "TARGET"; heaterconfig.push_back( ss.str() ); - heatervalue.push_back( tokens[3] ); - } else if ( tokens[2] == "RAMP" ) { // RAMP x + Tokenize(args, tokens, " "); - if ( tokens[3] == "ON" || tokens[3] == "OFF" ) { // RAMP ON|OFF - ss.str(""); ss << "MOD" << module << "/HEATER" << heaterid << "RAMP"; heaterconfig.push_back( ss.str() ); - std::string state = ( tokens[3]=="ON" ? "1" : "0" ); - heatervalue.push_back( state ); - - } else { // RAMP - try { - ramprate = std::stoi( tokens[3] ); - if ( ramprate < 1 || ramprate > 32767 ) { - message.str(""); message << "heater ramprate " << ramprate << " outside range {1:32767}"; - this->camera.log_error( function, message.str() ); - return ERROR; - } - - } catch (std::invalid_argument &) { - message.str(""); message << "converting RAMP " << tokens[3] << " to integer"; - this->camera.log_error( function, message.str() ); - return ERROR; - - } catch (std::out_of_range &) { - message.str(""); message << "RAMP : " << tokens[3] << " outside range of integer"; - this->camera.log_error( function, message.str() ); + // At minimum there must be two tokens, A|B + // + if (tokens.size() < 2) { + this->camera.log_error(function, "expected at least two arguments: A|B"); return ERROR; - } - ss.str(""); ss << "MOD" << module << "/HEATER" << heaterid << "RAMPRATE"; heaterconfig.push_back( ss.str() ); - heatervalue.push_back( tokens[3] ); } - } else if ( tokens[2] == "ILIM" ) { // ILIM x - int il_value=0; + // As long as there are at least two tokens, get the module and heaterid + // which will be common to everything. + // try { - il_value = std::stoi( tokens[3] ); - if ( il_value < 0 || il_value > 10000 ) { - message.str(""); message << "heater ilim " << il_value << " outside range {0:10000}"; - this->camera.log_error( function, message.str() ); - return ERROR; - } - + module = std::stoi(tokens[0]); + heaterid = tokens[1]; + if (heaterid != "A" && heaterid != "B") { + message.str(""); + message << "invalid heater " << heaterid << ": expected A|B"; + this->camera.log_error(function, message.str()); + return ERROR; + } } catch (std::invalid_argument &) { - message.str(""); message << "converting ILIM " << tokens[3] << " to integer"; - this->camera.log_error( function, message.str() ); - return ERROR; - + message.str(""); + message << "converting heater " << tokens[0] << " to integer"; + this->camera.log_error(function, message.str()); + return ERROR; } catch (std::out_of_range &) { - message.str(""); message << "ILIM : " << tokens[3] << " outside range of integer"; - this->camera.log_error( function, message.str() ); - return ERROR; - } - ss.str(""); ss << "MOD" << module << "/HEATER" << heaterid << "IL"; heaterconfig.push_back( ss.str() ); - heatervalue.push_back( tokens[3] ); - - } else if ( tokens[2] == "INPUT" ) { // INPUT A|B|C - std::string sensorid; - if ( tokens[3] == "A" ) { - sensorid = "0"; - - } else if ( tokens[3] == "B" ) { - sensorid = "1"; - - } else if ( tokens[3] == "C" ) { - sensorid = "2"; - // input C supported only on HeaterX cards - if ( this->modtype[ module-1 ] != 11 ) { - message.str(""); message << "sensor C not supported on module " << module << ": HeaterX module required"; - this->camera.log_error( function, message.str() ); + message.str(""); + message << "heater : " << tokens[0] << " is outside integer range"; + this->camera.log_error(function, message.str()); return ERROR; - } - - } else { - message.str(""); message << "invalid sensor " << tokens.at(3) << ": expected A|B INPUT A|B|C"; - this->camera.log_error( function, message.str() ); - return ERROR; } - ss.str(""); ss << "MOD" << module << "/HEATER" << heaterid << "SENSOR"; heaterconfig.push_back( ss.str() ); - heatervalue.push_back( sensorid ); - } else { - message.str(""); message << "expected heater <" << module << "> ON | RAMP for 3rd argument but got " << tokens[2]; - this->camera.log_error( function, message.str() ); - return ERROR; - } + // check that requested module is valid + // + switch (this->modtype[module - 1]) { + case 0: + message.str(""); + message << "module " << module << " not installed"; + this->camera.log_error(function, message.str()); + return ERROR; + case 5: // Heater + case 11: // HeaterX + break; + default: + message.str(""); + message << "module " << module << " not a heater board"; + this->camera.log_error(function, message.str()); + return ERROR; + } - } else if ( tokens.size() == 5 ) { // RAMP ON - // If there are five (5) tokens then they must be - // A|B RAMP ON - if ( tokens[2] != "RAMP" && tokens[3] != "ON" ) { - message.str(""); message << "expected RAMP ON but got"; for (int i=2; i<5; i++) message << " " << tokens[i]; - this->camera.log_error( function, message.str() ); - return ERROR; + // Now that we've passed the basic tests (firmware OK, basic syntax OK, and requested module is a heater) + // go ahead and set up some needed variables. + // - } else { // got " A|B RAMP ON" now check that the last (5th) token is a number - try { - ramprate = std::stoi( tokens[4] ); - if ( ramprate < 1 || ramprate > 32767 ) { - message.str(""); message << "heater ramprate " << ramprate << " outside range {1:32767}"; - this->camera.log_error( function, message.str() ); + // heater target min/max depends on backplane version + // + ret = compare_versions(this->backplaneversion, REV_HEATERTARGET); + if (ret == -999) { + message.str(""); + message << "comparing backplane version " << this->backplaneversion << " to " << REV_HEATERTARGET; + this->camera.log_error(function, message.str()); return ERROR; - } + } else if (ret == -1) { + this->heater_target_min = -150.0; + this->heater_target_max = 50.0; + } else { + this->heater_target_min = -250.0; + this->heater_target_max = 50.0; + } - } catch (std::invalid_argument &) { - message.str(""); message << "expected RAMP ON but unable to convert =" << tokens[4] << " to integer"; - this->camera.log_error( function, message.str() ); - return ERROR; + heaterconfig.clear(); + heatervalue.clear(); + std::stringstream ss; - } catch (std::out_of_range &) { - message.str(""); message << "expected RAMP ON but =" << tokens[4] << " outside range of integer"; - this->camera.log_error( function, message.str() ); - return ERROR; - } - ss.str(""); ss << "MOD" << module << "/HEATER" << heaterid << "RAMP"; heaterconfig.push_back( ss.str() ); - heatervalue.emplace_back("1" ); - ss.str(""); ss << "MOD" << module << "/HEATER" << heaterid << "RAMPRATE"; heaterconfig.push_back( ss.str() ); - heatervalue.push_back( tokens[4] ); - } + // Any one heater command can require reading or writing multiple configuration lines. + // The code will look at each case and push each heater configuration line that needs + // to be read or written into a vector (heaterconfig). For those that need to be written, + // the value to write will be pushed into a separate vector (heatervalue). After these + // vectors have been built up, the code will loop through each element of the vectors, + // reading and/or writing each. - } else if ( tokens.size() == 6 ) { - // If there are six (6) tokens then they must be - // A|B PID

- if ( tokens[2] != "PID" ) { - message.str(""); message << "expected PID

but got"; for (int i=2; i<6; i++) message << " " << tokens[i]; - this->camera.log_error( function, message.str() ); - return ERROR; + // Start looking at the heater arguments passed, which have been tokenized. + // There can be only 2, 3, 4, 5, or 6 tokens allowed. - } else { // got " A|B PID

" now check that the last 3 tokens are numbers - // Fractional PID requires a minimum backplane version + // If there are exactly two (2) tokens then we have received only: + // A|B + // which means we're reading the state and target. // - bool fractionalpid_ok = false; - ret = compare_versions( this->backplaneversion, REV_FRACTIONALPID ); - if ( ret == -999 ) { - message.str(""); message << "comparing backplane version " << this->backplaneversion << " to " << REV_FRACTIONALPID; - this->camera.log_error( function, message.str() ); - return ERROR; - - } else if ( ret == -1 ) { - fractionalpid_ok = false; - - } else fractionalpid_ok = true; + if (tokens.size() == 2) { + // no args reads ENABLE, TARGET + readonly = true; + ss.str(""); + ss << "MOD" << module << "/HEATER" << heaterid << "ENABLE"; + heaterconfig.push_back(ss.str()); + ss.str(""); + ss << "MOD" << module << "/HEATER" << heaterid << "TARGET"; + heaterconfig.push_back(ss.str()); + } else if (tokens.size() == 3) { + // If there are three (3) tokens then the 3rd must be one of the following: + // ON | OFF (for ENABLE), , PID, RAMP, ILIM + if (tokens[2] == "ON") { + // ON + ss.str(""); + ss << "MOD" << module << "/HEATER" << heaterid << "ENABLE"; + heaterconfig.push_back(ss.str()); + heatervalue.emplace_back("1"); + } else if (tokens[2] == "OFF") { + // OFF + ss.str(""); + ss << "MOD" << module << "/HEATER" << heaterid << "ENABLE"; + heaterconfig.push_back(ss.str()); + heatervalue.emplace_back("0"); + } else if (tokens[2] == "RAMP") { + // RAMP + readonly = true; + ss.str(""); + ss << "MOD" << module << "/HEATER" << heaterid << "RAMP"; + heaterconfig.push_back(ss.str()); + ss.str(""); + ss << "MOD" << module << "/HEATER" << heaterid << "RAMPRATE"; + heaterconfig.push_back(ss.str()); + } else if (tokens[2] == "PID") { + // PID + readonly = true; + ss.str(""); + ss << "MOD" << module << "/HEATER" << heaterid << "P"; + heaterconfig.push_back(ss.str()); + ss.str(""); + ss << "MOD" << module << "/HEATER" << heaterid << "I"; + heaterconfig.push_back(ss.str()); + ss.str(""); + ss << "MOD" << module << "/HEATER" << heaterid << "D"; + heaterconfig.push_back(ss.str()); + } else if (tokens[2] == "ILIM") { + // ILIM + readonly = true; + ss.str(""); + ss << "MOD" << module << "/HEATER" << heaterid << "IL"; + heaterconfig.push_back(ss.str()); + } else if (tokens[2] == "INPUT") { + // INPUT + readonly = true; + ss.str(""); + ss << "MOD" << module << "/HEATER" << heaterid << "SENSOR"; + heaterconfig.push_back(ss.str()); + } else { + // + // first check that the requested target is a valid number and within range... + // + try { + target = std::stof(tokens[2]); + if (target < this->heater_target_min || target > this->heater_target_max) { + message.str(""); + message << "requested heater target " << target << " outside range {" + << this->heater_target_min << ":" << this->heater_target_max << "}"; + this->camera.log_error(function, message.str()); + return ERROR; + } + } catch (std::invalid_argument &) { + message.str(""); + message << "converting heater =" << tokens[2] << " to float"; + this->camera.log_error(function, message.str()); + return ERROR; + } catch (std::out_of_range &) { + message.str(""); + message << "heater : " << tokens[2] << " outside range of float"; + this->camera.log_error(function, message.str()); + return ERROR; + } + // ...if target is OK then push into the config, value vectors + // + ss.str(""); + ss << "MOD" << module << "/HEATER" << heaterid << "TARGET"; + heaterconfig.push_back(ss.str()); + heatervalue.push_back(tokens[2]); + } + } else if (tokens.size() == 4) { + // If there are four (4) tokens then the 4th must be one of the following: + // in < A | B > [ ] + // | ON | OFF in < A | B > RAMP [ [ramprate] ] + // in < A | B > ILIM [ ] + // A | B | C in < A | B > INPUT [ A | B | C ] + + if (tokens[2] == "ON") { + // ON + // first check that the requested target is a valid number and within range... + // + try { + target = std::stof(tokens[3]); + if (target < this->heater_target_min || target > this->heater_target_max) { + message.str(""); + message << "requested heater target " << target << " outside range {" + << this->heater_target_min << ":" << this->heater_target_max << "}"; + this->camera.log_error(function, message.str()); + return ERROR; + } + } catch (std::invalid_argument &) { + message.str(""); + message << "converting heater " << tokens[3] << " to float"; + this->camera.log_error(function, message.str()); + return ERROR; + } catch (std::out_of_range &) { + message.str(""); + message << "heater : " << tokens[3] << " outside range of float"; + this->camera.log_error(function, message.str()); + return ERROR; + } + // ...if target is OK then push into the config, value vectors + // + ss.str(""); + ss << "MOD" << module << "/HEATER" << heaterid << "ENABLE"; + heaterconfig.push_back(ss.str()); + heatervalue.emplace_back("1"); + ss.str(""); + ss << "MOD" << module << "/HEATER" << heaterid << "TARGET"; + heaterconfig.push_back(ss.str()); + heatervalue.push_back(tokens[3]); + } else if (tokens[2] == "RAMP") { + // RAMP x + + if (tokens[3] == "ON" || tokens[3] == "OFF") { + // RAMP ON|OFF + ss.str(""); + ss << "MOD" << module << "/HEATER" << heaterid << "RAMP"; + heaterconfig.push_back(ss.str()); + std::string state = (tokens[3] == "ON" ? "1" : "0"); + heatervalue.push_back(state); + } else { + // RAMP + try { + ramprate = std::stoi(tokens[3]); + if (ramprate < 1 || ramprate > 32767) { + message.str(""); + message << "heater ramprate " << ramprate << " outside range {1:32767}"; + this->camera.log_error(function, message.str()); + return ERROR; + } + } catch (std::invalid_argument &) { + message.str(""); + message << "converting RAMP " << tokens[3] << " to integer"; + this->camera.log_error(function, message.str()); + return ERROR; + } catch (std::out_of_range &) { + message.str(""); + message << "RAMP : " << tokens[3] << " outside range of integer"; + this->camera.log_error(function, message.str()); + return ERROR; + } + ss.str(""); + ss << "MOD" << module << "/HEATER" << heaterid << "RAMPRATE"; + heaterconfig.push_back(ss.str()); + heatervalue.push_back(tokens[3]); + } + } else if (tokens[2] == "ILIM") { + // ILIM x + int il_value = 0; + try { + il_value = std::stoi(tokens[3]); + if (il_value < 0 || il_value > 10000) { + message.str(""); + message << "heater ilim " << il_value << " outside range {0:10000}"; + this->camera.log_error(function, message.str()); + return ERROR; + } + } catch (std::invalid_argument &) { + message.str(""); + message << "converting ILIM " << tokens[3] << " to integer"; + this->camera.log_error(function, message.str()); + return ERROR; + } catch (std::out_of_range &) { + message.str(""); + message << "ILIM : " << tokens[3] << " outside range of integer"; + this->camera.log_error(function, message.str()); + return ERROR; + } + ss.str(""); + ss << "MOD" << module << "/HEATER" << heaterid << "IL"; + heaterconfig.push_back(ss.str()); + heatervalue.push_back(tokens[3]); + } else if (tokens[2] == "INPUT") { + // INPUT A|B|C + std::string sensorid; + if (tokens[3] == "A") { + sensorid = "0"; + } else if (tokens[3] == "B") { + sensorid = "1"; + } else if (tokens[3] == "C") { + sensorid = "2"; + // input C supported only on HeaterX cards + if (this->modtype[module - 1] != 11) { + message.str(""); + message << "sensor C not supported on module " << module << ": HeaterX module required"; + this->camera.log_error(function, message.str()); + return ERROR; + } + } else { + message.str(""); + message << "invalid sensor " << tokens.at(3) << ": expected A|B INPUT A|B|C"; + this->camera.log_error(function, message.str()); + return ERROR; + } + ss.str(""); + ss << "MOD" << module << "/HEATER" << heaterid << "SENSOR"; + heaterconfig.push_back(ss.str()); + heatervalue.push_back(sensorid); + } else { + message.str(""); + message << "expected heater <" << module << "> ON | RAMP for 3rd argument but got " << tokens[2]; + this->camera.log_error(function, message.str()); + return ERROR; + } + } else if (tokens.size() == 5) { + // RAMP ON + // If there are five (5) tokens then they must be + // A|B RAMP ON + if (tokens[2] != "RAMP" && tokens[3] != "ON") { + message.str(""); + message << "expected RAMP ON but got"; + for (int i = 2; i < 5; i++) message << " " << tokens[i]; + this->camera.log_error(function, message.str()); + return ERROR; + } else { + // got " A|B RAMP ON" now check that the last (5th) token is a number + try { + ramprate = std::stoi(tokens[4]); + if (ramprate < 1 || ramprate > 32767) { + message.str(""); + message << "heater ramprate " << ramprate << " outside range {1:32767}"; + this->camera.log_error(function, message.str()); + return ERROR; + } + } catch (std::invalid_argument &) { + message.str(""); + message << "expected RAMP ON but unable to convert =" << tokens[4] << + " to integer"; + this->camera.log_error(function, message.str()); + return ERROR; + } catch (std::out_of_range &) { + message.str(""); + message << "expected RAMP ON but =" << tokens[4] << + " outside range of integer"; + this->camera.log_error(function, message.str()); + return ERROR; + } + ss.str(""); + ss << "MOD" << module << "/HEATER" << heaterid << "RAMP"; + heaterconfig.push_back(ss.str()); + heatervalue.emplace_back("1"); + ss.str(""); + ss << "MOD" << module << "/HEATER" << heaterid << "RAMPRATE"; + heaterconfig.push_back(ss.str()); + heatervalue.push_back(tokens[4]); + } + } else if (tokens.size() == 6) { + // If there are six (6) tokens then they must be + // A|B PID

+ if (tokens[2] != "PID") { + message.str(""); + message << "expected PID

but got"; + for (int i = 2; i < 6; i++) message << " " << tokens[i]; + this->camera.log_error(function, message.str()); + return ERROR; + } else { + // got " A|B PID

" now check that the last 3 tokens are numbers + // Fractional PID requires a minimum backplane version + // + bool fractionalpid_ok = false; + ret = compare_versions(this->backplaneversion, REV_FRACTIONALPID); + if (ret == -999) { + message.str(""); + message << "comparing backplane version " << this->backplaneversion << " to " << REV_FRACTIONALPID; + this->camera.log_error(function, message.str()); + return ERROR; + } else if (ret == -1) { + fractionalpid_ok = false; + } else fractionalpid_ok = true; - try { - // If using backplane where fractional PID was not allowed, check for decimal points - // - if ( (!fractionalpid_ok) && - ( ( tokens[3].find('.') != std::string::npos ) || - ( tokens[4].find('.') != std::string::npos ) || - ( tokens[5].find('.') != std::string::npos ) ) ) { - fesetround(FE_TONEAREST); // round to value nearest X. halfway cases rounded away from zero - tokens[3] = std::to_string( std::lrint( std::stof( tokens[3] ) ) ); // replace token with rounded integer - tokens[4] = std::to_string( std::lrint( std::stof( tokens[4] ) ) ); // replace token with rounded integer - tokens[5] = std::to_string( std::lrint( std::stof( tokens[5] ) ) ); // replace token with rounded integer - - message.str(""); message << "NOTICE:fractional heater PID requires backplane version " << REV_FRACTIONALPID << " or newer"; - logwrite( function, message.str() ); - this->camera.async.enqueue( message.str() ); - message.str(""); message << "NOTICE:backplane version " << this->backplaneversion << " detected"; - logwrite( function, message.str() ); - this->camera.async.enqueue( message.str() ); - message.str(""); message << "NOTICE:PIDs converted to: " << tokens[3] << " " << tokens[4] << " " << tokens[5]; - this->camera.async.enqueue( message.str() ); - logwrite( function, message.str() ); - - } - pid_p = std::stof( tokens[3] ); - pid_i = std::stof( tokens[4] ); - pid_d = std::stof( tokens[5] ); - if ( pid_p < 0 || pid_p > 10000 || pid_i < 0 || pid_i > 10000 || pid_d < 0 || pid_d > 10000 ) { - message.str(""); message << "one or more heater PID values outside range {0:10000}"; - this->camera.log_error( function, message.str() ); + try { + // If using backplane where fractional PID was not allowed, check for decimal points + // + if ((!fractionalpid_ok) && + ((tokens[3].find('.') != std::string::npos) || + (tokens[4].find('.') != std::string::npos) || + (tokens[5].find('.') != std::string::npos))) { + fesetround(FE_TONEAREST); // round to value nearest X. halfway cases rounded away from zero + tokens[3] = std::to_string(std::lrint(std::stof(tokens[3]))); + // replace token with rounded integer + tokens[4] = std::to_string(std::lrint(std::stof(tokens[4]))); + // replace token with rounded integer + tokens[5] = std::to_string(std::lrint(std::stof(tokens[5]))); + // replace token with rounded integer + + message.str(""); + message << "NOTICE:fractional heater PID requires backplane version " << REV_FRACTIONALPID << + " or newer"; + logwrite(function, message.str()); + this->camera.async.enqueue(message.str()); + message.str(""); + message << "NOTICE:backplane version " << this->backplaneversion << " detected"; + logwrite(function, message.str()); + this->camera.async.enqueue(message.str()); + message.str(""); + message << "NOTICE:PIDs converted to: " << tokens[3] << " " << tokens[4] << " " << tokens[5]; + this->camera.async.enqueue(message.str()); + logwrite(function, message.str()); + } + pid_p = std::stof(tokens[3]); + pid_i = std::stof(tokens[4]); + pid_d = std::stof(tokens[5]); + if (pid_p < 0 || pid_p > 10000 || pid_i < 0 || pid_i > 10000 || pid_d < 0 || pid_d > 10000) { + message.str(""); + message << "one or more heater PID values outside range {0:10000}"; + this->camera.log_error(function, message.str()); + return ERROR; + } + } catch (std::invalid_argument &) { + message.str(""); + message << "converting one or more heater PID values to numbers:"; + for (int i = 3; i < 6; i++) message << " " << tokens[i]; + this->camera.log_error(function, message.str()); + return ERROR; + } catch (std::out_of_range &) { + message.str(""); + message << "heater PID exception: one or more values outside range:"; + for (int i = 3; i < 6; i++) message << " " << tokens[i]; + this->camera.log_error(function, message.str()); + return ERROR; + } + ss.str(""); + ss << "MOD" << module << "/HEATER" << heaterid << "P"; + heaterconfig.push_back(ss.str()); + heatervalue.push_back(tokens[3]); + ss.str(""); + ss << "MOD" << module << "/HEATER" << heaterid << "I"; + heaterconfig.push_back(ss.str()); + heatervalue.push_back(tokens[4]); + ss.str(""); + ss << "MOD" << module << "/HEATER" << heaterid << "D"; + heaterconfig.push_back(ss.str()); + heatervalue.push_back(tokens[5]); + } + } else { + // Otherwise we have an invalid number of tokens + message.str(""); + message << "received " << tokens.size() << " arguments but expected 2, 3, 4, 5, or 6"; + this->camera.log_error(function, message.str()); return ERROR; - } - - } catch (std::invalid_argument &) { - message.str(""); message << "converting one or more heater PID values to numbers:"; - for (int i=3; i<6; i++) message << " " << tokens[i]; - this->camera.log_error( function, message.str() ); - return ERROR; - - } catch (std::out_of_range &) { - message.str(""); message << "heater PID exception: one or more values outside range:"; - for (int i=3; i<6; i++) message << " " << tokens[i]; - this->camera.log_error( function, message.str() ); - return ERROR; - } - ss.str(""); ss << "MOD" << module << "/HEATER" << heaterid << "P"; heaterconfig.push_back( ss.str() ); - heatervalue.push_back( tokens[3] ); - ss.str(""); ss << "MOD" << module << "/HEATER" << heaterid << "I"; heaterconfig.push_back( ss.str() ); - heatervalue.push_back( tokens[4] ); - ss.str(""); ss << "MOD" << module << "/HEATER" << heaterid << "D"; heaterconfig.push_back( ss.str() ); - heatervalue.push_back( tokens[5] ); - } + } - } else { - // Otherwise we have an invalid number of tokens - message.str(""); message << "received " << tokens.size() << " arguments but expected 2, 3, 4, 5, or 6"; - this->camera.log_error( function, message.str() ); - return ERROR; - } + long error; - long error; + if (!readonly) { + // For writing, loop through the heaterconfig and heatervalue vectors. + // They MUST be the same size! This should be impossible. + // + if (heaterconfig.size() != heatervalue.size()) { + message.str(""); + message << "BUG DETECTED: heaterconfig (" << heaterconfig.size() + << ") - heatervalue (" << heatervalue.size() << ") vector size mismatch"; + this->camera.log_error(function, message.str()); + return ERROR; + } - if ( ! readonly ) { + // Write the configuration lines + // + bool changed = false; + size_t error_count = 0; + for (size_t i = 0; i < heaterconfig.size(); i++) { + error = this->write_config_key(heaterconfig[i].c_str(), heatervalue[i].c_str(), changed); + message.str(""); + if (error != NO_ERROR) { + message << "writing configuration " << heaterconfig[i] << "=" << heatervalue[i]; + error_count++; // this counter will be checked before the APPLYMOD command + } else if (!changed) { + message << "heater configuration: " << heaterconfig[i] << "=" << heatervalue[i] << " unchanged"; + } else { + message << "updated heater configuration: " << heaterconfig[i] << "=" << heatervalue[i]; + } + error == NO_ERROR ? logwrite(function, message.str()) : this->camera.log_error(function, message.str()); + } - // For writing, loop through the heaterconfig and heatervalue vectors. - // They MUST be the same size! This should be impossible. - // - if ( heaterconfig.size() != heatervalue.size() ) { - message.str(""); - message << "BUG DETECTED: heaterconfig (" << heaterconfig.size() - << ") - heatervalue (" << heatervalue.size() << ") vector size mismatch"; - this->camera.log_error( function, message.str() ); - return ERROR; - } + // send the APPLMODxx command which parses and applies the configuration for module xx + // The APPLYMOD is sent even if an error occured writing the config key(s) because + // it's possible that one of the config keys was written. + // + // If error_count is the same as the number of configuration lines, then they all failed + // to write, in which case do not send APPLYMOD. But if even one config key was written + // then the APPLYMOD command must be sent. + // + if (error_count == heaterconfig.size()) { + return ERROR; + } - // Write the configuration lines - // - bool changed = false; - size_t error_count = 0; - for ( size_t i=0; i < heaterconfig.size(); i++ ) { - error = this->write_config_key( heaterconfig[i].c_str(), heatervalue[i].c_str(), changed ); - message.str(""); - if ( error != NO_ERROR ) { - message << "writing configuration " << heaterconfig[i] << "=" << heatervalue[i]; - error_count++; // this counter will be checked before the APPLYMOD command + std::stringstream applystr; + applystr << "APPLYMOD" + << std::setfill('0') + << std::setw(2) + << std::hex + << (module - 1); - } else if ( !changed ) { - message << "heater configuration: " << heaterconfig[i] << "=" << heatervalue[i] << " unchanged"; + error = this->archon_cmd(applystr.str()); - } else { - message << "updated heater configuration: " << heaterconfig[i] << "=" << heatervalue[i]; + if (error != NO_ERROR) { + logwrite(function, "ERROR: applying heater configuration"); + } } - error == NO_ERROR ? logwrite( function, message.str() ) : this->camera.log_error( function, message.str() ); - } - - // send the APPLMODxx command which parses and applies the configuration for module xx - // The APPLYMOD is sent even if an error occured writing the config key(s) because - // it's possible that one of the config keys was written. - // - // If error_count is the same as the number of configuration lines, then they all failed - // to write, in which case do not send APPLYMOD. But if even one config key was written - // then the APPLYMOD command must be sent. - // - if ( error_count == heaterconfig.size() ) { - return ERROR; - } - - std::stringstream applystr; - applystr << "APPLYMOD" - << std::setfill('0') - << std::setw(2) - << std::hex - << (module-1); - - error = this->archon_cmd( applystr.str() ); - - if ( error != NO_ERROR ) { - logwrite( function, "ERROR: applying heater configuration" ); - } - } - - // Now read the configuration line(s). - // For multiple lines, concatenate all values into one space-delimited string. - - // loop through the vector of heaterconfig keys, - // getting the value for each, and putting them into retss - // - std::string value; - std::stringstream retss; - for ( const auto &key : heaterconfig ) { - error = this->get_configmap_value( key, value ); + // Now read the configuration line(s). + // For multiple lines, concatenate all values into one space-delimited string. - if ( error != NO_ERROR ) { - message.str(""); message << "reading heater configuration " << key; - logwrite( function, message.str() ); - return error; + // loop through the vector of heaterconfig keys, + // getting the value for each, and putting them into retss + // + std::string value; + std::stringstream retss; + for (const auto &key: heaterconfig) { + error = this->get_configmap_value(key, value); - } else { - // If key ends with "ENABLE" or "RAMP" - // then convert the values 0,1 to OFF,ON, respectively - // - if ( key.substr( key.length()-6 ) == "ENABLE" || - key.substr( key.length()-4 ) == "RAMP" ) { - if ( value == "0" ) value = "OFF"; - else if ( value == "1" ) value = "ON"; - else { - message.str(""); message << "bad value " << value << " from configuration. expected 0 or 1"; - this->camera.log_error( function, message.str() ); - error = ERROR; - } - - } else if ( key.substr( key.length()-6 ) == "SENSOR" ) { - // or if key ends with "SENSOR" then map the value (0,1,2) to the name (A,B,C) - if ( value == "0" ) value = "A"; - else if ( value == "1" ) value = "B"; - else if ( value == "2" ) value = "C"; - else { - message.str(""); message << "bad value " << value << " from configuration. expected 0,1,2"; - this->camera.log_error( function, message.str() ); - error = ERROR; - } + if (error != NO_ERROR) { + message.str(""); + message << "reading heater configuration " << key; + logwrite(function, message.str()); + return error; + } else { + // If key ends with "ENABLE" or "RAMP" + // then convert the values 0,1 to OFF,ON, respectively + // + if (key.substr(key.length() - 6) == "ENABLE" || + key.substr(key.length() - 4) == "RAMP") { + if (value == "0") value = "OFF"; + else if (value == "1") value = "ON"; + else { + message.str(""); + message << "bad value " << value << " from configuration. expected 0 or 1"; + this->camera.log_error(function, message.str()); + error = ERROR; + } + } else if (key.substr(key.length() - 6) == "SENSOR") { + // or if key ends with "SENSOR" then map the value (0,1,2) to the name (A,B,C) + if (value == "0") value = "A"; + else if (value == "1") value = "B"; + else if (value == "2") value = "C"; + else { + message.str(""); + message << "bad value " << value << " from configuration. expected 0,1,2"; + this->camera.log_error(function, message.str()); + error = ERROR; + } + } + retss << value << " "; + message.str(""); + message << key << "=" << value; + logwrite(function, message.str()); + } } - retss << value << " "; - message.str(""); message << key << "=" << value; - logwrite( function, message.str() ); - } - } - retstring = retss.str(); // return value to calling function, passed by reference - - return ( error ); - } - /**************** Archon::Interface::heater *********************************/ - - - /**************** Archon::Interface::sensor *********************************/ - /** - * @fn sensor - * @brief set or get temperature sensor current - * @param args contains various allowable strings (see full descsription) - * @return ERROR or NO_ERROR - * - * sensor < A | B | C > [ current ] - * possible args: 2 (get) or 3 (set) - * - * sensor < A | B | C > AVG [ N ] - * possible args: 3 (get) or 4 (set) - * - * Sets or gets the temperature sensor current for the specified - * sensor on the specified module . refers to the - * (integer) module number. is specified in nano-amps. - * This is used only for RTDs - * - * When the AVG arg is used then set or get digital averaging - * - */ - long Interface::sensor(std::string args, std::string &retstring) { - std::string function = "Archon::Interface::sensor"; - std::stringstream message; - std::vector tokens; - std::string sensorid; //!< A | B | C - std::stringstream sensorconfig; //!< configuration line to read or write - std::string sensorvalue; //!< configuration line value - int module; //!< integer module number - bool readonly=true; //!< true is reading, not writing current - long error; - - // must have loaded firmware // TODO implement a command to read the configuration - // // memory from Archon, in order to remove this restriction. - // - if ( ! this->firmwareloaded ) { - this->camera.log_error( function, "firmware not loaded" ); - return ERROR; - } + retstring = retss.str(); // return value to calling function, passed by reference - // requires a minimum backplane version - // - int ret = compare_versions( this->backplaneversion, REV_SENSORCURRENT ); - if ( ret < 0 ) { - if ( ret == -999 ) { - message << "comparing backplane version " << this->backplaneversion << " to " << REV_SENSORCURRENT; - - } else { - message << "requires backplane version " << REV_SENSORCURRENT << " or newer. (" - << this->backplaneversion << " detected)"; - } - this->camera.log_error( function, message.str() ); - return ERROR; + return (error); } - std::transform( args.begin(), args.end(), args.begin(), ::toupper ); // make uppercase - - Tokenize( args, tokens, " " ); - - // At minimum there must be two tokens, - // - if ( tokens.size() < 2 ) { - this->camera.log_error( function, "expected at least two arguments: A|B" ); - return ERROR; - } + /**************** Archon::Interface::heater *********************************/ - // Get the module and sensorid - // - try { - module = std::stoi( tokens.at(0) ); - sensorid = tokens.at(1); - - if ( sensorid != "A" && sensorid != "B" && sensorid != "C" ) { - message.str(""); message << "invalid sensor " << sensorid << ": expected [ current | AVG [N] ]"; - this->camera.log_error( function, message.str() ); - return ERROR; - } - } catch ( std::invalid_argument & ) { - message.str(""); message << "parsing argument: " << args << ": expected [ current | AVG [N] ]"; - this->camera.log_error( function, message.str() ); - return ERROR; + /**************** Archon::Interface::sensor *********************************/ + /** + * @fn sensor + * @brief set or get temperature sensor current + * @param args contains various allowable strings (see full descsription) + * @return ERROR or NO_ERROR + * + * sensor < A | B | C > [ current ] + * possible args: 2 (get) or 3 (set) + * + * sensor < A | B | C > AVG [ N ] + * possible args: 3 (get) or 4 (set) + * + * Sets or gets the temperature sensor current for the specified + * sensor on the specified module . refers to the + * (integer) module number. is specified in nano-amps. + * This is used only for RTDs + * + * When the AVG arg is used then set or get digital averaging + * + */ + long Interface::sensor(std::string args, std::string &retstring) { + std::string function = "Archon::Interface::sensor"; + std::stringstream message; + std::vector tokens; + std::string sensorid; //!< A | B | C + std::stringstream sensorconfig; //!< configuration line to read or write + std::string sensorvalue; //!< configuration line value + int module; //!< integer module number + bool readonly = true; //!< true is reading, not writing current + long error; + + // must have loaded firmware // TODO implement a command to read the configuration + // // memory from Archon, in order to remove this restriction. + // + if (!this->firmwareloaded) { + this->camera.log_error(function, "firmware not loaded"); + return ERROR; + } - } catch ( std::out_of_range & ) { - message.str(""); message << "argument outside range in " << args << ": expected [ current | AVG [N] ]"; - this->camera.log_error( function, message.str() ); - return ERROR; - } + // requires a minimum backplane version + // + int ret = compare_versions(this->backplaneversion, REV_SENSORCURRENT); + if (ret < 0) { + if (ret == -999) { + message << "comparing backplane version " << this->backplaneversion << " to " << REV_SENSORCURRENT; + } else { + message << "requires backplane version " << REV_SENSORCURRENT << " or newer. (" + << this->backplaneversion << " detected)"; + } + this->camera.log_error(function, message.str()); + return ERROR; + } - // check that the requested module is valid - // - switch ( this->modtype[ module-1 ] ) { - case 0: - message.str(""); message << "module " << module << " not installed"; - this->camera.log_error( function, message.str() ); - return ERROR; - case 5: // Heater - case 11: // HeaterX - break; - default: - message.str(""); message << "module " << module << " is not a heater board"; - this->camera.log_error( function, message.str() ); - return ERROR; - } + std::transform(args.begin(), args.end(), args.begin(), ::toupper); // make uppercase - // input C supported only on HeaterX cards - if ( sensorid == "C" && this->modtype[ module-1 ] != 11 ) { - message.str(""); message << "sensor C not supported on module " << module << ": HeaterX module required"; - this->camera.log_error( function, message.str() ); - return ERROR; - } + Tokenize(args, tokens, " "); - // Now check the number of tokens to decide how to next proceed... + // At minimum there must be two tokens, + // + if (tokens.size() < 2) { + this->camera.log_error(function, "expected at least two arguments: A|B"); + return ERROR; + } - // If there are 2 tokens then must be to read the current, - // < A | B | C > - // - if ( tokens.size() == 2 ) { - readonly = true; - sensorconfig << "MOD" << module << "/SENSOR" << sensorid << "CURRENT"; - - } else if ( tokens.size() == 3 ) { - // If there are 3 tokens then it is either to write the current, - // < A | B | C > current - // or to read the average, - // < A | B | C > AVG - // if the 3rd arg is AVG then read the average (MODmSENSORxFILTER) - // - if ( tokens[2] == "AVG" ) { - readonly = true; - sensorconfig << "MOD" << module << "/SENSOR" << sensorid << "FILTER"; - } else { - // if it's not AVG then assume it's a current value and try to convert it to int - // - int current_val=-1; + // Get the module and sensorid + // try { - current_val = std::stoi( tokens[2] ); + module = std::stoi(tokens.at(0)); + sensorid = tokens.at(1); - } catch ( std::invalid_argument & ) { - message.str(""); message << "parsing \"" << args << "\" : expected \"AVG\" or integer for arg 3"; - this->camera.log_error( function, message.str() ); - return ERROR; - - } catch ( std::out_of_range & ) { - message.str(""); message << "parsing \"" << args << "\" : arg 3 outside integer range"; - this->camera.log_error( function, message.str() ); - return ERROR; + if (sensorid != "A" && sensorid != "B" && sensorid != "C") { + message.str(""); + message << "invalid sensor " << sensorid << ": expected [ current | AVG [N] ]"; + this->camera.log_error(function, message.str()); + return ERROR; + } + } catch (std::invalid_argument &) { + message.str(""); + message << "parsing argument: " << args << ": expected [ current | AVG [N] ]"; + this->camera.log_error(function, message.str()); + return ERROR; + } catch (std::out_of_range &) { + message.str(""); + message << "argument outside range in " << args << ": expected [ current | AVG [N] ]"; + this->camera.log_error(function, message.str()); + return ERROR; } - // successfully converted value so check the range + // check that the requested module is valid // - if ( current_val < 0 || current_val > 1600000 ) { - message.str(""); message << "requested current " << current_val << " outside range {0:1600000}"; - this->camera.log_error( function, message.str() ); - return ERROR; + switch (this->modtype[module - 1]) { + case 0: + message.str(""); + message << "module " << module << " not installed"; + this->camera.log_error(function, message.str()); + return ERROR; + case 5: // Heater + case 11: // HeaterX + break; + default: + message.str(""); + message << "module " << module << " is not a heater board"; + this->camera.log_error(function, message.str()); + return ERROR; + } + + // input C supported only on HeaterX cards + if (sensorid == "C" && this->modtype[module - 1] != 11) { + message.str(""); + message << "sensor C not supported on module " << module << ": HeaterX module required"; + this->camera.log_error(function, message.str()); + return ERROR; } - // prepare sensorconfig string for writing + // Now check the number of tokens to decide how to next proceed... + + // If there are 2 tokens then must be to read the current, + // < A | B | C > // - readonly = false; - sensorconfig << "MOD" << module << "/SENSOR" << sensorid << "CURRENT"; - sensorvalue = tokens[2]; - } - } else if ( tokens.size() == 4 ) { // set avg - // If there are 4 tokens thenn it must be to write the average, - // < A | B | C > AVG N - // check the contents of the 3rd arg - // - if ( tokens[2] != "AVG" ) { - message.str(""); message << "invalid syntax \"" << tokens[2] << "\". expected A|B|C AVG N"; - } + if (tokens.size() == 2) { + readonly = true; + sensorconfig << "MOD" << module << "/SENSOR" << sensorid << "CURRENT"; + } else if (tokens.size() == 3) { + // If there are 3 tokens then it is either to write the current, + // < A | B | C > current + // or to read the average, + // < A | B | C > AVG + // if the 3rd arg is AVG then read the average (MODmSENSORxFILTER) + // + if (tokens[2] == "AVG") { + readonly = true; + sensorconfig << "MOD" << module << "/SENSOR" << sensorid << "FILTER"; + } else { + // if it's not AVG then assume it's a current value and try to convert it to int + // + int current_val = -1; + try { + current_val = std::stoi(tokens[2]); + } catch (std::invalid_argument &) { + message.str(""); + message << "parsing \"" << args << "\" : expected \"AVG\" or integer for arg 3"; + this->camera.log_error(function, message.str()); + return ERROR; + } catch (std::out_of_range &) { + message.str(""); + message << "parsing \"" << args << "\" : arg 3 outside integer range"; + this->camera.log_error(function, message.str()); + return ERROR; + } - // convert the avg value N to int and check for proper value - int filter_val=-1; - try { - filter_val = std::stoi( tokens[3] ); + // successfully converted value so check the range + // + if (current_val < 0 || current_val > 1600000) { + message.str(""); + message << "requested current " << current_val << " outside range {0:1600000}"; + this->camera.log_error(function, message.str()); + return ERROR; + } - } catch ( std::invalid_argument & ) { - message.str(""); message << "parsing \"" << args << "\" : expected integer for arg 4"; - this->camera.log_error( function, message.str() ); - return ERROR; + // prepare sensorconfig string for writing + // + readonly = false; + sensorconfig << "MOD" << module << "/SENSOR" << sensorid << "CURRENT"; + sensorvalue = tokens[2]; + } + } else if (tokens.size() == 4) { + // set avg + // If there are 4 tokens thenn it must be to write the average, + // < A | B | C > AVG N + // check the contents of the 3rd arg + // + if (tokens[2] != "AVG") { + message.str(""); + message << "invalid syntax \"" << tokens[2] << "\". expected A|B|C AVG N"; + } - } catch ( std::out_of_range & ) { - message.str(""); message << "parsing \"" << args << "\" : arg 4 outside integer range"; - this->camera.log_error( function, message.str() ); - return ERROR; - } + // convert the avg value N to int and check for proper value + int filter_val = -1; + try { + filter_val = std::stoi(tokens[3]); + } catch (std::invalid_argument &) { + message.str(""); + message << "parsing \"" << args << "\" : expected integer for arg 4"; + this->camera.log_error(function, message.str()); + return ERROR; + } catch (std::out_of_range &) { + message.str(""); + message << "parsing \"" << args << "\" : arg 4 outside integer range"; + this->camera.log_error(function, message.str()); + return ERROR; + } - // prepare sensorconfig string for writing - // - readonly = false; - sensorconfig << "MOD" << module << "/SENSOR" << sensorid << "FILTER"; - - switch ( filter_val ) { - case 1: sensorvalue = "0"; break; - case 2: sensorvalue = "1"; break; - case 4: sensorvalue = "2"; break; - case 8: sensorvalue = "3"; break; - case 16: sensorvalue = "4"; break; - case 32: sensorvalue = "5"; break; - case 64: sensorvalue = "6"; break; - case 128: sensorvalue = "7"; break; - case 256: sensorvalue = "8"; break; - break; - default: - message.str(""); message << "requested average " << filter_val << " outside range {1,2,4,8,16,32,64,128,256}"; - this->camera.log_error( function, message.str() ); - return ( ERROR ); - } - } else { - // Otherwise an invalid number of tokens - message.str(""); message << "received " << tokens.size() << " arguments but expected 2, 3, or 4"; - this->camera.log_error( function, message.str() ); - return ERROR; - } + // prepare sensorconfig string for writing + // + readonly = false; + sensorconfig << "MOD" << module << "/SENSOR" << sensorid << "FILTER"; + + switch (filter_val) { + case 1: sensorvalue = "0"; + break; + case 2: sensorvalue = "1"; + break; + case 4: sensorvalue = "2"; + break; + case 8: sensorvalue = "3"; + break; + case 16: sensorvalue = "4"; + break; + case 32: sensorvalue = "5"; + break; + case 64: sensorvalue = "6"; + break; + case 128: sensorvalue = "7"; + break; + case 256: sensorvalue = "8"; + break; + break; + default: + message.str(""); + message << "requested average " << filter_val << " outside range {1,2,4,8,16,32,64,128,256}"; + this->camera.log_error(function, message.str()); + return (ERROR); + } + } else { + // Otherwise an invalid number of tokens + message.str(""); + message << "received " << tokens.size() << " arguments but expected 2, 3, or 4"; + this->camera.log_error(function, message.str()); + return ERROR; + } - #ifdef LOGLEVEL_DEBUG +#ifdef LOGLEVEL_DEBUG message.str(""); message << "[DEBUG] module=" << module << " sensorid=" << sensorid << " readonly=" << ( readonly ? "true" : "false" ) << " sensorconfig=" << sensorconfig.str() << " sensorvalue=" << sensorvalue; logwrite( function, message.str() ); - #endif - - std::string sensorkey = sensorconfig.str(); - - if ( ! readonly ) { - - // should be impossible but just make sure these aren't empty because they're needed - // - if ( sensorconfig.rdbuf()->in_avail() == 0 || sensorvalue.empty() ) { - this->camera.log_error( function, "BUG DETECTED: sensorconfig and sensorvalue cannot be empty" ); - return ( ERROR ); - } - - // Write the config line to update the sensor current - // - bool changed = false; - error = this->write_config_key( sensorkey.c_str(), sensorvalue.c_str(), changed ); - - // Now send the APPLYMODx command - // - std::stringstream applystr; - applystr << "APPLYMOD" - << std::setfill('0') - << std::setw(2) - << std::hex - << (module-1); - - if ( error == NO_ERROR ) error = this->archon_cmd( applystr.str() ); - - message.str(""); - - if ( error != NO_ERROR ) { - message << "writing sensor configuration: " << sensorkey << "=" << sensorvalue; - - } else if ( !changed ) { - message << "sensor configuration: " << sensorkey << "=" << sensorvalue << " unchanged"; - - } else { - message << "updated sensor configuration: " << sensorkey << "=" << sensorvalue; - } - logwrite( function, message.str() ); - } - - // now read back the configuration line - // - std::string value; - error = this->get_configmap_value( sensorkey, value ); - - if ( error != NO_ERROR ) { - message.str(""); message << "reading sensor configuration " << sensorkey; - logwrite( function, message.str() ); - return error; - } - - // return value to calling function, passed by reference - // - retstring = value; - - // if key ends with "FILTER" - // then convert the return value {0,1,2,...} to {1,2,4,8,...} - // - if ( sensorkey.substr( sensorkey.length()-6 ) == "FILTER" ) { - - // array of filter values that humans use - // - std::array< std::string, 9 > filter = { "1", "2", "4", "8", "16", "32", "64", "128", "256" }; - - // the value in the configuration is an index into the above array - // - int findex=0; - - try { - findex = std::stoi( value ); - - } catch ( std::invalid_argument & ) { - message.str(""); message << "bad value: " << value << " read back from configuration. expected integer"; - this->camera.log_error( function, message.str() ); - return ERROR; +#endif - } catch ( std::out_of_range & ) { - message.str(""); message << "value: " << value << " read back from configuration outside integer range"; - this->camera.log_error( function, message.str() ); - return ERROR; - } + std::string sensorkey = sensorconfig.str(); - try { - retstring = filter.at( findex ); // return value to calling function, passed by reference + if (!readonly) { + // should be impossible but just make sure these aren't empty because they're needed + // + if (sensorconfig.rdbuf()->in_avail() == 0 || sensorvalue.empty()) { + this->camera.log_error(function, "BUG DETECTED: sensorconfig and sensorvalue cannot be empty"); + return (ERROR); + } - } catch ( std::out_of_range & ) { - message.str(""); message << "filter index " << findex << " outside range: {0:" << filter.size()-1 << "}"; - this->camera.log_error( function, message.str() ); - return ERROR; - } - } + // Write the config line to update the sensor current + // + bool changed = false; + error = this->write_config_key(sensorkey.c_str(), sensorvalue.c_str(), changed); - message.str(""); message << sensorkey << "=" << value << " (" << retstring << ")"; - logwrite( function, message.str() ); + // Now send the APPLYMODx command + // + std::stringstream applystr; + applystr << "APPLYMOD" + << std::setfill('0') + << std::setw(2) + << std::hex + << (module - 1); - return ( error ); - } - /**************** Archon::Interface::sensor *********************************/ - - - /**************** Archon::Interface::bias ***********************************/ - /** - * @fn bias - * @brief set a bias - * @param args contains: module, channel, bias - * @return ERROR or NO_ERROR - * - */ - long Interface::bias(std::string args, std::string &retstring) { - std::string function = "Archon::Interface::bias"; - std::stringstream message; - std::vector tokens; - std::stringstream biasconfig; - int module; - int channel; - float voltage; - float vmin, vmax; - bool readonly=true; - - // must have loaded firmware - // - if ( ! this->firmwareloaded ) { - this->camera.log_error( function, "firmware not loaded" ); - return ERROR; - } + if (error == NO_ERROR) error = this->archon_cmd(applystr.str()); - Tokenize(args, tokens, " "); + message.str(""); - if (tokens.size() == 2) { - readonly = true; + if (error != NO_ERROR) { + message << "writing sensor configuration: " << sensorkey << "=" << sensorvalue; + } else if (!changed) { + message << "sensor configuration: " << sensorkey << "=" << sensorvalue << " unchanged"; + } else { + message << "updated sensor configuration: " << sensorkey << "=" << sensorvalue; + } + logwrite(function, message.str()); + } - } else if (tokens.size() == 3) { - readonly = false; + // now read back the configuration line + // + std::string value; + error = this->get_configmap_value(sensorkey, value); - } else { - message.str(""); message << "incorrect number of arguments: " << args << ": expected module channel [voltage]"; - this->camera.log_error( function, message.str() ); - return ERROR; - } + if (error != NO_ERROR) { + message.str(""); + message << "reading sensor configuration " << sensorkey; + logwrite(function, message.str()); + return error; + } - std::transform( args.begin(), args.end(), args.begin(), ::toupper ); // make uppercase + // return value to calling function, passed by reference + // + retstring = value; - try { - module = std::stoi( tokens[0] ); - channel = std::stoi( tokens[1] ); - if (!readonly) voltage = std::stof( tokens[2] ); + // if key ends with "FILTER" + // then convert the return value {0,1,2,...} to {1,2,4,8,...} + // + if (sensorkey.substr(sensorkey.length() - 6) == "FILTER") { + // array of filter values that humans use + // + std::array filter = {"1", "2", "4", "8", "16", "32", "64", "128", "256"}; - } catch (std::invalid_argument &) { - message.str(""); message << "parsing bias arguments: " << args << ": expected [ voltage ]"; - this->camera.log_error( function, message.str() ); - return ERROR; + // the value in the configuration is an index into the above array + // + int findex = 0; - } catch (std::out_of_range &) { - message.str(""); message << "argument range: " << args << ": expected [ voltage ]"; - this->camera.log_error( function, message.str() ); - return ERROR; - } + try { + findex = std::stoi(value); + } catch (std::invalid_argument &) { + message.str(""); + message << "bad value: " << value << " read back from configuration. expected integer"; + this->camera.log_error(function, message.str()); + return ERROR; + } catch (std::out_of_range &) { + message.str(""); + message << "value: " << value << " read back from configuration outside integer range"; + this->camera.log_error(function, message.str()); + return ERROR; + } - // Check that the module number is valid - // - if ( (module < 0) || (module > nmods) ) { - message.str(""); message << "module " << module << ": outside range {0:" << nmods << "}"; - this->camera.log_error( function, message.str() ); - return ERROR; - } + try { + retstring = filter.at(findex); // return value to calling function, passed by reference + } catch (std::out_of_range &) { + message.str(""); + message << "filter index " << findex << " outside range: {0:" << filter.size() - 1 << "}"; + this->camera.log_error(function, message.str()); + return ERROR; + } + } - // Use the module type to get LV or HV Bias - // and start building the bias configuration string. - // - switch ( this->modtype[ module-1 ] ) { - case 0: - message.str(""); message << "module " << module << " not installed"; - this->camera.log_error( function, message.str() ); - return ERROR; - break; - case 3: // LVBias - case 9: // LVXBias - biasconfig << "MOD" << module << "/LV"; - vmin = -14.0; - vmax = +14.0; - break; - case 4: // HVBias - case 8: // HVXBias - biasconfig << "MOD" << module << "/HV"; - vmin = 0.0; - vmax = +31.0; - break; - default: - message.str(""); message << "module " << module << " not a bias board"; - this->camera.log_error( function, message.str() ); - return ERROR; - break; - } + message.str(""); + message << sensorkey << "=" << value << " (" << retstring << ")"; + logwrite(function, message.str()); - // Check that the channel number is valid - // and add it to the bias configuration string. - // - if ( (channel < 1) || (channel > 30) ) { - message.str(""); message << "bias channel " << module << ": outside range {1:30}"; - this->camera.log_error( function, message.str() ); - return ERROR; - } - if ( (channel > 0) && (channel < 25) ) { - biasconfig << "LC_V" << channel; - } - if ( (channel > 24) && (channel < 31) ) { - channel -= 24; - biasconfig << "HC_V" << channel; + return (error); } - if ( (voltage < vmin) || (voltage > vmax) ) { - message.str(""); message << "bias voltage " << voltage << ": outside range {" << vmin << ":" << vmax << "}"; - this->camera.log_error( function, message.str() ); - return ERROR; - } + /**************** Archon::Interface::sensor *********************************/ - // Locate this line in the configuration so that it can be written to the Archon - // - std::string key = biasconfig.str(); - std::string value = std::to_string(voltage); - bool changed = false; - long error; - // If no voltage suppled (readonly) then just read the configuration and exit - // - if (readonly) { - message.str(""); - error = this->get_configmap_value(key, voltage); - if (error != NO_ERROR) { - message << "reading bias " << key; - - } else { - retstring = std::to_string(voltage); - message << "read bias " << key << "=" << voltage; - } - logwrite( function, message.str() ); - return error; - } + /**************** Archon::Interface::bias ***********************************/ + /** + * @fn bias + * @brief set a bias + * @param args contains: module, channel, bias + * @return ERROR or NO_ERROR + * + */ + long Interface::bias(std::string args, std::string &retstring) { + std::string function = "Archon::Interface::bias"; + std::stringstream message; + std::vector tokens; + std::stringstream biasconfig; + int module; + int channel; + float voltage; + float vmin, vmax; + bool readonly = true; + + // must have loaded firmware + // + if (!this->firmwareloaded) { + this->camera.log_error(function, "firmware not loaded"); + return ERROR; + } - // Write the config line to update the bias voltage - // - error = this->write_config_key(key.c_str(), value.c_str(), changed); + Tokenize(args, tokens, " "); - // Now send the APPLYMODx command - // - std::stringstream applystr; - applystr << "APPLYMOD" - << std::setfill('0') - << std::setw(2) - << std::hex - << (module-1); + if (tokens.size() == 2) { + readonly = true; + } else if (tokens.size() == 3) { + readonly = false; + } else { + message.str(""); + message << "incorrect number of arguments: " << args << ": expected module channel [voltage]"; + this->camera.log_error(function, message.str()); + return ERROR; + } - if (error == NO_ERROR) error = this->archon_cmd(applystr.str()); + std::transform(args.begin(), args.end(), args.begin(), ::toupper); // make uppercase - if (error != NO_ERROR) { - message << "writing bias configuration: " << key << "=" << value; + try { + module = std::stoi(tokens[0]); + channel = std::stoi(tokens[1]); + if (!readonly) voltage = std::stof(tokens[2]); + } catch (std::invalid_argument &) { + message.str(""); + message << "parsing bias arguments: " << args << ": expected [ voltage ]"; + this->camera.log_error(function, message.str()); + return ERROR; + } catch (std::out_of_range &) { + message.str(""); + message << "argument range: " << args << ": expected [ voltage ]"; + this->camera.log_error(function, message.str()); + return ERROR; + } - } else if (!changed) { - message << "bias configuration: " << key << "=" << value <<" unchanged"; + // Check that the module number is valid + // + if ((module < 0) || (module > nmods)) { + message.str(""); + message << "module " << module << ": outside range {0:" << nmods << "}"; + this->camera.log_error(function, message.str()); + return ERROR; + } - } else { - message << "updated bias configuration: " << key << "=" << value; - } + // Use the module type to get LV or HV Bias + // and start building the bias configuration string. + // + switch (this->modtype[module - 1]) { + case 0: + message.str(""); + message << "module " << module << " not installed"; + this->camera.log_error(function, message.str()); + return ERROR; + break; + case 3: // LVBias + case 9: // LVXBias + biasconfig << "MOD" << module << "/LV"; + vmin = -14.0; + vmax = +14.0; + break; + case 4: // HVBias + case 8: // HVXBias + biasconfig << "MOD" << module << "/HV"; + vmin = 0.0; + vmax = +31.0; + break; + default: + message.str(""); + message << "module " << module << " not a bias board"; + this->camera.log_error(function, message.str()); + return ERROR; + break; + } - logwrite(function, message.str()); + // Check that the channel number is valid + // and add it to the bias configuration string. + // + if ((channel < 1) || (channel > 30)) { + message.str(""); + message << "bias channel " << module << ": outside range {1:30}"; + this->camera.log_error(function, message.str()); + return ERROR; + } + if ((channel > 0) && (channel < 25)) { + biasconfig << "LC_V" << channel; + } + if ((channel > 24) && (channel < 31)) { + channel -= 24; + biasconfig << "HC_V" << channel; + } - return error; - } - /**************** Archon::Interface::bias ***********************************/ - - - /**************** Archon::Interface::cds ************************************/ - /** - * @fn cds - * @brief set / get CDS parameters - * @param - * @return ERROR or NO_ERROR - * - */ - long Interface::cds(std::string args, std::string &retstring) { - std::string function = "Archon::Interface::cds"; - std::stringstream message; - std::vector tokens; - std::string key, value; - bool changed; - long error = ERROR; - - if ( args.empty() ) { - this->camera.log_error( function, "no argument: expected cds [ value ]" ); - return ERROR; - } + if ((voltage < vmin) || (voltage > vmax)) { + message.str(""); + message << "bias voltage " << voltage << ": outside range {" << vmin << ":" << vmax << "}"; + this->camera.log_error(function, message.str()); + return ERROR; + } - try { - Tokenize(args, tokens, " "); + // Locate this line in the configuration so that it can be written to the Archon + // + std::string key = biasconfig.str(); + std::string value = std::to_string(voltage); + bool changed = false; + long error; - // One token -- - // get the configuration key value - // - if ( tokens.size() == 1 ) { - key = tokens.at(0); - std::transform( key.begin(), key.end(), key.begin(), ::toupper ); // make uppercase - error = this->get_configmap_value(key, retstring); // read - } - else - - // Two tokens -- - // set the configuration key to value, send APPLYCDS, then read back the config key - // - if ( tokens.size() == 2 ) { - key = tokens.at(0); - std::transform( key.begin(), key.end(), key.begin(), ::toupper ); // make uppercase - value = tokens.at(1); - error = this->write_config_key( key.c_str(), value.c_str(), changed ); // set - if (error == NO_ERROR) error = this->archon_cmd(APPLYCDS); // apply - if (error == NO_ERROR) error = this->get_configmap_value(key, retstring); // read back - - } else { - // More than two tokens is an error - this->camera.log_error( function, "Too many arguments. Expected cds [ value ]" ); - return ERROR; - } + // If no voltage suppled (readonly) then just read the configuration and exit + // + if (readonly) { + message.str(""); + error = this->get_configmap_value(key, voltage); + if (error != NO_ERROR) { + message << "reading bias " << key; + } else { + retstring = std::to_string(voltage); + message << "read bias " << key << "=" << voltage; + } + logwrite(function, message.str()); + return error; + } - } catch(std::out_of_range &) { - message << "parsing cds arguments: " << args << ": Expected cds [ value ]"; - this->camera.log_error( function, message.str() ); - return ERROR; + // Write the config line to update the bias voltage + // + error = this->write_config_key(key.c_str(), value.c_str(), changed); - } catch(...) { - message << "unknown exception parsing cds arguments: " << args << ": Expected cds [ value ]"; - this->camera.log_error( function, message.str() ); - return ERROR; - } + // Now send the APPLYMODx command + // + std::stringstream applystr; + applystr << "APPLYMOD" + << std::setfill('0') + << std::setw(2) + << std::hex + << (module - 1); - return error; - } - /**************** Archon::Interface::cds ************************************/ - - - /**************** Archon::Interface::inreg **********************************/ - /** - * @fn inreg - * @brief write to a VCPU INREGi - * @param args contains: module inreg value - * @return ERROR or NO_ERROR - * - */ - long Interface::inreg( std::string args ) { - std::string function = "Archon::Interface::inreg"; - std::stringstream message; - std::vector tokens; - int module, reg, value; - - // must have loaded firmware // TODO implement a command to read the configuration - // // memory from Archon, in order to remove this restriction. - // - if ( ! this->firmwareloaded ) { - this->camera.log_error( function, "firmware not loaded" ); - return ERROR; - } + if (error == NO_ERROR) error = this->archon_cmd(applystr.str()); - // VCPU requires a minimum backplane version - // - int ret = compare_versions( this->backplaneversion, REV_VCPU ); - if ( ret < 0 ) { - if ( ret == -999 ) { - message << "comparing backplane version " << this->backplaneversion << " to " << REV_VCPU; - - } else { - message << "requires backplane version " << REV_VCPU << " or newer. (" - << this->backplaneversion << " detected)"; - } - this->camera.log_error( function, message.str() ); - return ERROR; - } + if (error != NO_ERROR) { + message << "writing bias configuration: " << key << "=" << value; + } else if (!changed) { + message << "bias configuration: " << key << "=" << value << " unchanged"; + } else { + message << "updated bias configuration: " << key << "=" << value; + } - Tokenize(args, tokens, " "); + logwrite(function, message.str()); - // There must be three tokens, - // - if ( tokens.size() != 3 ) { - this->camera.log_error( function, "expected three arguments: " ); - return ERROR; + return error; } - // Now get the module and - // which will be common to everything. - // - try { - module = std::stoi( tokens[0] ); - reg = std::stoi( tokens[1] ); - value = std::stoi( tokens[2] ); - } - catch (std::invalid_argument &) { - message.str(""); message << "unable to convert one of \"" << args << "\" to integer"; - this->camera.log_error( function, message.str() ); - return ERROR; - } - catch (std::out_of_range &) { - message.str(""); message << "one of \"" << args << "\" is outside integer range"; - this->camera.log_error( function, message.str() ); - return ERROR; - } + /**************** Archon::Interface::bias ***********************************/ - // check that requested module is valid - // - switch ( this->modtype[ module-1 ] ) { - case 0: - message.str(""); message << "requested module " << module << " not installed"; - this->camera.log_error( function, message.str() ); - return ERROR; - case 3: // LVBias - case 5: // Heater - case 7: // HS - case 9: // LVXBias - case 10: // LVDS - case 11: // HeaterX - break; - default: - message.str(""); message << "requested module " << module << " does not contain a VCPU"; - this->camera.log_error( function, message.str() ); - return ERROR; - } - // check that register number is valid - // - if ( reg < 0 || reg > 15 ) { - message.str(""); message << "requested register " << reg << " outside range {0:15}"; - this->camera.log_error( function, message.str() ); - return ERROR; - } + /**************** Archon::Interface::cds ************************************/ + /** + * @fn cds + * @brief set / get CDS parameters + * @param + * @return ERROR or NO_ERROR + * + */ + long Interface::cds(std::string args, std::string &retstring) { + std::string function = "Archon::Interface::cds"; + std::stringstream message; + std::vector tokens; + std::string key, value; + bool changed; + long error = ERROR; - // check that value is within range - // - if ( value < 0 || value > 65535 ) { - message.str(""); message << "requested value " << value << " outside range {0:65535}"; - this->camera.log_error( function, message.str() ); - return ERROR; - } + if (args.empty()) { + this->camera.log_error(function, "no argument: expected cds [ value ]"); + return ERROR; + } - std::stringstream inreg_key; - bool changed = false; - inreg_key << "MOD" << module << "/VCPU_INREG" << reg; - long error = this->write_config_key( inreg_key.str().c_str(), value, changed ); - if ( error != NO_ERROR ) { - message.str(""); message << "configuration " << inreg_key.str() << "=" << value; - logwrite( function, message.str() ); - return ERROR; - - } else { - std::stringstream applystr; - applystr << "APPLYDIO" - << std::setfill('0') - << std::setw(2) - << std::hex - << (module-1); - error = this->archon_cmd( applystr.str() ); - return error; - } - } - /**************** Archon::Interface::inreg **********************************/ - - - /**************** Archon::Interface::test ***********************************/ - /** - * @fn test - * @brief test routines - * @param string args contains test name and arguments - * @param reference to retstring for any return values - * @return ERROR or NO_ERROR - * - * This is the place to put various debugging and system testing tools. - * It is placed here, rather than in camera, to allow for controller- - * specific tests. This means some common tests may need to be duplicated - * for each controller. - * - * These are kept out of server.cpp so that I don't need a separate, - * potentially conflicting command for each test I come up with, and also - * reduces the chance of someone accidentally, inadvertently entering one - * of these test commands. - * - * The server command is "test", the next parameter is the test name, - * and any parameters needed for the particular test are extracted as - * tokens from the args string passed in. - * - * The input args string is tokenized and tests are separated by a simple - * series of if..else.. conditionals. - * - * current tests are: - * ampinfo - print what is known about the amplifiers - * busy - Override the archon_busy flag - * fitsname - show what the fitsname will look like - * builddate - log the build date - * async - queue an asynchronous status message - * modules - Log all installed modules and their types - * parammap - Log all parammap entries found in the ACF file - * configmap - Log all configmap entries found in the ACF file - * bw - tests the exposure sequence bandwidth by running a sequence of exposures - * timer - test Archon time against system time - */ - long Interface::test(std::string args, std::string &retstring) { - std::string function = "Archon::Interface::test"; - std::stringstream message; - std::vector tokens; - long error; - - Tokenize(args, tokens, " "); - - if (tokens.empty()) { - this->camera.log_error( function, "no test name provided" ); - return ERROR; - } + try { + Tokenize(args, tokens, " "); - std::string testname; - /* the first token is the test name */ - try { - testname = tokens.at(0); + // One token -- + // get the configuration key value + // + if (tokens.size() == 1) { + key = tokens.at(0); + std::transform(key.begin(), key.end(), key.begin(), ::toupper); // make uppercase + error = this->get_configmap_value(key, retstring); // read + } else + + // Two tokens -- + // set the configuration key to value, send APPLYCDS, then read back the config key + // + if (tokens.size() == 2) { + key = tokens.at(0); + std::transform(key.begin(), key.end(), key.begin(), ::toupper); // make uppercase + value = tokens.at(1); + error = this->write_config_key(key.c_str(), value.c_str(), changed); // set + if (error == NO_ERROR) error = this->archon_cmd(APPLYCDS); // apply + if (error == NO_ERROR) error = this->get_configmap_value(key, retstring); // read back + } else { + // More than two tokens is an error + this->camera.log_error(function, "Too many arguments. Expected cds [ value ]"); + return ERROR; + } + } catch (std::out_of_range &) { + message << "parsing cds arguments: " << args << ": Expected cds [ value ]"; + this->camera.log_error(function, message.str()); + return ERROR; + } catch (...) { + message << "unknown exception parsing cds arguments: " << args << ": Expected cds [ value ]"; + this->camera.log_error(function, message.str()); + return ERROR; + } - } catch ( std::out_of_range & ) { - this->camera.log_error( function, "testname token out of range" ); - return ERROR; + return error; } - // ---------------------------------------------------- - // ampinfo - // ---------------------------------------------------- - // print what is known about the amplifiers - // - if (testname == "ampinfo") { - - std::string mode = this->camera_info.current_observing_mode; - int framemode = this->modemap[mode].geometry.framemode; - - message.str(""); message << "[ampinfo] observing mode=" << mode; - logwrite( function, message.str() ); - message.str(""); message << "[ampinfo] FRAMEMODE=" << this->modemap[mode].geometry.framemode; - logwrite( function, message.str() ); - message.str(""); message << "[ampinfo] LINECOUNT=" << this->modemap[mode].geometry.linecount << " PIXELCOUNT=" << this->modemap[mode].geometry.pixelcount; - logwrite( function, message.str() ); - message.str(""); message << "[ampinfo] num_taps=" << this->modemap[mode].tapinfo.num_taps; - logwrite( function, message.str() ); - message.str(""); message << "[ampinfo] hori_amps=" << this->modemap[mode].geometry.amps[0] << " vert_amps=" << this->modemap[mode].geometry.amps[1]; - logwrite( function, message.str() ); + /**************** Archon::Interface::cds ************************************/ - message.str(""); message << "[ampinfo] gains ="; - for ( auto gn : this->gain ) { - message << " " << gn; - } - logwrite( function, message.str() ); - int rows = this->modemap[mode].geometry.linecount; - int cols = this->modemap[mode].geometry.pixelcount; + /**************** Archon::Interface::inreg **********************************/ + /** + * @fn inreg + * @brief write to a VCPU INREGi + * @param args contains: module inreg value + * @return ERROR or NO_ERROR + * + */ + long Interface::inreg(std::string args) { + std::string function = "Archon::Interface::inreg"; + std::stringstream message; + std::vector tokens; + int module, reg, value; - int hamps = this->modemap[mode].geometry.amps[0]; - int vamps = this->modemap[mode].geometry.amps[1]; + // must have loaded firmware // TODO implement a command to read the configuration + // // memory from Archon, in order to remove this restriction. + // + if (!this->firmwareloaded) { + this->camera.log_error(function, "firmware not loaded"); + return ERROR; + } - int x0=-1, x1, y0, y1; + // VCPU requires a minimum backplane version + // + int ret = compare_versions(this->backplaneversion, REV_VCPU); + if (ret < 0) { + if (ret == -999) { + message << "comparing backplane version " << this->backplaneversion << " to " << REV_VCPU; + } else { + message << "requires backplane version " << REV_VCPU << " or newer. (" + << this->backplaneversion << " detected)"; + } + this->camera.log_error(function, message.str()); + return ERROR; + } - for ( int y=0; y + // + if (tokens.size() != 3) { + this->camera.log_error(function, "expected three arguments: "); + return ERROR; } - } - error = NO_ERROR; - - } else if (testname == "busy") { - // ---------------------------------------------------- - // busy - // ---------------------------------------------------- - // Override the archon_busy flag to test system responsiveness - // when the Archon is busy. - if ( tokens.size() == 1 ) { - message.str(""); message << "archon_busy=" << this->archon_busy; - logwrite( function, message.str() ); - error = NO_ERROR; - } else if ( tokens.size() == 2 ) { + // Now get the module and + // which will be common to everything. + // try { - this->archon_busy = tokens.at(1) == "yes"; - - } catch ( std::out_of_range & ) { - this->camera.log_error( function, "tokens out of range" ); - error=ERROR; + module = std::stoi(tokens[0]); + reg = std::stoi(tokens[1]); + value = std::stoi(tokens[2]); + } catch (std::invalid_argument &) { + message.str(""); + message << "unable to convert one of \"" << args << "\" to integer"; + this->camera.log_error(function, message.str()); + return ERROR; + } + catch (std::out_of_range &) { + message.str(""); + message << "one of \"" << args << "\" is outside integer range"; + this->camera.log_error(function, message.str()); + return ERROR; } - message.str(""); message << "archon_busy=" << this->archon_busy; - logwrite( function, message.str() ); - error = NO_ERROR; - } else { - message.str(""); message << "expected one argument, yes or no"; - this->camera.log_error( function, message.str() ); - error = ERROR; - } - retstring = this->archon_busy ? "true" : "false"; + // check that requested module is valid + // + switch (this->modtype[module - 1]) { + case 0: + message.str(""); + message << "requested module " << module << " not installed"; + this->camera.log_error(function, message.str()); + return ERROR; + case 3: // LVBias + case 5: // Heater + case 7: // HS + case 9: // LVXBias + case 10: // LVDS + case 11: // HeaterX + break; + default: + message.str(""); + message << "requested module " << module << " does not contain a VCPU"; + this->camera.log_error(function, message.str()); + return ERROR; + } - } else if (testname == "fitsname") { - // ---------------------------------------------------- - // fitsname - // ---------------------------------------------------- - // Show what the fitsname will look like. - // This is a "test" rather than a regular command so that it doesn't get mistaken - // for returning a real, usable filename. When using fitsnaming=time, the filename - // has to be generated at the moment the file is opened. - std::string msg; - this->camera.set_fitstime( get_timestamp() ); // must set camera.fitstime first - error = this->camera.get_fitsname(msg); // get the fitsname (by reference) - retstring = msg; - message.str(""); message << "NOTICE:" << msg; - this->camera.async.enqueue( message.str() ); // queue the fitsname - logwrite(function, msg); // log the fitsname - if (error!=NO_ERROR) { - this->camera.log_error( function, "couldn't validate fits filename" ); - } - // end if (testname == fitsname) + // check that register number is valid + // + if (reg < 0 || reg > 15) { + message.str(""); + message << "requested register " << reg << " outside range {0:15}"; + this->camera.log_error(function, message.str()); + return ERROR; + } - } else if ( testname == "builddate" ) { - // ---------------------------------------------------- - // builddate - // ---------------------------------------------------- - // log the build date - std::string build(BUILD_DATE); build.append(" "); build.append(BUILD_TIME); - retstring = build; - error = NO_ERROR; - logwrite( function, build ); - // end if ( testname == builddate ) - - } else if (testname == "async") { - // ---------------------------------------------------- - // async [message] - // ---------------------------------------------------- - // queue an asynchronous message - // The [message] param is optional. If not provided then "test" is queued. - if (tokens.size() > 1) { - if (tokens.size() > 2) { - logwrite(function, "NOTICE:received multiple strings -- only the first will be queued"); + // check that value is within range + // + if (value < 0 || value > 65535) { + message.str(""); + message << "requested value " << value << " outside range {0:65535}"; + this->camera.log_error(function, message.str()); + return ERROR; } - try { message.str(""); message << "NOTICE:" << tokens.at(1); - logwrite( function, message.str() ); - this->camera.async.enqueue( message.str() ); - } catch ( std::out_of_range & ) { - this->camera.log_error( function, "tokens out of range" ); - error=ERROR; + std::stringstream inreg_key; + bool changed = false; + inreg_key << "MOD" << module << "/VCPU_INREG" << reg; + long error = this->write_config_key(inreg_key.str().c_str(), value, changed); + if (error != NO_ERROR) { + message.str(""); + message << "configuration " << inreg_key.str() << "=" << value; + logwrite(function, message.str()); + return ERROR; + } else { + std::stringstream applystr; + applystr << "APPLYDIO" + << std::setfill('0') + << std::setw(2) + << std::hex + << (module - 1); + error = this->archon_cmd(applystr.str()); + return error; } + } - } else { // if no string passed then queue a simple test message - logwrite(function, "NOTICE:test"); - this->camera.async.enqueue("NOTICE:test"); - } - error = NO_ERROR; - // end if (testname == async) + /**************** Archon::Interface::inreg **********************************/ - } else if (testname == "modules") { - // ---------------------------------------------------- - // modules - // ---------------------------------------------------- - // Log all installed modules - logwrite( function, "installed module types: " ); - message.str(""); - for ( const auto &mod : this->modtype ) { - message << mod << " "; - } - logwrite( function, message.str() ); - retstring = message.str(); - error = NO_ERROR; - } else if (testname == "parammap") { - // ---------------------------------------------------- - // parammap - // ---------------------------------------------------- - // Log all parammap entries found in the ACF file - - // loop through the modes - // - logwrite(function, "parammap entries by mode section:"); - for (auto & mode_it : this->modemap) { - std::string mode = mode_it.first; - message.str(""); message << "found mode section " << mode; - logwrite(function, message.str()); - for (auto param_it = this->modemap[mode].parammap.begin(); param_it != this->modemap[mode].parammap.end(); ++param_it) { - message.str(""); message << "MODE_" << mode << ": " << param_it->first << "=" << param_it->second.value; - logwrite(function, message.str()); - } - } + /**************** Archon::Interface::test ***********************************/ + /** + * @fn test + * @brief test routines + * @param string args contains test name and arguments + * @param reference to retstring for any return values + * @return ERROR or NO_ERROR + * + * This is the place to put various debugging and system testing tools. + * It is placed here, rather than in camera, to allow for controller- + * specific tests. This means some common tests may need to be duplicated + * for each controller. + * + * These are kept out of server.cpp so that I don't need a separate, + * potentially conflicting command for each test I come up with, and also + * reduces the chance of someone accidentally, inadvertently entering one + * of these test commands. + * + * The server command is "test", the next parameter is the test name, + * and any parameters needed for the particular test are extracted as + * tokens from the args string passed in. + * + * The input args string is tokenized and tests are separated by a simple + * series of if..else.. conditionals. + * + * current tests are: + * ampinfo - print what is known about the amplifiers + * busy - Override the archon_busy flag + * fitsname - show what the fitsname will look like + * builddate - log the build date + * async - queue an asynchronous status message + * modules - Log all installed modules and their types + * parammap - Log all parammap entries found in the ACF file + * configmap - Log all configmap entries found in the ACF file + * bw - tests the exposure sequence bandwidth by running a sequence of exposures + * timer - test Archon time against system time + */ + long Interface::test(std::string args, std::string &retstring) { + std::string function = "Archon::Interface::test"; + std::stringstream message; + std::vector tokens; + long error; - logwrite(function, "ALL parammap entries in ACF:"); - int keycount=0; - for (auto & param_it : this->parammap) { - keycount++; - message.str(""); message << param_it.first << "=" << param_it.second.value; - logwrite(function, message.str()); - this->camera.async.enqueue( "NOTICE:"+message.str() ); - } - message.str(""); message << "found " << keycount << " parammap entries"; - logwrite(function, message.str()); - error = NO_ERROR; - // end if (testname == parammap) + Tokenize(args, tokens, " "); - } else if (testname == "configmap") { - // ---------------------------------------------------- - // configmap - // ---------------------------------------------------- - // Log all configmap entries found in the ACF file - error = NO_ERROR; - logwrite(function, "configmap entries by mode section:"); - for (auto & mode_it : this->modemap) { - std::string mode = mode_it.first; - message.str(""); message << "found mode section " << mode; - logwrite(function, message.str()); - for (auto config_it = this->modemap[mode].configmap.begin(); config_it != this->modemap[mode].configmap.end(); ++config_it) { - message.str(""); message << "MODE_" << mode << ": " << config_it->first << "=" << config_it->second.value; - logwrite(function, message.str()); + if (tokens.empty()) { + this->camera.log_error(function, "no test name provided"); + return ERROR; } - } - // if a second argument was passed then this is a config key - // try to read it - // - if ( tokens.size() == 2 ) { + std::string testname; + /* the first token is the test name */ try { - std::string configkey = tokens.at(1); - error = this->get_configmap_value(configkey, retstring); - - } catch ( std::out_of_range & ) { - this->camera.log_error( function, "configkey token out of range" ); - error=ERROR; - } - } - - // if a third argument was passed then set this configkey - // - if ( tokens.size() == 3 ) { - try { std::string key = tokens.at(1); - std::string value = tokens.at(2); - bool configchanged; - error = this->write_config_key( key.c_str(), value.c_str(), configchanged ); - if (error == NO_ERROR) error = this->archon_cmd(APPLYCDS); - - } catch ( std::out_of_range & ) { - this->camera.log_error( function, "key,value tokens out of range" ); - error=ERROR; + testname = tokens.at(0); + } catch (std::out_of_range &) { + this->camera.log_error(function, "testname token out of range"); + return ERROR; } - } - int keycount=0; - for (auto config_it = this->configmap.begin(); config_it != this->configmap.end(); ++config_it) { - keycount++; - } - message.str(""); message << "found " << keycount << " configmap entries"; - logwrite(function, message.str()); - // end if (testname == configmap) - - } else if (testname == "bw") { // ---------------------------------------------------- - // bw + // ampinfo // ---------------------------------------------------- - // Bandwidth test - // This tests the exposure sequence bandwidth by running a sequence - // of exposures, including reading the frame buffer -- everything except - // for the fits file writing. - - if ( ! this->modeselected ) { - this->camera.log_error( function, "no mode selected" ); - return ERROR; - } - - std::string nseqstr; - int nseq; - bool ro=false; // read only - bool rw=false; // read and write + // print what is known about the amplifiers + // + if (testname == "ampinfo") { + std::string mode = this->camera_info.current_observing_mode; + int framemode = this->modemap[mode].geometry.framemode; - if (tokens.size() > 1) { - try { - nseqstr = tokens.at(1); + message.str(""); + message << "[ampinfo] observing mode=" << mode; + logwrite(function, message.str()); + message.str(""); + message << "[ampinfo] FRAMEMODE=" << this->modemap[mode].geometry.framemode; + logwrite(function, message.str()); + message.str(""); + message << "[ampinfo] LINECOUNT=" << this->modemap[mode].geometry.linecount << " PIXELCOUNT=" << this-> + modemap[mode].geometry.pixelcount; + logwrite(function, message.str()); + message.str(""); + message << "[ampinfo] num_taps=" << this->modemap[mode].tapinfo.num_taps; + logwrite(function, message.str()); + message.str(""); + message << "[ampinfo] hori_amps=" << this->modemap[mode].geometry.amps[0] << " vert_amps=" << this->modemap[ + mode].geometry.amps[1]; + logwrite(function, message.str()); - } catch ( std::out_of_range & ) { - this->camera.log_error( function, "nseqstr token out of range" ); - return ERROR; - } + message.str(""); + message << "[ampinfo] gains ="; + for (auto gn: this->gain) { + message << " " << gn; + } + logwrite(function, message.str()); - } else { - this->camera.log_error( function, "usage: test bw [ rw | ro ]"); - return ERROR; - } + int rows = this->modemap[mode].geometry.linecount; + int cols = this->modemap[mode].geometry.pixelcount; - if (tokens.size() > 2) { - try { if (tokens.at(2) == "rw") rw=true; else rw=false; - if (tokens.at(2) == "ro") ro=true; else ro=false; + int hamps = this->modemap[mode].geometry.amps[0]; + int vamps = this->modemap[mode].geometry.amps[1]; - } catch ( std::out_of_range & ) { - this->camera.log_error( function, "rw tokens out of range" ); - error=ERROR; - } - } + int x0 = -1, x1, y0, y1; - try { - nseq = std::stoi( nseqstr ); // test that nseqstr is an integer before trying to use it + for (int y = 0; y < vamps; y++) { + for (int x = 0; x < hamps; x++) { + if (framemode == 2) { + x0 = x; + x1 = x + 1; + y0 = y; + y1 = y + 1; + } else { + x0++; + x1 = x0 + 1; + y0 = 0; + y1 = 1; + } + message.str(""); + message << "[ampinfo] x0=" << x0 << " x1=" << x1 << " y0=" << y0 << " y1=" << y1 + << " | amp section (xrange, yrange) " << (x0 * cols + 1) << ":" << (x1) * cols << ", " << ( + y0 * rows + 1) << ":" << (y1) * rows; + logwrite(function, message.str()); + } + } + error = NO_ERROR; + } else if (testname == "busy") { + // ---------------------------------------------------- + // busy + // ---------------------------------------------------- + // Override the archon_busy flag to test system responsiveness + // when the Archon is busy. + if (tokens.size() == 1) { + message.str(""); + message << "archon_busy=" << this->archon_busy; + logwrite(function, message.str()); + error = NO_ERROR; + } else if (tokens.size() == 2) { + try { + this->archon_busy = tokens.at(1) == "yes"; + } catch (std::out_of_range &) { + this->camera.log_error(function, "tokens out of range"); + error = ERROR; + } + message.str(""); + message << "archon_busy=" << this->archon_busy; + logwrite(function, message.str()); + error = NO_ERROR; + } else { + message.str(""); + message << "expected one argument, yes or no"; + this->camera.log_error(function, message.str()); + error = ERROR; + } + retstring = this->archon_busy ? "true" : "false"; + } else if (testname == "fitsname") { + // ---------------------------------------------------- + // fitsname + // ---------------------------------------------------- + // Show what the fitsname will look like. + // This is a "test" rather than a regular command so that it doesn't get mistaken + // for returning a real, usable filename. When using fitsnaming=time, the filename + // has to be generated at the moment the file is opened. + std::string msg; + this->camera.set_fitstime(get_timestamp()); // must set camera.fitstime first + error = this->camera.get_fitsname(msg); // get the fitsname (by reference) + retstring = msg; + message.str(""); + message << "NOTICE:" << msg; + this->camera.async.enqueue(message.str()); // queue the fitsname + logwrite(function, msg); // log the fitsname + if (error != NO_ERROR) { + this->camera.log_error(function, "couldn't validate fits filename"); + } + // end if (testname == fitsname) + } else if (testname == "builddate") { + // ---------------------------------------------------- + // builddate + // ---------------------------------------------------- + // log the build date + std::string build(BUILD_DATE); + build.append(" "); + build.append(BUILD_TIME); + retstring = build; + error = NO_ERROR; + logwrite(function, build); + // end if ( testname == builddate ) + } else if (testname == "async") { + // ---------------------------------------------------- + // async [message] + // ---------------------------------------------------- + // queue an asynchronous message + // The [message] param is optional. If not provided then "test" is queued. + if (tokens.size() > 1) { + if (tokens.size() > 2) { + logwrite(function, "NOTICE:received multiple strings -- only the first will be queued"); + } + try { + message.str(""); + message << "NOTICE:" << tokens.at(1); + logwrite(function, message.str()); + this->camera.async.enqueue(message.str()); + } catch (std::out_of_range &) { + this->camera.log_error(function, "tokens out of range"); + error = ERROR; + } + } else { + // if no string passed then queue a simple test message + logwrite(function, "NOTICE:test"); + this->camera.async.enqueue("NOTICE:test"); + } + error = NO_ERROR; + // end if (testname == async) + } else if (testname == "modules") { + // ---------------------------------------------------- + // modules + // ---------------------------------------------------- + // Log all installed modules + logwrite(function, "installed module types: "); + message.str(""); + for (const auto &mod: this->modtype) { + message << mod << " "; + } + logwrite(function, message.str()); + retstring = message.str(); + error = NO_ERROR; + } else if (testname == "parammap") { + // ---------------------------------------------------- + // parammap + // ---------------------------------------------------- + // Log all parammap entries found in the ACF file + + // loop through the modes + // + logwrite(function, "parammap entries by mode section:"); + for (auto &mode_it: this->modemap) { + std::string mode = mode_it.first; + message.str(""); + message << "found mode section " << mode; + logwrite(function, message.str()); + for (auto param_it = this->modemap[mode].parammap.begin(); + param_it != this->modemap[mode].parammap.end(); ++param_it) { + message.str(""); + message << "MODE_" << mode << ": " << param_it->first << "=" << param_it->second.value; + logwrite(function, message.str()); + } + } - } catch (std::invalid_argument &) { - message.str(""); message << "unable to convert sequences: " << nseqstr << " to integer"; - this->camera.log_error( function, message.str() ); - return ERROR; + logwrite(function, "ALL parammap entries in ACF:"); + int keycount = 0; + for (auto ¶m_it: this->parammap) { + keycount++; + message.str(""); + message << param_it.first << "=" << param_it.second.value; + logwrite(function, message.str()); + this->camera.async.enqueue("NOTICE:" + message.str()); + } + message.str(""); + message << "found " << keycount << " parammap entries"; + logwrite(function, message.str()); + error = NO_ERROR; + // end if (testname == parammap) + } else if (testname == "configmap") { + // ---------------------------------------------------- + // configmap + // ---------------------------------------------------- + // Log all configmap entries found in the ACF file + error = NO_ERROR; + logwrite(function, "configmap entries by mode section:"); + for (auto &mode_it: this->modemap) { + std::string mode = mode_it.first; + message.str(""); + message << "found mode section " << mode; + logwrite(function, message.str()); + for (auto config_it = this->modemap[mode].configmap.begin(); + config_it != this->modemap[mode].configmap.end(); ++config_it) { + message.str(""); + message << "MODE_" << mode << ": " << config_it->first << "=" << config_it->second.value; + logwrite(function, message.str()); + } + } - } catch (std::out_of_range &) { - message.str(""); message << "sequences " << nseqstr << " outside integer range"; - this->camera.log_error( function, message.str() ); - return ERROR; - } + // if a second argument was passed then this is a config key + // try to read it + // + if (tokens.size() == 2) { + try { + std::string configkey = tokens.at(1); + error = this->get_configmap_value(configkey, retstring); + } catch (std::out_of_range &) { + this->camera.log_error(function, "configkey token out of range"); + error = ERROR; + } + } - // exposeparam is set by the configuration file - // check to make sure it was set, or else expose won't work - // - if (this->exposeparam.empty()) { - message.str(""); message << "EXPOSE_PARAM not defined in configuration file " << this->config.filename; - this->camera.log_error( function, message.str() ); - return ERROR; - } - error = this->get_frame_status(); // TODO is this needed here? + // if a third argument was passed then set this configkey + // + if (tokens.size() == 3) { + try { + std::string key = tokens.at(1); + std::string value = tokens.at(2); + bool configchanged; + error = this->write_config_key(key.c_str(), value.c_str(), configchanged); + if (error == NO_ERROR) error = this->archon_cmd(APPLYCDS); + } catch (std::out_of_range &) { + this->camera.log_error(function, "key,value tokens out of range"); + error = ERROR; + } + } - if (error != NO_ERROR) { - logwrite( function, "ERROR: unable to get frame status" ); - return ERROR; - } - this->lastframe = this->frame.bufframen[this->frame.index]; // save the last frame number acquired (wait_for_readout will need this) - - // initiate the exposure here - // - error = this->prep_parameter(this->exposeparam, nseqstr); - if (error == NO_ERROR) error = this->load_parameter(this->exposeparam, nseqstr); - - // get system time and Archon's timer after exposure starts - // start_timer is used to determine when the exposure has ended, in wait_for_exposure() - // - if (error == NO_ERROR) { - this->camera_info.start_time = get_timestamp(); // current system time formatted as YYYY-MM-DDTHH:MM:SS.sss - error = this->get_timer(&this->start_timer); // Archon internal timer (one tick=10 nsec) - if ( error != NO_ERROR ) { - logwrite( function, "ERROR: couldn't get start time" ); - return error; - } - this->camera.set_fitstime(this->camera_info.start_time); // sets camera.fitstime (YYYYMMDDHHMMSS) used for filename - // If read-write selected then need to do some FITS stuff - // - if ( rw ) { - this->camera_info.extension = 0; // always initialize extension - error = this->camera.get_fitsname( this->camera_info.fits_name ); // assemble the FITS filename if rw selected - if ( error != NO_ERROR ) { - logwrite( function, "ERROR: couldn't validate fits filename" ); - return error; - } - this->add_filename_key(); // add filename to system keys database - Common::FitsKeys::fits_key_t::iterator keyit; // add keys from the ACF file - for (keyit = this->modemap[this->camera_info.current_observing_mode].acfkeys.keydb.begin(); - keyit != this->modemap[this->camera_info.current_observing_mode].acfkeys.keydb.end(); - keyit++) { - this->camera_info.userkeys.keydb[keyit->second.keyword].keyword = keyit->second.keyword; - this->camera_info.userkeys.keydb[keyit->second.keyword].keytype = keyit->second.keytype; - this->camera_info.userkeys.keydb[keyit->second.keyword].keyvalue = keyit->second.keyvalue; - this->camera_info.userkeys.keydb[keyit->second.keyword].keycomment = keyit->second.keycomment; - } + int keycount = 0; + for (auto config_it = this->configmap.begin(); config_it != this->configmap.end(); ++config_it) { + keycount++; + } + message.str(""); + message << "found " << keycount << " configmap entries"; + logwrite(function, message.str()); + // end if (testname == configmap) + } else if (testname == "bw") { + // ---------------------------------------------------- + // bw + // ---------------------------------------------------- + // Bandwidth test + // This tests the exposure sequence bandwidth by running a sequence + // of exposures, including reading the frame buffer -- everything except + // for the fits file writing. + + if (!this->modeselected) { + this->camera.log_error(function, "no mode selected"); + return ERROR; + } - this->camera_info.iscube = this->camera.datacube(); + std::string nseqstr; + int nseq; + bool ro = false; // read only + bool rw = false; // read and write - // open the file now for datacubes - // - if ( this->camera.datacube() ) { - error = this->fits_file.open_file( - this->camera.writekeys_when == "before", this->camera_info ); - if ( error != NO_ERROR ) { - this->camera.log_error( function, "couldn't open fits file" ); - return error; + if (tokens.size() > 1) { + try { + nseqstr = tokens.at(1); + } catch (std::out_of_range &) { + this->camera.log_error(function, "nseqstr token out of range"); + return ERROR; + } + } else { + this->camera.log_error(function, "usage: test bw [ rw | ro ]"); + return ERROR; } - } - } - } - if (error == NO_ERROR) logwrite(function, "exposure started"); + if (tokens.size() > 2) { + try { + if (tokens.at(2) == "rw") rw = true; + else rw = false; + if (tokens.at(2) == "ro") ro = true; + else ro = false; + } catch (std::out_of_range &) { + this->camera.log_error(function, "rw tokens out of range"); + error = ERROR; + } + } - long frames_read = 0; + try { + nseq = std::stoi(nseqstr); // test that nseqstr is an integer before trying to use it + } catch (std::invalid_argument &) { + message.str(""); + message << "unable to convert sequences: " << nseqstr << " to integer"; + this->camera.log_error(function, message.str()); + return ERROR; + } catch (std::out_of_range &) { + message.str(""); + message << "sequences " << nseqstr << " outside integer range"; + this->camera.log_error(function, message.str()); + return ERROR; + } - // Wait for Archon frame buffer to be ready, then read the latest ready frame buffer to the host. - // Loop over all expected frames. - // - while (nseq-- > 0) { + // exposeparam is set by the configuration file + // check to make sure it was set, or else expose won't work + // + if (this->exposeparam.empty()) { + message.str(""); + message << "EXPOSE_PARAM not defined in configuration file " << this->config.filename; + this->camera.log_error(function, message.str()); + return ERROR; + } + error = this->get_frame_status(); // TODO is this needed here? - // If read-write selected, - // Open a new FITS file for each frame when not using datacubes - // - if ( rw && !this->camera.datacube() ) { - this->camera_info.start_time = get_timestamp(); // current system time formatted as YYYY-MM-DDTHH:MM:SS.sss - if ( this->get_timer(&this->start_timer) != NO_ERROR ) { // Archon internal timer (one tick=10 nsec) - logwrite( function, "ERROR: couldn't get start time" ); - return error; - } - this->camera.set_fitstime(this->camera_info.start_time); // sets camera.fitstime (YYYYMMDDHHMMSS) used for filename - error=this->camera.get_fitsname(this->camera_info.fits_name); // Assemble the FITS filename - if ( error != NO_ERROR ) { - logwrite( function, "ERROR: couldn't validate fits filename" ); - return error; - } - this->add_filename_key(); // add filename to system keys database + if (error != NO_ERROR) { + logwrite(function, "ERROR: unable to get frame status"); + return ERROR; + } + this->lastframe = this->frame.bufframen[this->frame.index]; + // save the last frame number acquired (wait_for_readout will need this) - error = this->fits_file.open_file( - this->camera.writekeys_when == "before", this->camera_info ); - if ( error != NO_ERROR ) { - this->camera.log_error( function, "couldn't open fits file" ); - return error; - } - } + // initiate the exposure here + // + error = this->prep_parameter(this->exposeparam, nseqstr); + if (error == NO_ERROR) error = this->load_parameter(this->exposeparam, nseqstr); - if (this->camera_info.exposure_time != 0) { // wait for the exposure delay to complete (if there is one) - error = this->wait_for_exposure(); - if (error==ERROR) { - logwrite( function, "ERROR: exposure delay error" ); - break; + // get system time and Archon's timer after exposure starts + // start_timer is used to determine when the exposure has ended, in wait_for_exposure() + // + if (error == NO_ERROR) { + this->camera_info.start_time = get_timestamp(); + // current system time formatted as YYYY-MM-DDTHH:MM:SS.sss + error = this->get_timer(&this->start_timer); // Archon internal timer (one tick=10 nsec) + if (error != NO_ERROR) { + logwrite(function, "ERROR: couldn't get start time"); + return error; + } + this->camera.set_fitstime(this->camera_info.start_time); + // sets camera.fitstime (YYYYMMDDHHMMSS) used for filename + // If read-write selected then need to do some FITS stuff + // + if (rw) { + this->camera_info.extension = 0; // always initialize extension + error = this->camera.get_fitsname(this->camera_info.fits_name); + // assemble the FITS filename if rw selected + if (error != NO_ERROR) { + logwrite(function, "ERROR: couldn't validate fits filename"); + return error; + } + this->add_filename_key(); // add filename to system keys database + Common::FitsKeys::fits_key_t::iterator keyit; // add keys from the ACF file + for (keyit = this->modemap[this->camera_info.current_observing_mode].acfkeys.keydb.begin(); + keyit != this->modemap[this->camera_info.current_observing_mode].acfkeys.keydb.end(); + keyit++) { + this->camera_info.userkeys.keydb[keyit->second.keyword].keyword = keyit->second.keyword; + this->camera_info.userkeys.keydb[keyit->second.keyword].keytype = keyit->second.keytype; + this->camera_info.userkeys.keydb[keyit->second.keyword].keyvalue = keyit->second.keyvalue; + this->camera_info.userkeys.keydb[keyit->second.keyword].keycomment = keyit->second.keycomment; + } - } else { - logwrite(function, "exposure delay complete"); - } - } + this->camera_info.iscube = this->camera.datacube(); - if (error==NO_ERROR) error = this->wait_for_readout(); // wait for the readout into frame buffer, - if (error==NO_ERROR && ro) error = this->read_frame(Camera::FRAME_IMAGE); // read image frame directly with no write - if (error==NO_ERROR && rw) error = this->read_frame(); // read image frame directly with no write - if (error==NO_ERROR && rw && !this->camera.datacube()) { - this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info ); - this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" - } - if (error==NO_ERROR) frames_read++; - } - retstring = std::to_string( frames_read ); - - // for cubes, close the FITS file now that they've all been written - // (or any time there is an error) - // - if ( rw && ( this->camera.datacube() || (error==ERROR) ) ) { - this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info ); - this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" - } + // open the file now for datacubes + // + if (this->camera.datacube()) { + error = this->fits_file.open_file( + this->camera.writekeys_when == "before", this->camera_info); + if (error != NO_ERROR) { + this->camera.log_error(function, "couldn't open fits file"); + return error; + } + } + } + } - logwrite( function, (error==ERROR ? "ERROR" : "complete") ); + if (error == NO_ERROR) logwrite(function, "exposure started"); - message.str(""); message << "frames read = " << frames_read; - logwrite(function, message.str()); - // end if (testname==bw) + long frames_read = 0; - } else if (testname == "timer") { - // ---------------------------------------------------- - // timer - // ---------------------------------------------------- - // test Archon time against system time - - int nseq; - int sleepus; - double systime1, systime2; - unsigned long int archontime1, archontime2; - std::vector deltatime; - int delta_archon, delta_system; - - if (tokens.size() < 3) { - this->camera.log_error( function, "expected test timer " ); - return ERROR; - } + // Wait for Archon frame buffer to be ready, then read the latest ready frame buffer to the host. + // Loop over all expected frames. + // + while (nseq-- > 0) { + // If read-write selected, + // Open a new FITS file for each frame when not using datacubes + // + if (rw && !this->camera.datacube()) { + this->camera_info.start_time = get_timestamp(); + // current system time formatted as YYYY-MM-DDTHH:MM:SS.sss + if (this->get_timer(&this->start_timer) != NO_ERROR) { + // Archon internal timer (one tick=10 nsec) + logwrite(function, "ERROR: couldn't get start time"); + return error; + } + this->camera.set_fitstime(this->camera_info.start_time); + // sets camera.fitstime (YYYYMMDDHHMMSS) used for filename + error = this->camera.get_fitsname(this->camera_info.fits_name); // Assemble the FITS filename + if (error != NO_ERROR) { + logwrite(function, "ERROR: couldn't validate fits filename"); + return error; + } + this->add_filename_key(); // add filename to system keys database - try { - nseq = std::stoi( tokens.at(1) ); - sleepus = std::stoi( tokens.at(2) ); + error = this->fits_file.open_file( + this->camera.writekeys_when == "before", this->camera_info); + if (error != NO_ERROR) { + this->camera.log_error(function, "couldn't open fits file"); + return error; + } + } - } catch (std::invalid_argument &) { - message.str(""); message << "unable to convert one or more args to an integer"; - this->camera.log_error( function, message.str() ); - return ERROR; + if (this->camera_info.exposure_time != 0) { + // wait for the exposure delay to complete (if there is one) + error = this->wait_for_exposure(); + if (error == ERROR) { + logwrite(function, "ERROR: exposure delay error"); + break; + } else { + logwrite(function, "exposure delay complete"); + } + } - } catch (std::out_of_range &) { - message.str(""); message << "nseq, sleepus tokens outside range"; - this->camera.log_error( function, message.str() ); - return ERROR; - } + if (error == NO_ERROR) error = this->wait_for_readout(); // wait for the readout into frame buffer, + if (error == NO_ERROR && ro) error = this->read_frame(Camera::FRAME_IMAGE); + // read image frame directly with no write + if (error == NO_ERROR && rw) error = this->read_frame(); // read image frame directly with no write + if (error == NO_ERROR && rw && !this->camera.datacube()) { + this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info); + this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" + } + if (error == NO_ERROR) frames_read++; + } + retstring = std::to_string(frames_read); - error = NO_ERROR; + // for cubes, close the FITS file now that they've all been written + // (or any time there is an error) + // + if (rw && (this->camera.datacube() || (error == ERROR))) { + this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info); + this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" + } - // turn off background polling while doing the timing test - // - if (error == NO_ERROR) error = this->archon_cmd(POLLOFF); + logwrite(function, (error == ERROR ? "ERROR" : "complete")); - // send the Archon TIMER command here, nseq times, with sleepus delay between each - // - int nseqsave = nseq; - while ( error==NO_ERROR && nseq-- > 0 ) { - // get Archon timer [ns] and system time [s], twice - // - error = get_timer(&archontime1); - systime1 = get_clock_time(); - error = get_timer(&archontime2); - systime2 = get_clock_time(); + message.str(""); + message << "frames read = " << frames_read; + logwrite(function, message.str()); + // end if (testname==bw) + } else if (testname == "timer") { + // ---------------------------------------------------- + // timer + // ---------------------------------------------------- + // test Archon time against system time + + int nseq; + int sleepus; + double systime1, systime2; + unsigned long int archontime1, archontime2; + std::vector deltatime; + int delta_archon, delta_system; + + if (tokens.size() < 3) { + this->camera.log_error(function, "expected test timer "); + return ERROR; + } - // difference between two calls, converted to µsec - // - delta_archon = (int)((archontime2 - archontime1) / 100.); // archon time was in 10 nsec - delta_system = (int)((systime2 - systime1) * 1000000.); // system time was in sec + try { + nseq = std::stoi(tokens.at(1)); + sleepus = std::stoi(tokens.at(2)); + } catch (std::invalid_argument &) { + message.str(""); + message << "unable to convert one or more args to an integer"; + this->camera.log_error(function, message.str()); + return ERROR; + } catch (std::out_of_range &) { + message.str(""); + message << "nseq, sleepus tokens outside range"; + this->camera.log_error(function, message.str()); + return ERROR; + } - // enque each line to the async message port - // - message.str(""); message << "TEST_TIMER: " << nseqsave-nseq << ", " << delta_archon << ", " << delta_system; - this->camera.async.enqueue( message.str() ); + error = NO_ERROR; - // save the difference between - // - deltatime.push_back( abs( delta_archon - delta_system ) ); + // turn off background polling while doing the timing test + // + if (error == NO_ERROR) error = this->archon_cmd(POLLOFF); - usleep( sleepus ); - } + // send the Archon TIMER command here, nseq times, with sleepus delay between each + // + int nseqsave = nseq; + while (error == NO_ERROR && nseq-- > 0) { + // get Archon timer [ns] and system time [s], twice + // + error = get_timer(&archontime1); + systime1 = get_clock_time(); + error = get_timer(&archontime2); + systime2 = get_clock_time(); + + // difference between two calls, converted to µsec + // + delta_archon = (int) ((archontime2 - archontime1) / 100.); // archon time was in 10 nsec + delta_system = (int) ((systime2 - systime1) * 1000000.); // system time was in sec + + // enque each line to the async message port + // + message.str(""); + message << "TEST_TIMER: " << nseqsave - nseq << ", " << delta_archon << ", " << delta_system; + this->camera.async.enqueue(message.str()); + + // save the difference between + // + deltatime.push_back(abs(delta_archon - delta_system)); + + usleep(sleepus); + } - // background polling back on - // - if (error == NO_ERROR) error = this->archon_cmd(POLLON); + // background polling back on + // + if (error == NO_ERROR) error = this->archon_cmd(POLLON); - // calculate the average and standard deviation of the difference - // between system and archon - // - double sum = std::accumulate(std::begin(deltatime), std::end(deltatime), 0.0); - double m = sum / deltatime.size(); + // calculate the average and standard deviation of the difference + // between system and archon + // + double sum = std::accumulate(std::begin(deltatime), std::end(deltatime), 0.0); + double m = sum / deltatime.size(); - double accum = 0.0; - std::for_each (std::begin(deltatime), std::end(deltatime), [&](const double d) { - accum += (d - m) * (d - m); - }); + double accum = 0.0; + std::for_each(std::begin(deltatime), std::end(deltatime), [&](const double d) { + accum += (d - m) * (d - m); + }); - double stdev = sqrt(accum / (deltatime.size()-1)); + double stdev = sqrt(accum / (deltatime.size() - 1)); - message.str(""); message << "average delta=" << m << " stddev=" << stdev; - logwrite(function, message.str()); + message.str(""); + message << "average delta=" << m << " stddev=" << stdev; + logwrite(function, message.str()); - retstring = "delta=" + std::to_string( m ) + " stddev=" + std::to_string( stdev ); - // end if (testname==timer) + retstring = "delta=" + std::to_string(m) + " stddev=" + std::to_string(stdev); + // end if (testname==timer) + } else { + // ---------------------------------------------------- + // invalid test name + // ---------------------------------------------------- + message.str(""); + message << "unknown test: " << testname; + this->camera.log_error(function, message.str()); + error = ERROR; + } - } else { - // ---------------------------------------------------- - // invalid test name - // ---------------------------------------------------- - message.str(""); message << "unknown test: " << testname; - this->camera.log_error( function, message.str() ); - error = ERROR; + return error; } - return error; - } - /**************** Archon::Interface::test ***********************************/ - + /**************** Archon::Interface::test ***********************************/ } diff --git a/camerad/archon.h b/camerad/archon.h index a48e1633..8612381f 100644 --- a/camerad/archon.h +++ b/camerad/archon.h @@ -57,305 +57,354 @@ #define REV_VCPU std::string("1.0.784") namespace Archon { - - // Archon hardware-based constants. - // These shouldn't change unless there is a significant hardware change. - // - const int nbufs = 3; //!< total number of frame buffers //TODO rename to maxnbufs? - const int nmods = 12; //!< number of modules per controller - const int nadchan = 4; //!< number of channels per ADC module - - // Parameter defaults, unless overridden by the configuration file - // - const int DEF_TRIGIN_EXPOSE_ENABLE = 1; - const int DEF_TRIGIN_EXPOSE_DISABLE = 0; - const int DEF_TRIGIN_UNTIMED_ENABLE = 1; - const int DEF_TRIGIN_UNTIMED_DISABLE = 0; - const int DEF_TRIGIN_READOUT_ENABLE = 1; - const int DEF_TRIGIN_READOUT_DISABLE = 0; - const int DEF_SHUTENABLE_ENABLE = 1; - const int DEF_SHUTENABLE_DISABLE = 0; - - class Interface { + // Archon hardware-based constants. + // These shouldn't change unless there is a significant hardware change. + // + const int nbufs = 3; //!< total number of frame buffers //TODO rename to maxnbufs? + const int nmods = 12; //!< number of modules per controller + const int nadchan = 4; //!< number of channels per ADC module + + // Parameter defaults, unless overridden by the configuration file + // + const int DEF_TRIGIN_EXPOSE_ENABLE = 1; + const int DEF_TRIGIN_EXPOSE_DISABLE = 0; + const int DEF_TRIGIN_UNTIMED_ENABLE = 1; + const int DEF_TRIGIN_UNTIMED_DISABLE = 0; + const int DEF_TRIGIN_READOUT_ENABLE = 1; + const int DEF_TRIGIN_READOUT_DISABLE = 0; + const int DEF_SHUTENABLE_ENABLE = 1; + const int DEF_SHUTENABLE_DISABLE = 0; + + class Interface { private: - unsigned long int start_timer, finish_timer; //!< Archon internal timer, start and end of exposure - int n_hdrshift; //!< number of right-shift bits for Archon buffer in HDR mode + unsigned long int start_timer, finish_timer; //!< Archon internal timer, start and end of exposure + int n_hdrshift; //!< number of right-shift bits for Archon buffer in HDR mode public: - Interface(); - ~Interface(); - - // Class Objects - // - Network::TcpSocket archon; - Camera::Information camera_info; /// this is the main camera_info object - Camera::Information fits_info; /// used to copy the camera_info object to preserve info for FITS writing - Camera::Camera camera; /// instantiate a Camera object - Common::FitsKeys userkeys; //!< instantiate a Common object - Common::FitsKeys systemkeys; //!< instantiate a Common object - - Config config; - - FITS_file fits_file; //!< instantiate a FITS container object - - int msgref; //!< Archon message reference identifier, matches reply to command - bool abort; - int taplines; - std::vector gain; //!< digital CDS gain (from TAPLINE definition) - std::vector offset; //!< digital CDS offset (from TAPLINE definition) - bool modeselected; //!< true if a valid mode has been selected, false otherwise - bool firmwareloaded; //!< true if firmware is loaded, false otherwise - bool is_longexposure; //!< true for long exposure mode (exptime in sec), false for exptime in msec - bool is_window; //!< true if in window mode for h2rg, false if not - bool is_autofetch; - int win_hstart; - int win_hstop; - int win_vstart; - int win_vstop; - int taplines_store; //!< int number of original taplines - std::string tapline0_store; //!< store tapline0 for window mode so can restore later - - bool lastcubeamps; - - std::string trigin_state; //!< for external triggering of exposures - - int trigin_expose; //!< current value of trigin expose - int trigin_expose_enable; //!< value which enables trigin expose - int trigin_expose_disable; //!< value which disables trigin expose - - int trigin_untimed; //!< current value of trigin_untimed - int trigin_untimed_enable; //!< value which enables trigin untimed - int trigin_untimed_disable; //!< value which disables trigin untimed - - int trigin_readout; //!< current value of trigin_readout - int trigin_readout_enable; //!< value which enables trigin readout - int trigin_readout_disable; //!< value which disables trigin readout - - std::string trigin_exposeparam; //!< parameter name to write for trigin_expose - std::string trigin_untimedparam; //!< parameter name to write for trigin_untimed - std::string trigin_readoutparam; //!< parameter name to write for trigin_readout - - float heater_target_min; //!< minimum heater target temperature - float heater_target_max; //!< maximum heater target temperature - - char *image_data; //!< image data buffer - uint32_t image_data_bytes; //!< requested number of bytes allocated for image_data rounded up to block size - uint32_t image_data_allocated; //!< allocated number of bytes for image_data - - std::atomic archon_busy; //!< indicates a thread is accessing Archon - std::mutex archon_mutex; //!< protects Archon from being accessed by multiple threads, - //!< use in conjunction with archon_busy flag - std::string exposeparam; //!< param name to trigger exposure when set =1 - - std::string shutenableparam; //!< param name to enable shutter open on expose - int shutenable_enable; //!< the value which enables shutter enable - int shutenable_disable; //!< the value which disables shutter enable - - // Functions - // - static long interface(std::string &iface); //!< get interface type - long configure_controller(); //!< get configuration parameters - long prepare_image_buffer(); //!< prepare image_data, allocating memory as needed - long connect_controller(const std::string& devices_in); //!< open connection to archon controller - long disconnect_controller(); //!< disconnect from archon controller - long load_timing(std::string acffile); //!< load specified ACF then LOADTIMING - long load_timing(std::string acffile, std::string &retstring); - long load_firmware(std::string acffile); //!< load specified acf then APPLYALL - long load_firmware(std::string acffile, std::string &retstring); - long load_acf(std::string acffile); //!< only load (WCONFIG) the specified ACF file - long set_camera_mode(std::string mode_in); - long load_mode_settings(std::string mode); - long native(const std::string& cmd); - long archon_cmd(const std::string& cmd); - long archon_cmd(const std::string& cmd, std::string &reply); - long read_parameter(const std::string& paramname, std::string &valstring); - long prep_parameter(const std::string& paramname, std::string value); - long load_parameter(std::string paramname, std::string value); - long fetchlog(); - long get_frame_status(); - void print_frame_status(); - long lock_buffer(int buffer); - long get_timer(unsigned long int *timer); - long fetch(uint64_t bufaddr, uint32_t bufblocks); - long read_frame(); //!< read Archon frame buffer into host memory - long hread_frame(); - long read_frame(Camera::frame_type_t frame_type); /// read Archon frame buffer into host memory - long write_frame(); //!< write (a previously read) Archon frame buffer to disk - long write_raw(); //!< write raw 16 bit data to a FITS file - long write_config_key( const char *key, const char *newvalue, bool &changed ); - long write_config_key( const char *key, int newvalue, bool &changed ); - long write_parameter( const char *paramname, const char *newvalue, bool &changed ); - long write_parameter( const char *paramname, int newvalue, bool &changed ); - long write_parameter( const char *paramname, const char *newvalue ); - long write_parameter( const char *paramname, int newvalue ); - template long get_configmap_value(std::string key_in, T& value_out); - void add_filename_key(); - long expose(std::string nseq_in); - long hexpose(std::string nseq_in); - long hsetup(); - long hroi(std::string geom_in, std::string &retstring); - long hwindow(std::string state_in, std::string &state_out); - long autofetch(std::string state_in, std::string &state_out); - long video(); - long wait_for_exposure(); - long wait_for_readout(); - long hwait_for_readout(); - long get_parameter(std::string parameter, std::string &retstring); - long set_parameter( std::string parameter, long value ); - long set_parameter(std::string parameter); - long exptime(std::string exptime_in, std::string &retstring); - void copy_keydb(); /// copy user keyword database into camera_info - long longexposure(std::string state_in, std::string &state_out); - long shutter(std::string shutter_in, std::string& shutter_out); - long hdrshift(std::string bits_in, std::string &bits_out); - long trigin(std::string state_in); - long heater(std::string args, std::string &retstring); - long sensor(std::string args, std::string &retstring); - long bias(std::string args, std::string &retstring); - long cds(std::string args, std::string &retstring); - long inreg( std::string args ); - long region_of_interest( std::string args, std::string &retstring ); - long test(std::string args, std::string &retstring); - - /** - * @var struct geometry_t geometry[] - * @details structure of geometry which is unique to each observing mode - */ - struct geometry_t { - int amps[2]; // number of amplifiers per detector for each axis, set in set_camera_mode - int num_detect; // number of detectors, set in set_camera_mode - int linecount; // number of lines per tap - int pixelcount; // number of pixels per tap - int framemode; // Archon deinterlacing mode, 0=topfirst, 1=bottomfirst, 2=split - }; - - /** - * @var struct tapinfo_t tapinfo[] - * @details structure of tapinfo which is unique to each observing mode - */ - struct tapinfo_t { - int num_taps; - int tap[16]; - float gain[16]; - float offset[16]; - std::string readoutdir[16]; - }; - - /** - * @var struct frame_data_t frame - * @details structure to contain Archon results from "FRAME" command - */ - struct frame_data_t { - int index; // index of newest buffer data - int frame; // frame of newest buffer data - int next_index; // index of next buffer - std::string timer; // current hex 64 bit internal timer - int rbuf; // current buffer locked for reading - int wbuf; // current buffer locked for writing - std::vector bufsample; // sample mode 0=16 bit, 1=32 bit - std::vector bufcomplete; // buffer complete, 1=ready to read - std::vector bufmode; // buffer mode: 0=top 1=bottom 2=split - std::vector bufbase; // buffer base address for fetching - std::vector bufframen; // buffer frame number - std::vector bufwidth; // buffer width - std::vector bufheight; // buffer height - std::vector bufpixels; // buffer pixel progress - std::vector buflines; // buffer line progress - std::vector bufrawblocks; // buffer raw blocks per line - std::vector bufrawlines; // buffer raw lines - std::vector bufrawoffset; // buffer raw offset - 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 - } frame; - - /** @var vector modtype - * @details stores the type of each module from the SYSTEM command - */ - std::vector modtype; - - /** @var vector modversion - * @details stores the version of each module from the SYSTEM command - */ - std::vector modversion; - - std::string backplaneversion; - - /** @var int lastframe - * @details the last (I.E. previous) frame number acquired - */ - int lastframe; - - /** - * rawinfo_t is a struct which contains variables specific to raw data functions - */ - struct rawinfo_t { - int adchan; // selected A/D channels - int rawsamples; // number of raw samples per line - int rawlines; // number of raw lines - int iteration; // iteration number - int iterations; // number of iterations - } rawinfo; - - /** - * config_line_t is a struct for the configfile key=value map, used to - * store the configuration line and its associated line number. - */ - typedef struct { - int line; // the line number, used for updating Archon - std::string value; // used for configmap - } config_line_t; - - /** - * param_line_t is a struct for the PARAMETER name key=value map, used to - * store parameters where the format is PARAMETERn=parametername=value - */ - typedef struct { - std::string key; // the PARAMETERn part - std::string name; // the parametername part - std::string value; // the value part - int line; // the line number, used for updating Archon - } param_line_t; - - typedef std::map cfg_map_t; - typedef std::map param_map_t; - - cfg_map_t configmap; - param_map_t parammap; - - /** - * \var modeinfo_t modeinfo - * \details structure contains a configmap and parammap unique to each mode, - * specified in the [MODE_*] sections at the end of the .acf file. - */ - typedef struct { - int rawenable; //!< initialized to -1, then set according to RAWENABLE in .acf file - cfg_map_t configmap; //!< key=value map for configuration lines set in mode sections - param_map_t parammap; //!< PARAMETERn=parametername=value map for mode sections - Common::FitsKeys acfkeys; //!< create a FitsKeys object to hold user keys read from ACF file for each mode - geometry_t geometry; - tapinfo_t tapinfo; - } modeinfo_t; - - std::map modemap; - - /** - * generic key=value STL map for Archon commands - */ - typedef std::map map_t; - - /** - * \var map_t systemmap - * \details key=value map for Archon SYSTEM command - */ - map_t systemmap; - - /** - * \var map_t statusmap - * \details key=value map for Archon STATUS command - */ - map_t statusmap; - - }; + Interface(); + + ~Interface(); + + // Class Objects + // + Network::TcpSocket archon; + Camera::Information camera_info; /// this is the main camera_info object + Camera::Information fits_info; /// used to copy the camera_info object to preserve info for FITS writing + Camera::Camera camera; /// instantiate a Camera object + Common::FitsKeys userkeys; //!< instantiate a Common object + Common::FitsKeys systemkeys; //!< instantiate a Common object + + Config config; + + FITS_file fits_file; //!< instantiate a FITS container object + + int msgref; //!< Archon message reference identifier, matches reply to command + bool abort; + int taplines; + std::vector gain; //!< digital CDS gain (from TAPLINE definition) + std::vector offset; //!< digital CDS offset (from TAPLINE definition) + bool modeselected; //!< true if a valid mode has been selected, false otherwise + bool firmwareloaded; //!< true if firmware is loaded, false otherwise + bool is_longexposure; //!< true for long exposure mode (exptime in sec), false for exptime in msec + bool is_window; //!< true if in window mode for h2rg, false if not + bool is_autofetch; + int win_hstart; + int win_hstop; + int win_vstart; + int win_vstop; + int taplines_store; //!< int number of original taplines + std::string tapline0_store; //!< store tapline0 for window mode so can restore later + + bool lastcubeamps; + + std::string trigin_state; //!< for external triggering of exposures + + int trigin_expose; //!< current value of trigin expose + int trigin_expose_enable; //!< value which enables trigin expose + int trigin_expose_disable; //!< value which disables trigin expose + + int trigin_untimed; //!< current value of trigin_untimed + int trigin_untimed_enable; //!< value which enables trigin untimed + int trigin_untimed_disable; //!< value which disables trigin untimed + + int trigin_readout; //!< current value of trigin_readout + int trigin_readout_enable; //!< value which enables trigin readout + int trigin_readout_disable; //!< value which disables trigin readout + + std::string trigin_exposeparam; //!< parameter name to write for trigin_expose + std::string trigin_untimedparam; //!< parameter name to write for trigin_untimed + std::string trigin_readoutparam; //!< parameter name to write for trigin_readout + + float heater_target_min; //!< minimum heater target temperature + float heater_target_max; //!< maximum heater target temperature + + char *image_data; //!< image data buffer + uint32_t image_data_bytes; //!< requested number of bytes allocated for image_data rounded up to block size + uint32_t image_data_allocated; //!< allocated number of bytes for image_data + + std::atomic archon_busy; //!< indicates a thread is accessing Archon + std::mutex archon_mutex; + //!< protects Archon from being accessed by multiple threads, + //!< use in conjunction with archon_busy flag + std::string exposeparam; //!< param name to trigger exposure when set =1 + + std::string shutenableparam; //!< param name to enable shutter open on expose + int shutenable_enable; //!< the value which enables shutter enable + int shutenable_disable; //!< the value which disables shutter enable + + // Functions + // + static long interface(std::string &iface); //!< get interface type + long configure_controller(); //!< get configuration parameters + long prepare_image_buffer(); //!< prepare image_data, allocating memory as needed + long connect_controller(const std::string &devices_in); //!< open connection to archon controller + long disconnect_controller(); //!< disconnect from archon controller + long load_timing(std::string acffile); //!< load specified ACF then LOADTIMING + long load_timing(std::string acffile, std::string &retstring); + + long load_firmware(std::string acffile); //!< load specified acf then APPLYALL + long load_firmware(std::string acffile, std::string &retstring); + + long load_acf(std::string acffile); //!< only load (WCONFIG) the specified ACF file + long set_camera_mode(std::string mode_in); + + long load_mode_settings(std::string mode); + + long native(const std::string &cmd); + + long archon_cmd(const std::string &cmd); + + long archon_cmd(const std::string &cmd, std::string &reply); + + long read_parameter(const std::string ¶mname, std::string &valstring); + + long prep_parameter(const std::string ¶mname, std::string value); + + long load_parameter(std::string paramname, std::string value); + + long fetchlog(); + + long get_frame_status(); + + void print_frame_status(); + + long lock_buffer(int buffer); + + long get_timer(unsigned long int *timer); + + long fetch(uint64_t bufaddr, uint32_t bufblocks); + + long read_frame(); //!< read Archon frame buffer into host memory + long hread_frame(); + + long read_frame(Camera::frame_type_t frame_type); /// read Archon frame buffer into host memory + long write_frame(); //!< write (a previously read) Archon frame buffer to disk + long write_raw(); //!< write raw 16 bit data to a FITS file + long write_config_key(const char *key, const char *newvalue, bool &changed); + + long write_config_key(const char *key, int newvalue, bool &changed); + + long write_parameter(const char *paramname, const char *newvalue, bool &changed); + + long write_parameter(const char *paramname, int newvalue, bool &changed); + + long write_parameter(const char *paramname, const char *newvalue); + + long write_parameter(const char *paramname, int newvalue); + + template + long get_configmap_value(std::string key_in, T &value_out); + + void add_filename_key(); + + long expose(std::string nseq_in); + + long hexpose(std::string nseq_in); + + long hsetup(); + + long hroi(std::string geom_in, std::string &retstring); + + long hwindow(std::string state_in, std::string &state_out); + + long autofetch(std::string state_in, std::string &state_out); + + long video(); + + long wait_for_exposure(); + + long wait_for_readout(); + + long hwait_for_readout(); + + long get_parameter(std::string parameter, std::string &retstring); + + long set_parameter(std::string parameter, long value); + + long set_parameter(std::string parameter); + + long exptime(std::string exptime_in, std::string &retstring); + + void copy_keydb(); /// copy user keyword database into camera_info + long longexposure(std::string state_in, std::string &state_out); + + long shutter(std::string shutter_in, std::string &shutter_out); + + long hdrshift(std::string bits_in, std::string &bits_out); + + long trigin(std::string state_in); + + long heater(std::string args, std::string &retstring); + + long sensor(std::string args, std::string &retstring); + + long bias(std::string args, std::string &retstring); + + long cds(std::string args, std::string &retstring); + + long inreg(std::string args); + + long region_of_interest(std::string args, std::string &retstring); + + long test(std::string args, std::string &retstring); + + /** + * @var struct geometry_t geometry[] + * @details structure of geometry which is unique to each observing mode + */ + struct geometry_t { + int amps[2]; // number of amplifiers per detector for each axis, set in set_camera_mode + int num_detect; // number of detectors, set in set_camera_mode + int linecount; // number of lines per tap + int pixelcount; // number of pixels per tap + int framemode; // Archon deinterlacing mode, 0=topfirst, 1=bottomfirst, 2=split + }; + + /** + * @var struct tapinfo_t tapinfo[] + * @details structure of tapinfo which is unique to each observing mode + */ + struct tapinfo_t { + int num_taps; + int tap[16]; + float gain[16]; + float offset[16]; + std::string readoutdir[16]; + }; + /** + * @var struct frame_data_t frame + * @details structure to contain Archon results from "FRAME" command + */ + struct frame_data_t { + int index; // index of newest buffer data + int frame; // frame of newest buffer data + int next_index; // index of next buffer + std::string timer; // current hex 64 bit internal timer + int rbuf; // current buffer locked for reading + int wbuf; // current buffer locked for writing + std::vector bufsample; // sample mode 0=16 bit, 1=32 bit + std::vector bufcomplete; // buffer complete, 1=ready to read + std::vector bufmode; // buffer mode: 0=top 1=bottom 2=split + std::vector bufbase; // buffer base address for fetching + std::vector bufframen; // buffer frame number + std::vector bufwidth; // buffer width + std::vector bufheight; // buffer height + std::vector bufpixels; // buffer pixel progress + std::vector buflines; // buffer line progress + std::vector bufrawblocks; // buffer raw blocks per line + std::vector bufrawlines; // buffer raw lines + std::vector bufrawoffset; // buffer raw offset + 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 + } frame; + + /** @var vector modtype + * @details stores the type of each module from the SYSTEM command + */ + std::vector modtype; + + /** @var vector modversion + * @details stores the version of each module from the SYSTEM command + */ + std::vector modversion; + + std::string backplaneversion; + + /** @var int lastframe + * @details the last (I.E. previous) frame number acquired + */ + int lastframe; + + /** + * rawinfo_t is a struct which contains variables specific to raw data functions + */ + struct rawinfo_t { + int adchan; // selected A/D channels + int rawsamples; // number of raw samples per line + int rawlines; // number of raw lines + int iteration; // iteration number + int iterations; // number of iterations + } rawinfo; + + /** + * config_line_t is a struct for the configfile key=value map, used to + * store the configuration line and its associated line number. + */ + typedef struct { + int line; // the line number, used for updating Archon + std::string value; // used for configmap + } config_line_t; + + /** + * param_line_t is a struct for the PARAMETER name key=value map, used to + * store parameters where the format is PARAMETERn=parametername=value + */ + typedef struct { + std::string key; // the PARAMETERn part + std::string name; // the parametername part + std::string value; // the value part + int line; // the line number, used for updating Archon + } param_line_t; + + typedef std::map cfg_map_t; + typedef std::map param_map_t; + + cfg_map_t configmap; + param_map_t parammap; + + /** + * \var modeinfo_t modeinfo + * \details structure contains a configmap and parammap unique to each mode, + * specified in the [MODE_*] sections at the end of the .acf file. + */ + typedef struct { + int rawenable; //!< initialized to -1, then set according to RAWENABLE in .acf file + cfg_map_t configmap; //!< key=value map for configuration lines set in mode sections + param_map_t parammap; //!< PARAMETERn=parametername=value map for mode sections + Common::FitsKeys acfkeys; //!< create a FitsKeys object to hold user keys read from ACF file for each mode + geometry_t geometry; + tapinfo_t tapinfo; + } modeinfo_t; + + std::map modemap; + + /** + * generic key=value STL map for Archon commands + */ + typedef std::map map_t; + + /** + * \var map_t systemmap + * \details key=value map for Archon SYSTEM command + */ + map_t systemmap; + + /** + * \var map_t statusmap + * \details key=value map for Archon STATUS command + */ + map_t statusmap; + }; } diff --git a/camerad/camera.cpp b/camerad/camera.cpp index 3d8136ca..9674b679 100644 --- a/camerad/camera.cpp +++ b/camerad/camera.cpp @@ -23,802 +23,850 @@ #include "camera.h" namespace Camera { + /** Camera::Camera::abort ***************************************************/ + /** + * @fn abort + * @brief abort the current operation (exposure, readout, etc.) + * @param none + * @return none + * + */ + void Camera::abort() { + std::string function = "Camera::Camera::abort"; + std::stringstream message; + this->abortstate = true; + logwrite(function, "received abort"); + return; + } - /** Camera::Camera::abort ***************************************************/ - /** - * @fn abort - * @brief abort the current operation (exposure, readout, etc.) - * @param none - * @return none - * - */ - void Camera::abort() { - std::string function = "Camera::Camera::abort"; - std::stringstream message; - this->abortstate = true; - logwrite(function, "received abort"); - return; - } - /** Camera::Camera::abort ***************************************************/ - -void Camera::set_abortstate(bool state) { - this->abort_mutex.lock(); - this->abortstate = state; - this->_abortstate = state; - this->abort_mutex.unlock(); -} - -bool Camera::get_abortstate() { - bool state; - this->abort_mutex.lock(); - state = this->abortstate; - this->abort_mutex.unlock(); - return state; -} - + /** Camera::Camera::abort ***************************************************/ - /** Camera::Camera::log_error ***********************************************/ - /** - * @fn log_error - * @brief logs the error and saves the message to be returned on the command port - * @param std::string function name - * @param std::string message (error) - * @return ERROR or NO_ERROR - * - */ - void Camera::log_error( std::string function, std::string message ) { - std::stringstream err; - - // Save this message in class variable - this->lasterrorstring.str(""); - this->lasterrorstring << message; - - // Form an error string as "ERROR: " - err << "ERROR: " << this->lasterrorstring.str(); - - // Log and send to async port in the usual ways - // - logwrite( function, err.str() ); - this->async.enqueue( err.str() ); - } - /** Camera::Camera::log_error ***********************************************/ - - - /** Camera::Camera::get_longerror *******************************************/ - /** - * @fn get_longerror - * @brief return the saved error message - * @param none - * @return std::string message - * - * If is_longerror is set (true) then return the last saved error message - * in lasterrorstring, then erase that string. - * - * If is_longerror is clear (false) then return an empty string. - * - */ - std::string Camera::get_longerror() { - std::string err = ( this->is_longerror ? ( " " + this->lasterrorstring.str() ) : "" ); - this->lasterrorstring.str(""); - return ( err ); - } - /** Camera::Camera::get_longerror *******************************************/ - - - /** Camera::Camera::writekeys ***********************************************/ - /** - * @fn writekeys - * @brief set or get the writekeys_when value - * @param std::string writekeys_in - * @param std::string& writekeys_out - * @return ERROR or NO_ERROR - * - */ - long Camera::writekeys(std::string writekeys_in, std::string &writekeys_out) { - std::string function = "Camera::Camera::writekeys"; - std::stringstream message; - long error = NO_ERROR; - - if ( !writekeys_in.empty() ) { - try { - std::transform( writekeys_in.begin(), writekeys_in.end(), writekeys_in.begin(), ::tolower ); // make lowercase - if ( writekeys_in == "before" || writekeys_in == "after" ) this->writekeys_when = writekeys_in; - else { - message.str(""); message << writekeys_in << " is invalid. Expecting before or after"; - this->log_error( function, message.str() ); - error = ERROR; - } - } - catch (...) { - message.str(""); message << "unknown exception parsing argument: " << writekeys_in; - this->log_error( function, message.str() ); - return ERROR; - } + void Camera::set_abortstate(bool state) { + this->abort_mutex.lock(); + this->abortstate = state; + this->_abortstate = state; + this->abort_mutex.unlock(); } - writekeys_out = this->writekeys_when; - return error; - } - /** Camera::Camera::writekeys ***********************************************/ - - - /** Camera::Camera::fitsnaming **********************************************/ - /** - * @fn fitsnaming - * @brief set or get the fits naming type - * @param std::string naming_in - * @param std::string& naming_out - * @return ERROR or NO_ERROR - * - */ - long Camera::fitsnaming(std::string naming_in, std::string& naming_out) { - std::string function = "Camera::Camera::fitsnaming"; - std::stringstream message; - long error; - - message.str(""); message << "fits naming: " << this->fits_naming; - - if (naming_in.empty()) { // no string passed so this is a request. do nothing but will return the current value - error = NO_ERROR; - } else if ( (naming_in.compare("time")==0) || (naming_in.compare("number")==0) ) { - this->fits_naming = naming_in; // set new value - error = NO_ERROR; - } else { - message.str(""); message << "invalid naming type: " << naming_in << ". Must be \"time\" or \"number\"."; - error = ERROR; + bool Camera::get_abortstate() { + bool state; + this->abort_mutex.lock(); + state = this->abortstate; + this->abort_mutex.unlock(); + return state; } - error == NO_ERROR ? logwrite(function, message.str()) : this->log_error( function, message.str() ); - naming_out = this->fits_naming; // return the current value - return error; - } - /** Camera::Camera::fitsnaming **********************************************/ - - - /** Camera::Camera::imnum ***************************************************/ - /** - * @fn imnum - * @brief set or get the image_num member - * @param std::string num_in - * @param std::string& num_out - * @return ERROR or NO_ERROR - * - */ - long Camera::imnum(std::string num_in, std::string& num_out) { - std::string function = "Camera::Camera::imnum"; - std::stringstream message; - - // If no string is passed then this is a request; return the current value. - // - if (num_in.empty()) { - message.str(""); message << "image number: " << this->image_num; - logwrite(function, message.str()); - num_out = std::to_string(this->image_num); - return NO_ERROR; - } - else { // Otherwise check the incoming value - int num; - try { - num = std::stoi(num_in); - } - catch (std::invalid_argument &) { - this->log_error( function, "invalid number: unable to convert to integer" ); - return ERROR; - } - catch (std::out_of_range &) { - this->log_error( function, "imnum out of integer range" ); - return ERROR; - } - if (num < 0) { // can't be negative - message.str(""); message << "requested image number " << num << " must be >= 0"; - this->log_error( function, message.str() ); - return ERROR; - } - else { // value is OK - this->image_num = num; - num_out = num_in; - return NO_ERROR; - } - } - } - /** Camera::Camera::imnum ***************************************************/ - - - /** Camera::Camera::basename ************************************************/ - /** - * @fn basename - * @brief set or get the base_name member - * @param std::string name_in - * @param std::string& name_out - * @return NO_ERROR - * - * This function is overloaded with a form that doesn't use a return value. - * The only restriction on base name is that it can't contain a '/' character. - * - */ - long Camera::basename(std::string name_in) { - std::string dontcare; - return basename(name_in, dontcare); - } - long Camera::basename(std::string name_in, std::string& name_out) { - std::string function = "Camera::Camera::basename"; - std::stringstream message; - long error=NO_ERROR; - - // Base name cannot contain a "/" because that would be a subdirectory, - // and subdirectories are not checked here, only by imdir command. - // - if ( name_in.find('/') != std::string::npos ) { - this->log_error( function, "basename cannot contain a '/' character" ); - error = ERROR; + /** Camera::Camera::log_error ***********************************************/ + /** + * @fn log_error + * @brief logs the error and saves the message to be returned on the command port + * @param std::string function name + * @param std::string message (error) + * @return ERROR or NO_ERROR + * + */ + void Camera::log_error(std::string function, std::string message) { + std::stringstream err; + + // Save this message in class variable + this->lasterrorstring.str(""); + this->lasterrorstring << message; + + // Form an error string as "ERROR: " + err << "ERROR: " << this->lasterrorstring.str(); + + // Log and send to async port in the usual ways + // + logwrite(function, err.str()); + this->async.enqueue(err.str()); } - else if ( !name_in.empty() ) { // If a name is supplied - this->base_name = name_in; // then set the image name. - error = NO_ERROR; + + /** Camera::Camera::log_error ***********************************************/ + + + /** Camera::Camera::get_longerror *******************************************/ + /** + * @fn get_longerror + * @brief return the saved error message + * @param none + * @return std::string message + * + * If is_longerror is set (true) then return the last saved error message + * in lasterrorstring, then erase that string. + * + * If is_longerror is clear (false) then return an empty string. + * + */ + std::string Camera::get_longerror() { + std::string err = (this->is_longerror ? (" " + this->lasterrorstring.str()) : ""); + this->lasterrorstring.str(""); + return (err); } - // In any case, log and return the current value. - // - message.str(""); message << "base name is " << this->base_name; - logwrite(function, message.str()); - name_out = this->base_name; - - return error; - } - /** Camera::Camera::basename ************************************************/ - - - /** Camera::Camera::imdir ***************************************************/ - /** - * @fn imdir - * @brief set or get the image_dir base directory - * @param std::string dir_in - * @param std::string& dir_out (pass reference for return value) - * @return ERROR or NO_ERROR - * - * This function is overloaded with a form that doesn't use a return value reference. - * - * The base directory for images is this->image_dir. It is set (or read) here. It - * may contain any number of subdirectories. This function will try to create any - * needed subdirectories if they don't already exist. If autodir is set then a - * UTC date subdirectory is added later, in the get_fitsname() function. - * - */ - long Camera::imdir(std::string dir_in) { - std::string dontcare; - return imdir(dir_in, dontcare); - } - long Camera::imdir(std::string dir_in, std::string& dir_out) { - std::string function = "Camera::Camera::imdir"; - std::stringstream message; - std::vector tokens; - long error = NO_ERROR; - - // Tokenize the input string on the '/' character to get each requested - // subdirectory as a separate token. - // - Tokenize(dir_in, tokens, "/"); - - std::stringstream nextdir; // the next subdirectory to check and/or create - - // Loop through each requested subdirectory to check if they exist. - // Try to create them if they don't exist. - // - for ( auto tok : tokens ) { - - // The next directory to create -- - // start from the bottom and append each successive token. - // - nextdir << "/" << tok; - - // Check if each directory exists - // - DIR *dirp; // pointer to the directory - if ( (dirp = opendir(nextdir.str().c_str())) == NULL ) { - // If directory doesn't exist then try to create it. - // - if ( ( mkdir( nextdir.str().c_str(), (S_IRWXU | this->dirmode) ) ) == 0 ) { - message.str(""); message << "created directory " << nextdir.str(); - logwrite(function, message.str()); - } - else { // error creating date subdirectory - message.str(""); - message << "creating directory " << nextdir.str() << ": " << strerror(errno); - this->log_error( function, message.str() ); - error = ERROR; - break; + /** Camera::Camera::get_longerror *******************************************/ + + + /** Camera::Camera::writekeys ***********************************************/ + /** + * @fn writekeys + * @brief set or get the writekeys_when value + * @param std::string writekeys_in + * @param std::string& writekeys_out + * @return ERROR or NO_ERROR + * + */ + long Camera::writekeys(std::string writekeys_in, std::string &writekeys_out) { + std::string function = "Camera::Camera::writekeys"; + std::stringstream message; + long error = NO_ERROR; + + if (!writekeys_in.empty()) { + try { + std::transform(writekeys_in.begin(), writekeys_in.end(), writekeys_in.begin(), ::tolower); + // make lowercase + if (writekeys_in == "before" || writekeys_in == "after") this->writekeys_when = writekeys_in; + else { + message.str(""); + message << writekeys_in << " is invalid. Expecting before or after"; + this->log_error(function, message.str()); + error = ERROR; + } + } catch (...) { + message.str(""); + message << "unknown exception parsing argument: " << writekeys_in; + this->log_error(function, message.str()); + return ERROR; + } } - } - else { - closedir(dirp); // directory already existed so close it - } + + writekeys_out = this->writekeys_when; + return error; } - // Make sure the directory can be written to by writing a test file. - // - if ( error == NO_ERROR && !dir_in.empty() ) { - try { - std::string testfile; - testfile = dir_in + "/.tmp"; - FILE* fp = std::fopen(testfile.c_str(), "w"); // create the test file - if (!fp) { - message.str(""); message << "cannot write to requested image directory " << dir_in; - this->log_error( function, message.str() ); - error = ERROR; - } - else { // remove the test file - std::fclose(fp); - if (std::remove(testfile.c_str()) != 0) { - message.str(""); message << "removing temporary file " << testfile; - this->log_error( function, message.str() ); + /** Camera::Camera::writekeys ***********************************************/ + + + /** Camera::Camera::fitsnaming **********************************************/ + /** + * @fn fitsnaming + * @brief set or get the fits naming type + * @param std::string naming_in + * @param std::string& naming_out + * @return ERROR or NO_ERROR + * + */ + long Camera::fitsnaming(std::string naming_in, std::string &naming_out) { + std::string function = "Camera::Camera::fitsnaming"; + std::stringstream message; + long error; + + message.str(""); + message << "fits naming: " << this->fits_naming; + + if (naming_in.empty()) { + // no string passed so this is a request. do nothing but will return the current value + error = NO_ERROR; + } else if ((naming_in.compare("time") == 0) || (naming_in.compare("number") == 0)) { + this->fits_naming = naming_in; // set new value + error = NO_ERROR; + } else { + message.str(""); + message << "invalid naming type: " << naming_in << ". Must be \"time\" or \"number\"."; error = ERROR; - } } - } - catch(...) { - message.str(""); message << "writing to " << dir_in; - this->log_error( function, message.str() ); - error = ERROR; - } - if ( error == NO_ERROR) this->image_dir = dir_in; // passed all tests so set the image_dir + + error == NO_ERROR ? logwrite(function, message.str()) : this->log_error(function, message.str()); + naming_out = this->fits_naming; // return the current value + return error; } - // In any case, return the current value. - // - message.str(""); message << "image directory: " << this->image_dir; - logwrite(function, message.str()); - dir_out = this->image_dir; - return error; - } - /** Camera::Camera::imdir ***************************************************/ - - - /** Camera::Camera::autodir *************************************************/ - /** - * @fn autodir - * @brief set or get autodir_state used for creating UTC date subdirectory - * @param std::string dir_in - * @param std::string& dir_out (pass reference for return value) - * @return ERROR or NO_ERROR - * - * The base directory for images is this->image_dir. It is set (or read) here. It - * is not created; it must already exist. The date subdirectory is added later, in - * the get_fitsname() function. - * - */ - long Camera::autodir(std::string state_in, std::string& state_out) { - std::string function = "Camera::Camera::autodir"; - std::stringstream message; - long error = NO_ERROR; - - if ( !state_in.empty() ) { - try { - bool verifiedstate; - std::transform( state_in.begin(), state_in.end(), state_in.begin(), ::tolower ); // make lowercase - if ( state_in == "no" ) verifiedstate = false; - else - if ( state_in == "yes" ) verifiedstate = true; - else { - message.str(""); message << state_in << " is invalid. Expecting yes or no"; - this->log_error( function, message.str() ); - error = ERROR; + /** Camera::Camera::fitsnaming **********************************************/ + + + /** Camera::Camera::imnum ***************************************************/ + /** + * @fn imnum + * @brief set or get the image_num member + * @param std::string num_in + * @param std::string& num_out + * @return ERROR or NO_ERROR + * + */ + long Camera::imnum(std::string num_in, std::string &num_out) { + std::string function = "Camera::Camera::imnum"; + std::stringstream message; + + // If no string is passed then this is a request; return the current value. + // + if (num_in.empty()) { + message.str(""); + message << "image number: " << this->image_num; + logwrite(function, message.str()); + num_out = std::to_string(this->image_num); + return NO_ERROR; + } else { + // Otherwise check the incoming value + int num; + try { + num = std::stoi(num_in); + } catch (std::invalid_argument &) { + this->log_error(function, "invalid number: unable to convert to integer"); + return ERROR; + } + catch (std::out_of_range &) { + this->log_error(function, "imnum out of integer range"); + return ERROR; + } + if (num < 0) { + // can't be negative + message.str(""); + message << "requested image number " << num << " must be >= 0"; + this->log_error(function, message.str()); + return ERROR; + } else { + // value is OK + this->image_num = num; + num_out = num_in; + return NO_ERROR; + } } - if ( error == NO_ERROR ) this->autodir_state = verifiedstate; - } - catch (...) { - message.str(""); message << "unknown exception parsing argument: " << state_in; - this->log_error( function, message.str() ); - return ERROR; - } } - // set the return value and report the state now, either setting or getting - // - state_out = this->autodir_state ? "yes" : "no"; - message.str(""); - message << "autodir is " << ( this->autodir_state ? "ON" : "OFF" ); - logwrite( function, message.str() ); - - return error; - - } - /** Camera::Camera::autodir *************************************************/ - - - - /** Camera::Camera:set_fitstime *********************************************/ - /** - * @fn set_fitstime - * @brief set the "fitstime" variable used for the filename - * @param string formatted as "YYYY-MM-DDTHH:MM:SS.sssss" - * @return std::string - * - * The Camera class has a public string variable "fitstime" which is - * to be used for the FITS filename, when the time-format is selected. - * This time should be the whole-second of the time that the exposure - * was started, so that time is passed in here. This function strips - * that string down to just the numerals for use in the filename. - * - */ - void Camera::set_fitstime(std::string time_in) { - std::string function = "Camera::Camera::set_fitstime"; - std::stringstream message; - - if ( time_in.length() != 26 ) { // wrong number of characters, input can't be formatted correctly - message.str(""); message << "ERROR: bad input time: " << time_in; - logwrite(function, message.str()); - this->fitstime = "99999999999999"; - return; + /** Camera::Camera::imnum ***************************************************/ + + + /** Camera::Camera::basename ************************************************/ + /** + * @fn basename + * @brief set or get the base_name member + * @param std::string name_in + * @param std::string& name_out + * @return NO_ERROR + * + * This function is overloaded with a form that doesn't use a return value. + * The only restriction on base name is that it can't contain a '/' character. + * + */ + long Camera::basename(std::string name_in) { + std::string dontcare; + return basename(name_in, dontcare); } - this->fitstime = time_in.substr(0,4) // YYYY - + time_in.substr(5,2) // MM - + time_in.substr(8,2) // DD - + time_in.substr(11,2) // HH - + time_in.substr(14,2) // MM - + time_in.substr(17,2); // SS - - return; - } - /** Camera::Camera:set_fitstime *********************************************/ - - - /** Camera::Camera:get_fitsname *********************************************/ - /** - * @fn get_fitsname - * @brief assemble the FITS filename - * @param std::string controllerid (optional, due to overloading) - * @param std::string &name_out reference for name - * @return ERROR or NO_ERROR - * - * This function assembles the fully qualified path to the output FITS filename - * using the parts (dir, basename, time or number) stored in the Camera::Camera class. - * If the filename already exists then a -number is inserted, incrementing that - * number until a unique name is achieved. - * - * This function is overloaded, to allow passing a controller id to include in the filename. - * - */ - long Camera::get_fitsname(std::string &name_out) { - return ( this->get_fitsname("", name_out) ); - } - long Camera::get_fitsname(std::string controllerid, std::string &name_out) { - std::string function = "Camera::Camera::get_fitsname"; - std::stringstream message; - std::stringstream fn, fitsname; - - // image_dir is the requested base directory and now optionaly add on the date directory - // - std::stringstream basedir; - if ( this->autodir_state ) basedir << this->image_dir << "/" << get_system_date(); - else basedir << this->image_dir; - - // Make sure the directory exists - // - DIR *dirp; // pointer to the directory - if ( (dirp = opendir(basedir.str().c_str())) == NULL ) { - // If directory doesn't exist then try to create it. - // Note that this only creates the bottom-level directory, the added date part. - // The base directory has to exist. - // - if ( ( mkdir( basedir.str().c_str(), (S_IRWXU | this->dirmode) ) ) == 0 ) { - message.str(""); message << "created directory " << basedir.str(); - logwrite(function, message.str()); - } - else { // error creating date subdirectory - message.str(""); - message << "code " << errno << " creating directory " << basedir.str() << ": " << strerror(errno); - this->log_error( function, message.str() ); - // a common error might be that the base directory doesn't exist + long Camera::basename(std::string name_in, std::string &name_out) { + std::string function = "Camera::Camera::basename"; + std::stringstream message; + long error = NO_ERROR; + + // Base name cannot contain a "/" because that would be a subdirectory, + // and subdirectories are not checked here, only by imdir command. // - if (errno==ENOENT) { - message.str(""); - message << "requested base directory " << basedir.str() << " does not exist"; - this->log_error( function, message.str() ); + if (name_in.find('/') != std::string::npos) { + this->log_error(function, "basename cannot contain a '/' character"); + error = ERROR; + } else if (!name_in.empty()) { + // If a name is supplied + this->base_name = name_in; // then set the image name. + error = NO_ERROR; } - return ERROR; - } + + // In any case, log and return the current value. + // + message.str(""); + message << "base name is " << this->base_name; + logwrite(function, message.str()); + name_out = this->base_name; + + return error; } - else { - closedir(dirp); // directory already existed so close it + + /** Camera::Camera::basename ************************************************/ + + + /** Camera::Camera::imdir ***************************************************/ + /** + * @fn imdir + * @brief set or get the image_dir base directory + * @param std::string dir_in + * @param std::string& dir_out (pass reference for return value) + * @return ERROR or NO_ERROR + * + * This function is overloaded with a form that doesn't use a return value reference. + * + * The base directory for images is this->image_dir. It is set (or read) here. It + * may contain any number of subdirectories. This function will try to create any + * needed subdirectories if they don't already exist. If autodir is set then a + * UTC date subdirectory is added later, in the get_fitsname() function. + * + */ + long Camera::imdir(std::string dir_in) { + std::string dontcare; + return imdir(dir_in, dontcare); } - // start building the filename with directory/basename_ - // where "basedir" was just assembled above - // - fitsname.str(""); - fitsname << basedir.str() << "/" << this->base_name << "_"; + long Camera::imdir(std::string dir_in, std::string &dir_out) { + std::string function = "Camera::Camera::imdir"; + std::stringstream message; + std::vector tokens; + long error = NO_ERROR; + + // Tokenize the input string on the '/' character to get each requested + // subdirectory as a separate token. + // + Tokenize(dir_in, tokens, "/"); + + std::stringstream nextdir; // the next subdirectory to check and/or create + + // Loop through each requested subdirectory to check if they exist. + // Try to create them if they don't exist. + // + for (auto tok: tokens) { + // The next directory to create -- + // start from the bottom and append each successive token. + // + nextdir << "/" << tok; + + // Check if each directory exists + // + DIR *dirp; // pointer to the directory + if ((dirp = opendir(nextdir.str().c_str())) == NULL) { + // If directory doesn't exist then try to create it. + // + if ((mkdir(nextdir.str().c_str(), (S_IRWXU | this->dirmode))) == 0) { + message.str(""); + message << "created directory " << nextdir.str(); + logwrite(function, message.str()); + } else { + // error creating date subdirectory + message.str(""); + message << "creating directory " << nextdir.str() << ": " << strerror(errno); + this->log_error(function, message.str()); + error = ERROR; + break; + } + } else { + closedir(dirp); // directory already existed so close it + } + } + + // Make sure the directory can be written to by writing a test file. + // + if (error == NO_ERROR && !dir_in.empty()) { + try { + std::string testfile; + testfile = dir_in + "/.tmp"; + FILE *fp = std::fopen(testfile.c_str(), "w"); // create the test file + if (!fp) { + message.str(""); + message << "cannot write to requested image directory " << dir_in; + this->log_error(function, message.str()); + error = ERROR; + } else { + // remove the test file + std::fclose(fp); + if (std::remove(testfile.c_str()) != 0) { + message.str(""); + message << "removing temporary file " << testfile; + this->log_error(function, message.str()); + error = ERROR; + } + } + } catch (...) { + message.str(""); + message << "writing to " << dir_in; + this->log_error(function, message.str()); + error = ERROR; + } + if (error == NO_ERROR) this->image_dir = dir_in; // passed all tests so set the image_dir + } - // add the controllerid if one is given - // - if ( ! controllerid.empty() ) { - fitsname << controllerid << "_"; + // In any case, return the current value. + // + message.str(""); + message << "image directory: " << this->image_dir; + logwrite(function, message.str()); + dir_out = this->image_dir; + return error; } - // add the time or number suffix - // - if (this->fits_naming.compare("time")==0) { - fitsname << this->fitstime; + /** Camera::Camera::imdir ***************************************************/ + + + /** Camera::Camera::autodir *************************************************/ + /** + * @fn autodir + * @brief set or get autodir_state used for creating UTC date subdirectory + * @param std::string dir_in + * @param std::string& dir_out (pass reference for return value) + * @return ERROR or NO_ERROR + * + * The base directory for images is this->image_dir. It is set (or read) here. It + * is not created; it must already exist. The date subdirectory is added later, in + * the get_fitsname() function. + * + */ + long Camera::autodir(std::string state_in, std::string &state_out) { + std::string function = "Camera::Camera::autodir"; + std::stringstream message; + long error = NO_ERROR; + + if (!state_in.empty()) { + try { + bool verifiedstate; + std::transform(state_in.begin(), state_in.end(), state_in.begin(), ::tolower); // make lowercase + if (state_in == "no") verifiedstate = false; + else if (state_in == "yes") verifiedstate = true; + else { + message.str(""); + message << state_in << " is invalid. Expecting yes or no"; + this->log_error(function, message.str()); + error = ERROR; + } + if (error == NO_ERROR) this->autodir_state = verifiedstate; + } catch (...) { + message.str(""); + message << "unknown exception parsing argument: " << state_in; + this->log_error(function, message.str()); + return ERROR; + } + } + + // set the return value and report the state now, either setting or getting + // + state_out = this->autodir_state ? "yes" : "no"; + message.str(""); + message << "autodir is " << (this->autodir_state ? "ON" : "OFF"); + logwrite(function, message.str()); + + return error; } - else - if (this->fits_naming.compare("number")==0) { - // width of image_num portion of the filename is at least 4 digits, and grows as needed - // - int width = (this->image_num < 10000 ? 4 : - (this->image_num < 100000 ? 5 : - (this->image_num < 1000000 ? 6 : - (this->image_num < 10000000 ? 7 : - (this->image_num < 100000000 ? 8 : - (this->image_num < 1000000000 ? 9 : 10)))))); - fitsname << std::setfill('0') << std::setw(width) << this->image_num; + + /** Camera::Camera::autodir *************************************************/ + + + /** Camera::Camera:set_fitstime *********************************************/ + /** + * @fn set_fitstime + * @brief set the "fitstime" variable used for the filename + * @param string formatted as "YYYY-MM-DDTHH:MM:SS.sssss" + * @return std::string + * + * The Camera class has a public string variable "fitstime" which is + * to be used for the FITS filename, when the time-format is selected. + * This time should be the whole-second of the time that the exposure + * was started, so that time is passed in here. This function strips + * that string down to just the numerals for use in the filename. + * + */ + void Camera::set_fitstime(std::string time_in) { + std::string function = "Camera::Camera::set_fitstime"; + std::stringstream message; + + if (time_in.length() != 26) { + // wrong number of characters, input can't be formatted correctly + message.str(""); + message << "ERROR: bad input time: " << time_in; + logwrite(function, message.str()); + this->fitstime = "99999999999999"; + return; + } + + this->fitstime = time_in.substr(0, 4) // YYYY + + time_in.substr(5, 2) // MM + + time_in.substr(8, 2) // DD + + time_in.substr(11, 2) // HH + + time_in.substr(14, 2) // MM + + time_in.substr(17, 2); // SS + + return; } - // Check if file exists and include a -# to set apart duplicates. - // - struct stat st; - int dupnumber=1; - fn.str(""); fn << fitsname.str(); - fn << ".fits"; - while (stat(fn.str().c_str(), &st) == 0) { - fn.str(""); fn << fitsname.str(); - fn << "-" << dupnumber << ".fits"; - dupnumber++; // keep incrementing until we have a unique filename + /** Camera::Camera:set_fitstime *********************************************/ + + + /** Camera::Camera:get_fitsname *********************************************/ + /** + * @fn get_fitsname + * @brief assemble the FITS filename + * @param std::string controllerid (optional, due to overloading) + * @param std::string &name_out reference for name + * @return ERROR or NO_ERROR + * + * This function assembles the fully qualified path to the output FITS filename + * using the parts (dir, basename, time or number) stored in the Camera::Camera class. + * If the filename already exists then a -number is inserted, incrementing that + * number until a unique name is achieved. + * + * This function is overloaded, to allow passing a controller id to include in the filename. + * + */ + long Camera::get_fitsname(std::string &name_out) { + return (this->get_fitsname("", name_out)); } + long Camera::get_fitsname(std::string controllerid, std::string &name_out) { + std::string function = "Camera::Camera::get_fitsname"; + std::stringstream message; + std::stringstream fn, fitsname; + + // image_dir is the requested base directory and now optionaly add on the date directory + // + std::stringstream basedir; + if (this->autodir_state) basedir << this->image_dir << "/" << get_system_date(); + else basedir << this->image_dir; + + // Make sure the directory exists + // + DIR *dirp; // pointer to the directory + if ((dirp = opendir(basedir.str().c_str())) == NULL) { + // If directory doesn't exist then try to create it. + // Note that this only creates the bottom-level directory, the added date part. + // The base directory has to exist. + // + if ((mkdir(basedir.str().c_str(), (S_IRWXU | this->dirmode))) == 0) { + message.str(""); + message << "created directory " << basedir.str(); + logwrite(function, message.str()); + } else { + // error creating date subdirectory + message.str(""); + message << "code " << errno << " creating directory " << basedir.str() << ": " << strerror(errno); + this->log_error(function, message.str()); + // a common error might be that the base directory doesn't exist + // + if (errno == ENOENT) { + message.str(""); + message << "requested base directory " << basedir.str() << " does not exist"; + this->log_error(function, message.str()); + } + return ERROR; + } + } else { + closedir(dirp); // directory already existed so close it + } + + // start building the filename with directory/basename_ + // where "basedir" was just assembled above + // + fitsname.str(""); + fitsname << basedir.str() << "/" << this->base_name << "_"; + + // add the controllerid if one is given + // + if (!controllerid.empty()) { + fitsname << controllerid << "_"; + } + + // add the time or number suffix + // + if (this->fits_naming.compare("time") == 0) { + fitsname << this->fitstime; + } else if (this->fits_naming.compare("number") == 0) { + // width of image_num portion of the filename is at least 4 digits, and grows as needed + // + int width = (this->image_num < 10000 + ? 4 + : (this->image_num < 100000 + ? 5 + : (this->image_num < 1000000 + ? 6 + : (this->image_num < 10000000 + ? 7 + : (this->image_num < 100000000 + ? 8 + : (this->image_num < 1000000000 ? 9 : 10)))))); + fitsname << std::setfill('0') << std::setw(width) << this->image_num; + } + + // Check if file exists and include a -# to set apart duplicates. + // + struct stat st; + int dupnumber = 1; + fn.str(""); + fn << fitsname.str(); + fn << ".fits"; + while (stat(fn.str().c_str(), &st) == 0) { + fn.str(""); + fn << fitsname.str(); + fn << "-" << dupnumber << ".fits"; + dupnumber++; // keep incrementing until we have a unique filename + } + #ifdef LOGLEVEL_DEBUG - message.str(""); message << "[DEBUG] fits_naming=" << this->fits_naming + message.str(""); message << "[DEBUG] fits_naming=" << this->fits_naming << " controllerid=" << controllerid << " will write to file: " << fn.str(); logwrite(function, message.str()); #endif - name_out = fn.str(); - return NO_ERROR; - } - /** Camera::Camera:get_fitsname *********************************************/ - - - /** Camera::Camera::datacube ************************************************/ - /** - * @fn datacube - * @brief set or get the datacube state - * @param std::string state_in - * @return true or false - * - * The state_in string should be "True" or "False", case-insensitive. - * - * This function is overloaded. - * - */ - void Camera::datacube(bool state_in) { // write-only boolean - std::string dontcare; - this->datacube( (state_in ? "true" : "false"), dontcare ); - } - bool Camera::datacube() { // read-only boolean - return ( this->is_datacube ); - } - long Camera::datacube(std::string state_in, std::string &state_out) { // read-write string, called from server - std::string function = "Camera::Camera::datacube"; - std::stringstream message; - int error = NO_ERROR; - - // If something is passed then try to use it to set the datacube state - // - if ( !state_in.empty() ) { - try { - std::transform( state_in.begin(), state_in.end(), state_in.begin(), ::tolower ); // make lowercase - if (state_in == "false" ) this->is_datacube = false; - else - if (state_in == "true" ) this->is_datacube = true; - else { - message.str(""); message << state_in << " is invalid. Expecting true or false"; - this->log_error( function, message.str() ); - error = ERROR; - } - } - catch (...) { - message.str(""); message << "unknown exception parsing argument: " << state_in; - this->log_error( function, message.str() ); - error = ERROR; - } + name_out = fn.str(); + return NO_ERROR; } - // error or not, the state reported is whatever was last successfully set - // - state_out = (this->is_datacube ? "true" : "false"); - logwrite( function, state_out ); - message.str(""); message << "NOTICE:datacube=" << state_out; - this->async.enqueue( message.str() ); - - // and this lets the server know if it was set or not - // - return error; - } - /** Camera::Camera::datacube ************************************************/ - - - - /** Camera::Camera::longerror ***********************************************/ - /** - * @fn longerror - * @brief set or get the longerror state - * @param std::string state_in - * @return true or false - * - * The state_in string should be "True" or "False", case-insensitive. - * - * This function is overloaded. - * - */ - void Camera::longerror(bool state_in) { // write-only boolean - std::string dontcare; - this->longerror( (state_in ? "true" : "false"), dontcare ); - } - bool Camera::longerror() { // read-only boolean - return ( this->is_longerror ); - } - long Camera::longerror(std::string state_in, std::string &state_out) { // read-write string, called from server - std::string function = "Camera::Camera::longerror"; - std::stringstream message; - int error = NO_ERROR; - - // If something is passed then try to use it to set the longerror state - // - if ( !state_in.empty() ) { - try { - std::transform( state_in.begin(), state_in.end(), state_in.begin(), ::tolower ); // make lowercase - if (state_in == "false" ) this->is_longerror = false; - else - if (state_in == "true" ) this->is_longerror = true; - else { - message.str(""); message << state_in << " is invalid. Expecting true or false"; - this->log_error( function, message.str() ); - error = ERROR; - } - } - catch (...) { - message.str(""); message << "unknown exception parsing argument: " << state_in; - this->log_error( function, message.str() ); - error = ERROR; - } + /** Camera::Camera:get_fitsname *********************************************/ + + + /** Camera::Camera::datacube ************************************************/ + /** + * @fn datacube + * @brief set or get the datacube state + * @param std::string state_in + * @return true or false + * + * The state_in string should be "True" or "False", case-insensitive. + * + * This function is overloaded. + * + */ + void Camera::datacube(bool state_in) { + // write-only boolean + std::string dontcare; + this->datacube((state_in ? "true" : "false"), dontcare); } - // error or not, the state reported is whatever was last successfully set - // - state_out = (this->is_longerror ? "true" : "false"); - logwrite( function, state_out ); - message.str(""); message << "NOTICE:longerror=" << state_out; - this->async.enqueue( message.str() ); - - // and this lets the server know if it was set or not - // - return error; - } - /** Camera::Camera::longerror ***********************************************/ - - - /** Camera::Camera::cubeamps ************************************************/ - /** - * @fn cubeamps - * @brief set or get the cubeamps state - * @param std::string state_in - * @return true or false - * - * The state_in string should be "True" or "False", case-insensitive. - * - * This function is overloaded. - * - * datacube also gets enabled/disabled along with cubeamps. If datacube - * is needed after disabling cubeamps then it must be separately enabled. - * - */ - void Camera::cubeamps(bool state_in) { // write-only boolean - std::string dontcare; - this->cubeamps( (state_in ? "true" : "false"), dontcare ); - } - bool Camera::cubeamps() { // read-only boolean - return ( this->is_cubeamps ); - } - long Camera::cubeamps(std::string state_in, std::string &state_out) { // read-write string, called from server - std::string function = "Camera::Camera::cubeamps"; - std::stringstream message; - int error = NO_ERROR; - - // If something is passed then try to use it to set the cubeamps state - // - if ( !state_in.empty() ) { - try { - std::transform( state_in.begin(), state_in.end(), state_in.begin(), ::tolower ); // make lowercase - if (state_in == "false" ) { - this->is_cubeamps = false; - this->is_datacube = false; - } - else - if (state_in == "true" ) { - this->is_cubeamps = true; - this->is_datacube = true; + bool Camera::datacube() { + // read-only boolean + return (this->is_datacube); + } + + long Camera::datacube(std::string state_in, std::string &state_out) { + // read-write string, called from server + std::string function = "Camera::Camera::datacube"; + std::stringstream message; + int error = NO_ERROR; + + // If something is passed then try to use it to set the datacube state + // + if (!state_in.empty()) { + try { + std::transform(state_in.begin(), state_in.end(), state_in.begin(), ::tolower); // make lowercase + if (state_in == "false") this->is_datacube = false; + else if (state_in == "true") this->is_datacube = true; + else { + message.str(""); + message << state_in << " is invalid. Expecting true or false"; + this->log_error(function, message.str()); + error = ERROR; + } + } catch (...) { + message.str(""); + message << "unknown exception parsing argument: " << state_in; + this->log_error(function, message.str()); + error = ERROR; + } } - else { - message.str(""); message << state_in << " is invalid. Expecting true or false"; - this->log_error( function, message.str() ); - error = ERROR; + + // error or not, the state reported is whatever was last successfully set + // + state_out = (this->is_datacube ? "true" : "false"); + logwrite(function, state_out); + message.str(""); + message << "NOTICE:datacube=" << state_out; + this->async.enqueue(message.str()); + + // and this lets the server know if it was set or not + // + return error; + } + + /** Camera::Camera::datacube ************************************************/ + + + /** Camera::Camera::longerror ***********************************************/ + /** + * @fn longerror + * @brief set or get the longerror state + * @param std::string state_in + * @return true or false + * + * The state_in string should be "True" or "False", case-insensitive. + * + * This function is overloaded. + * + */ + void Camera::longerror(bool state_in) { + // write-only boolean + std::string dontcare; + this->longerror((state_in ? "true" : "false"), dontcare); + } + + bool Camera::longerror() { + // read-only boolean + return (this->is_longerror); + } + + long Camera::longerror(std::string state_in, std::string &state_out) { + // read-write string, called from server + std::string function = "Camera::Camera::longerror"; + std::stringstream message; + int error = NO_ERROR; + + // If something is passed then try to use it to set the longerror state + // + if (!state_in.empty()) { + try { + std::transform(state_in.begin(), state_in.end(), state_in.begin(), ::tolower); // make lowercase + if (state_in == "false") this->is_longerror = false; + else if (state_in == "true") this->is_longerror = true; + else { + message.str(""); + message << state_in << " is invalid. Expecting true or false"; + this->log_error(function, message.str()); + error = ERROR; + } + } catch (...) { + message.str(""); + message << "unknown exception parsing argument: " << state_in; + this->log_error(function, message.str()); + error = ERROR; + } } - } - catch (...) { - message.str(""); message << "unknown exception parsing argument: " << state_in; - this->log_error( function, message.str() ); - error = ERROR; - } + + // error or not, the state reported is whatever was last successfully set + // + state_out = (this->is_longerror ? "true" : "false"); + logwrite(function, state_out); + message.str(""); + message << "NOTICE:longerror=" << state_out; + this->async.enqueue(message.str()); + + // and this lets the server know if it was set or not + // + return error; } - // error or not, the state reported is whatever was last successfully set - // - state_out = (this->is_cubeamps ? "true" : "false"); - logwrite( function, state_out ); - message.str(""); message << "NOTICE:cubeamps=" << state_out; - this->async.enqueue( message.str() ); - - // and this lets the server know if it was set or not - // - return error; - } - /** Camera::Camera::cubeamps ************************************************/ - - - /**************** Camera::Information::pre_exposures ************************/ - /** - * @fn pre_exposures - * @brief set/get pre-exposures - * @param string num_in incoming value - * @param string &num_out return value - * @return ERROR or NO_ERROR - * - * Get / set number of pre-exposures, which are exposures taken by the - * controller but are not saved. This number is stored in the - * Camera:Information class and will show up in the camera_info object. - * - */ - long Information::pre_exposures( std::string num_in, std::string &num_out ) { - std::string function = "Camera::Information::pre_exposures"; - std::stringstream message; - - // If no string is passed then this is a request; return the current value. - // - if ( num_in.empty() ) { - message.str(""); message << "pre-exposures: " << this->num_pre_exposures; - logwrite( function, message.str() ); - num_out = std::to_string( this->num_pre_exposures ); - return NO_ERROR; + /** Camera::Camera::longerror ***********************************************/ + + + /** Camera::Camera::cubeamps ************************************************/ + /** + * @fn cubeamps + * @brief set or get the cubeamps state + * @param std::string state_in + * @return true or false + * + * The state_in string should be "True" or "False", case-insensitive. + * + * This function is overloaded. + * + * datacube also gets enabled/disabled along with cubeamps. If datacube + * is needed after disabling cubeamps then it must be separately enabled. + * + */ + void Camera::cubeamps(bool state_in) { + // write-only boolean + std::string dontcare; + this->cubeamps((state_in ? "true" : "false"), dontcare); } - else { // Otherwise check the incoming value - int num; - try { - num = std::stoi( num_in ); // convert incoming string to integer - } - catch ( std::invalid_argument & ) { - message.str(""); message << "ERROR: invalid number: unable to convert " << num_in << " to integer"; - logwrite( function, message.str() ); - return ERROR; - } - catch ( std::out_of_range & ) { - message.str(""); message << "ERROR: " << num_in << " out of integer range"; - logwrite( function, message.str() ); - return ERROR; - } - if ( num < 0 ) { // can't be negative - message.str(""); message << "ERROR: requested pre-exposures " << num << " must be >= 0"; - logwrite( function, message.str() ); - return ERROR; - } - else { // incoming value is OK - this->num_pre_exposures = num; // set the class variable - num_out = num_in; // set the return string value - return NO_ERROR; - } + bool Camera::cubeamps() { + // read-only boolean + return (this->is_cubeamps); + } + + long Camera::cubeamps(std::string state_in, std::string &state_out) { + // read-write string, called from server + std::string function = "Camera::Camera::cubeamps"; + std::stringstream message; + int error = NO_ERROR; + + // If something is passed then try to use it to set the cubeamps state + // + if (!state_in.empty()) { + try { + std::transform(state_in.begin(), state_in.end(), state_in.begin(), ::tolower); // make lowercase + if (state_in == "false") { + this->is_cubeamps = false; + this->is_datacube = false; + } else if (state_in == "true") { + this->is_cubeamps = true; + this->is_datacube = true; + } else { + message.str(""); + message << state_in << " is invalid. Expecting true or false"; + this->log_error(function, message.str()); + error = ERROR; + } + } catch (...) { + message.str(""); + message << "unknown exception parsing argument: " << state_in; + this->log_error(function, message.str()); + error = ERROR; + } + } + + // error or not, the state reported is whatever was last successfully set + // + state_out = (this->is_cubeamps ? "true" : "false"); + logwrite(function, state_out); + message.str(""); + message << "NOTICE:cubeamps=" << state_out; + this->async.enqueue(message.str()); + + // and this lets the server know if it was set or not + // + return error; + } + + /** Camera::Camera::cubeamps ************************************************/ + + + /**************** Camera::Information::pre_exposures ************************/ + /** + * @fn pre_exposures + * @brief set/get pre-exposures + * @param string num_in incoming value + * @param string &num_out return value + * @return ERROR or NO_ERROR + * + * Get / set number of pre-exposures, which are exposures taken by the + * controller but are not saved. This number is stored in the + * Camera:Information class and will show up in the camera_info object. + * + */ + long Information::pre_exposures(std::string num_in, std::string &num_out) { + std::string function = "Camera::Information::pre_exposures"; + std::stringstream message; + + // If no string is passed then this is a request; return the current value. + // + if (num_in.empty()) { + message.str(""); + message << "pre-exposures: " << this->num_pre_exposures; + logwrite(function, message.str()); + num_out = std::to_string(this->num_pre_exposures); + return NO_ERROR; + } else { + // Otherwise check the incoming value + int num; + try { + num = std::stoi(num_in); // convert incoming string to integer + } catch (std::invalid_argument &) { + message.str(""); + message << "ERROR: invalid number: unable to convert " << num_in << " to integer"; + logwrite(function, message.str()); + return ERROR; + } + catch (std::out_of_range &) { + message.str(""); + message << "ERROR: " << num_in << " out of integer range"; + logwrite(function, message.str()); + return ERROR; + } + if (num < 0) { + // can't be negative + message.str(""); + message << "ERROR: requested pre-exposures " << num << " must be >= 0"; + logwrite(function, message.str()); + return ERROR; + } else { + // incoming value is OK + this->num_pre_exposures = num; // set the class variable + num_out = num_in; // set the return string value + return NO_ERROR; + } + } } - } - /**************** Camera::Information::pre_exposures ************************/ + /**************** Camera::Information::pre_exposures ************************/ } diff --git a/camerad/camera.h b/camerad/camera.h index 7c216db7..75cc9179 100644 --- a/camerad/camera.h +++ b/camerad/camera.h @@ -24,193 +24,218 @@ #define SNPRINTF(VAR, ...) { snprintf(VAR, sizeof(VAR), __VA_ARGS__); } namespace Camera { - - /**************** Camera::Camera ********************************************/ - // - class Camera { + /**************** Camera::Camera ********************************************/ + // + class Camera { private: - std::string image_dir; - std::string base_name; - std::string fits_naming; + std::string image_dir; + std::string base_name; + std::string fits_naming; - std::string fitstime; //!< "YYYYMMDDHHMMSS" uesd for filename, set by get_fitsname() + std::string fitstime; //!< "YYYYMMDDHHMMSS" uesd for filename, set by get_fitsname() - mode_t dirmode; //!< user specified mode to OR with 0700 for imdir creation - int image_num; - bool is_datacube; - bool is_longerror; //!< set to return error message on command port - bool is_cubeamps; //!< should amplifiers be written as multi-extension data cubes? - std::atomic _abortstate;; + mode_t dirmode; //!< user specified mode to OR with 0700 for imdir creation + int image_num; + bool is_datacube; + bool is_longerror; //!< set to return error message on command port + bool is_cubeamps; //!< should amplifiers be written as multi-extension data cubes? + std::atomic _abortstate;; - std::mutex abort_mutex; - std::stringstream lasterrorstring; //!< a place to preserve an error message + std::mutex abort_mutex; + std::stringstream lasterrorstring; //!< a place to preserve an error message public: - Camera() : image_dir("/tmp"), base_name("image"), fits_naming("time"), - dirmode(0), image_num(0), is_datacube(false), is_longerror(false), is_cubeamps(false), _abortstate(false), - autodir_state(true), abortstate(false), writekeys_when("before") { } - - - bool autodir_state; //!< if true then images are saved in a date subdir below image_dir, i.e. image_dir/YYYYMMDD/ - bool abortstate; //!< set true to abort the current operation (exposure, readout, etc.) - - std::string writekeys_when; //!< when to write fits keys "before" or "after" exposure - Common::Queue async; /// message queue object - - void set_abortstate(bool state); - bool get_abortstate(); - - void set_dirmode( mode_t mode_in ) { this->dirmode = mode_in; } - - std::map firmware; //!< firmware file for given controller device number, read from .cfg file - std::map readout_time; //!< readout time in msec for given controller device number, read from .cfg file - - void log_error( std::string function, std::string message ); - - std::string get_longerror(); - - long imdir(std::string dir_in); - long imdir(std::string dir_in, std::string& dir_out); - long autodir(std::string state_in, std::string& state_out); - long basename(std::string name_in); - long basename(std::string name_in, std::string& name_out); - long imnum(std::string num_in, std::string& num_out); - long writekeys(std::string writekeys_in, std::string &writekeys_out); - long fitsnaming(std::string naming_in, std::string& naming_out); - void increment_imnum() { if (this->fits_naming=="number") this->image_num++; }; - void set_fitstime(std::string time_in); - long get_fitsname(std::string &name_out); - long get_fitsname(std::string controllerid, std::string &name_out); - void abort(); - void datacube(bool state_in); - bool datacube(); - long datacube(std::string state_in, std::string &state_out); - void longerror(bool state_in); - bool longerror(); - long longerror(std::string state_in, std::string &state_out); - void cubeamps(bool state_in); - bool cubeamps(); - long cubeamps(std::string state_in, std::string &state_out); - }; - /**************** Camera::Camera ********************************************/ - - typedef enum { - FRAME_IMAGE, - FRAME_RAW, - NUM_FRAME_TYPES - } frame_type_t; - - const char * const frame_type_str[NUM_FRAME_TYPES] = { - "IMAGE", - "RAW" - }; - - /**************** Camera::Information ***************************************/ - // - class Information { - private: - public: - std::string hostname; //!< Archon controller hostname - int port; //!< Archon controller TPC/IP port number - int activebufs; //!< Archon controller number of active frame buffers - int bitpix; //!< Archon bits per pixel based on SAMPLEMODE - int datatype; //!< FITS data type (corresponding to bitpix) used in set_axes() - bool type_set; //!< set when FITS data type has been defined - frame_type_t frame_type; //!< frame_type is IMAGE or RAW - long detector_pixels[2]; //!< element 0=cols (pixels), 1=rows (lines) - long section_size; //!< pixels to write for this section (could be less than full sensor size) - long image_memory; //!< bytes per image sensor - std::string current_observing_mode; //!< the current mode - std::string readout_name; //!< name of the readout source - int readout_type; //!< type of the readout source is an enum - long naxis; - long axes[2]; - int binning[2]; - long axis_pixels[2]; - long region_of_interest[4]; - long image_center[2]; - bool abortexposure; - bool iscube; //!< the info object given to the FITS writer will need to know cube status - int extension; //!< extension number for data cubes - bool shutterenable; //!< set true to allow the controller to open the shutter on expose, false to disable it - std::string shutteractivate; //!< shutter activation state - int32_t exposure_time; //!< exposure time in exposure_unit - std::string exposure_unit; //!< exposure time unit - int exposure_factor; //!< multiplier for exposure_unit relative to 1 sec (=1 for sec, =1000 for msec, etc.) - double exposure_progress; //!< exposure progress (fraction) - int num_pre_exposures; //!< pre-exposures are exposures taken but not saved - std::string fits_name; //!< contatenation of Camera's image_dir + image_name + image_num - std::string start_time; //!< system time when the exposure started (YYYY-MM-DDTHH:MM:SS.sss) - - std::vector< std::vector > amp_section; - - Common::FitsKeys userkeys; /// create a FitsKeys object for FITS keys specified by the user - Common::FitsKeys systemkeys; /// create a FitsKeys object for FITS keys imposed by the software - - - Information() { - this->axes[0] = 1; - this->axes[1] = 1; - this->binning[0] = 1; - this->binning[1] = 1; - this->region_of_interest[0] = 1; - this->region_of_interest[1] = 1; - this->region_of_interest[2] = 1; - this->region_of_interest[3] = 1; - this->image_center[0] = 1; - this->image_center[1] = 1; - this->iscube = false; - this->datatype = -1; - this->type_set = false; //!< set true when datatype has been defined - this->exposure_time = -1; //!< default exposure time is undefined - this->exposure_unit = ""; //!< default exposure unit is undefined - this->exposure_factor = -1; //!< default factor is undefined - this->shutteractivate = ""; - this->num_pre_exposures = 0; //!< default is no pre-exposures - } - - long pre_exposures( std::string num_in, std::string &num_out ); - - long set_axes() { - std::string function = "Camera::Information::set_axes"; - std::stringstream message; - long bytes_per_pixel; - - if ( this->frame_type == FRAME_RAW ) { - bytes_per_pixel = 2; - this->datatype = USHORT_IMG; + Camera() : image_dir("/tmp"), base_name("image"), fits_naming("time"), + dirmode(0), image_num(0), is_datacube(false), is_longerror(false), is_cubeamps(false), + _abortstate(false), + autodir_state(true), abortstate(false), writekeys_when("before") { } - else { - switch ( this->bitpix ) { - case 16: - bytes_per_pixel = 2; - this->datatype = SHORT_IMG; - break; - case 32: - bytes_per_pixel = 4; - this->datatype = FLOAT_IMG; - break; - default: - message << "ERROR: unknown bitpix " << this->bitpix << ": expected {16,32}"; - logwrite( function, message.str() ); - return (ERROR); - } - } - this->type_set = true; // datatype has been set - this->naxis = 2; - this->axis_pixels[0] = this->region_of_interest[1] - - this->region_of_interest[0] + 1; - this->axis_pixels[1] = this->region_of_interest[3] - - this->region_of_interest[2] + 1; + bool autodir_state; + //!< if true then images are saved in a date subdir below image_dir, i.e. image_dir/YYYYMMDD/ + bool abortstate; //!< set true to abort the current operation (exposure, readout, etc.) + + std::string writekeys_when; //!< when to write fits keys "before" or "after" exposure + Common::Queue async; /// message queue object + + void set_abortstate(bool state); + + bool get_abortstate(); + + void set_dirmode(mode_t mode_in) { this->dirmode = mode_in; } + + std::map firmware; //!< firmware file for given controller device number, read from .cfg file + std::map readout_time; + //!< readout time in msec for given controller device number, read from .cfg file + + void log_error(std::string function, std::string message); + + std::string get_longerror(); + + long imdir(std::string dir_in); + + long imdir(std::string dir_in, std::string &dir_out); + + long autodir(std::string state_in, std::string &state_out); + + long basename(std::string name_in); + + long basename(std::string name_in, std::string &name_out); + + long imnum(std::string num_in, std::string &num_out); + + long writekeys(std::string writekeys_in, std::string &writekeys_out); + + long fitsnaming(std::string naming_in, std::string &naming_out); + + void increment_imnum() { if (this->fits_naming == "number") this->image_num++; }; - this->axes[0] = this->axis_pixels[0] / this->binning[0]; - this->axes[1] = this->axis_pixels[1] / this->binning[1]; + void set_fitstime(std::string time_in); - this->section_size = this->axes[0] * this->axes[1]; // Pixels to write for this image section - this->image_memory = this->detector_pixels[0] - * this->detector_pixels[1] * bytes_per_pixel; // Bytes per detector + long get_fitsname(std::string &name_out); + + long get_fitsname(std::string controllerid, std::string &name_out); + + void abort(); + + void datacube(bool state_in); + + bool datacube(); + + long datacube(std::string state_in, std::string &state_out); + + void longerror(bool state_in); + + bool longerror(); + + long longerror(std::string state_in, std::string &state_out); + + void cubeamps(bool state_in); + + bool cubeamps(); + + long cubeamps(std::string state_in, std::string &state_out); + }; + + /**************** Camera::Camera ********************************************/ + + typedef enum { + FRAME_IMAGE, + FRAME_RAW, + NUM_FRAME_TYPES + } frame_type_t; + + const char *const frame_type_str[NUM_FRAME_TYPES] = { + "IMAGE", + "RAW" + }; + + /**************** Camera::Information ***************************************/ + // + class Information { + private: + public: + std::string hostname; //!< Archon controller hostname + int port; //!< Archon controller TPC/IP port number + int activebufs; //!< Archon controller number of active frame buffers + int bitpix; //!< Archon bits per pixel based on SAMPLEMODE + int datatype; //!< FITS data type (corresponding to bitpix) used in set_axes() + bool type_set; //!< set when FITS data type has been defined + frame_type_t frame_type; //!< frame_type is IMAGE or RAW + long detector_pixels[2]; //!< element 0=cols (pixels), 1=rows (lines) + long section_size; //!< pixels to write for this section (could be less than full sensor size) + long image_memory; //!< bytes per image sensor + std::string current_observing_mode; //!< the current mode + std::string readout_name; //!< name of the readout source + int readout_type; //!< type of the readout source is an enum + long naxis; + long axes[2]; + int binning[2]; + long axis_pixels[2]; + long region_of_interest[4]; + long image_center[2]; + bool abortexposure; + bool iscube; //!< the info object given to the FITS writer will need to know cube status + int extension; //!< extension number for data cubes + bool shutterenable; //!< set true to allow the controller to open the shutter on expose, false to disable it + std::string shutteractivate; //!< shutter activation state + int32_t exposure_time; //!< exposure time in exposure_unit + std::string exposure_unit; //!< exposure time unit + int exposure_factor; //!< multiplier for exposure_unit relative to 1 sec (=1 for sec, =1000 for msec, etc.) + double exposure_progress; //!< exposure progress (fraction) + int num_pre_exposures; //!< pre-exposures are exposures taken but not saved + std::string fits_name; //!< contatenation of Camera's image_dir + image_name + image_num + std::string start_time; //!< system time when the exposure started (YYYY-MM-DDTHH:MM:SS.sss) + + std::vector > amp_section; + + Common::FitsKeys userkeys; /// create a FitsKeys object for FITS keys specified by the user + Common::FitsKeys systemkeys; /// create a FitsKeys object for FITS keys imposed by the software + + + Information() { + this->axes[0] = 1; + this->axes[1] = 1; + this->binning[0] = 1; + this->binning[1] = 1; + this->region_of_interest[0] = 1; + this->region_of_interest[1] = 1; + this->region_of_interest[2] = 1; + this->region_of_interest[3] = 1; + this->image_center[0] = 1; + this->image_center[1] = 1; + this->iscube = false; + this->datatype = -1; + this->type_set = false; //!< set true when datatype has been defined + this->exposure_time = -1; //!< default exposure time is undefined + this->exposure_unit = ""; //!< default exposure unit is undefined + this->exposure_factor = -1; //!< default factor is undefined + this->shutteractivate = ""; + this->num_pre_exposures = 0; //!< default is no pre-exposures + } + + long pre_exposures(std::string num_in, std::string &num_out); + + long set_axes() { + std::string function = "Camera::Information::set_axes"; + std::stringstream message; + long bytes_per_pixel; + + if (this->frame_type == FRAME_RAW) { + bytes_per_pixel = 2; + this->datatype = USHORT_IMG; + } else { + switch (this->bitpix) { + case 16: + bytes_per_pixel = 2; + this->datatype = SHORT_IMG; + break; + case 32: + bytes_per_pixel = 4; + this->datatype = FLOAT_IMG; + break; + default: + message << "ERROR: unknown bitpix " << this->bitpix << ": expected {16,32}"; + logwrite(function, message.str()); + return (ERROR); + } + } + this->type_set = true; // datatype has been set + + this->naxis = 2; + + this->axis_pixels[0] = this->region_of_interest[1] - + this->region_of_interest[0] + 1; + this->axis_pixels[1] = this->region_of_interest[3] - + this->region_of_interest[2] + 1; + + this->axes[0] = this->axis_pixels[0] / this->binning[0]; + this->axes[1] = this->axis_pixels[1] / this->binning[1]; + + this->section_size = this->axes[0] * this->axes[1]; // Pixels to write for this image section + this->image_memory = this->detector_pixels[0] + * this->detector_pixels[1] * bytes_per_pixel; // Bytes per detector #ifdef LOGLEVEL_DEBUG message << "[DEBUG] region_of_interest[1]=" << this->region_of_interest[1] @@ -222,9 +247,9 @@ namespace Camera { logwrite( function, message.str() ); #endif - return (NO_ERROR); - } - }; - /**************** Camera::Information ***************************************/ + return (NO_ERROR); + } + }; + /**************** Camera::Information ***************************************/ } diff --git a/camerad/camerad.cpp b/camerad/camerad.cpp index 4b90b127..c3431205 100644 --- a/camerad/camerad.cpp +++ b/camerad/camerad.cpp @@ -13,9 +13,9 @@ Camera::Server server; -std::string log_path; /// must set in config file -std::string log_tmzone; /// can set in config file, default UTC if empty -std::string log_tostderr="true"; /// can be overridden by config file +std::string log_path; /// must set in config file +std::string log_tmzone; /// can set in config file, default UTC if empty +std::string log_tostderr = "true"; /// can be overridden by config file /** signal_handler ***********************************************************/ /** @@ -26,38 +26,39 @@ std::string log_tostderr="true"; /// can be overridden by config file * */ void signal_handler(int signo) { - std::string function = "Camera::signal_handler"; - switch (signo) { - case SIGTERM: - case SIGINT: - logwrite(function, "received termination signal"); - server.camera.async.enqueue("exit"); // shutdown the async_main thread if running - server.exit_cleanly(); // shutdown the server - break; - case SIGHUP: - logwrite(function, "caught SIGHUP"); - server.configure_controller(); - break; - case SIGPIPE: - logwrite(function, "caught SIGPIPE"); - break; - default: - logwrite(function, "received unknown signal"); - server.camera.async.enqueue("exit"); // shutdown the async_main thread if running - server.exit_cleanly(); // shutdown the server - break; - } - return; + std::string function = "Camera::signal_handler"; + switch (signo) { + case SIGTERM: + case SIGINT: + logwrite(function, "received termination signal"); + server.camera.async.enqueue("exit"); // shutdown the async_main thread if running + server.exit_cleanly(); // shutdown the server + break; + case SIGHUP: + logwrite(function, "caught SIGHUP"); + server.configure_controller(); + break; + case SIGPIPE: + logwrite(function, "caught SIGPIPE"); + break; + default: + logwrite(function, "received unknown signal"); + server.camera.async.enqueue("exit"); // shutdown the async_main thread if running + server.exit_cleanly(); // shutdown the server + break; + } + return; } + /** signal_handler ***********************************************************/ -int main(int argc, char **argv); // main thread (just gets things started) -void new_log_day(); // create a new log each day -void block_main(Network::TcpSocket sock); // this thread handles requests on blocking port -void thread_main(Network::TcpSocket sock); // this thread handles requests on non-blocking port -void async_main(Network::UdpSocket sock); // this thread handles the asyncrhonous UDP message port -void doit(Network::TcpSocket sock); // the worker thread +int main(int argc, char **argv); // main thread (just gets things started) +void new_log_day(); // create a new log each day +void block_main(Network::TcpSocket sock); // this thread handles requests on blocking port +void thread_main(Network::TcpSocket sock); // this thread handles requests on non-blocking port +void async_main(Network::UdpSocket sock); // this thread handles the asyncrhonous UDP message port +void doit(Network::TcpSocket sock); // the worker thread /** main *********************************************************************/ @@ -69,201 +70,221 @@ void doit(Network::TcpSocket sock); // the worker thread * */ int main(int argc, char **argv) { - std::string function = "Camera::main"; - std::stringstream message; - std::string cwd = std::filesystem::current_path().string(); - long ret=NO_ERROR; - - // Daemonize by default, but allow command line arg to keep it as - // a foreground process - // - if ( ! cmdOptionExists( argv, argv+argc, "--foreground" ) ) { - logwrite( function, "starting daemon" ); - Daemon::daemonize( Camera::DAEMON_NAME, cwd, "", "", "" ); - } - - // capture these signals - // - signal(SIGINT, signal_handler); - signal(SIGPIPE, signal_handler); - signal(SIGHUP, signal_handler); - - // check for "-f " command line option to specify config file - // - if ( cmdOptionExists( argv, argv+argc, "-f" ) ) { - char* filename = getCmdOption( argv, argv+argc, "-f" ); - if ( filename ) { - server.config.filename = std::string( filename ); + std::string function = "Camera::main"; + std::stringstream message; + std::string cwd = std::filesystem::current_path().string(); + long ret = NO_ERROR; + + // Daemonize by default, but allow command line arg to keep it as + // a foreground process + // + if (!cmdOptionExists(argv, argv + argc, "--foreground")) { + logwrite(function, "starting daemon"); + Daemon::daemonize(Camera::DAEMON_NAME, cwd, "", "", ""); } - } else if (argc>1) { - // if no "-f " then as long as there's at least one arg, - // assume that is the config file name. - // - server.config.filename = std::string( argv[1] ); - } else { - logwrite(function, "ERROR: no configuration file specified"); - server.exit_cleanly(); - } - - if ( server.config.read_config(server.config) != NO_ERROR) { // read configuration file specified on command line - logwrite(function, "ERROR: unable to configure system"); - server.exit_cleanly(); - } - - // camerad would like a few configuration keys before the daemon starts up - // - for (int entry=0; entry < server.config.n_entries; entry++) { - if (server.config.param[entry] == "LOGPATH") log_path = server.config.arg[entry]; // where to write log files - - if (server.config.param[entry] == "LOGSTDERR") { // should I log also to stderr? - std::string stderrstr = server.config.arg[entry]; - std::transform( stderrstr.begin(), stderrstr.end(), stderrstr.begin(), ::tolower ); - - if ( stderrstr != "true" && stderrstr != "false" ) { - message.str(""); message << "ERROR unknown LOGSTDERR=\"" << stderrstr << "\": expected true|false"; - logwrite( function, message.str() ); + + // capture these signals + // + signal(SIGINT, signal_handler); + signal(SIGPIPE, signal_handler); + signal(SIGHUP, signal_handler); + + // check for "-f " command line option to specify config file + // + if (cmdOptionExists(argv, argv + argc, "-f")) { + char *filename = getCmdOption(argv, argv + argc, "-f"); + if (filename) { + server.config.filename = std::string(filename); + } + } else if (argc > 1) { + // if no "-f " then as long as there's at least one arg, + // assume that is the config file name. + // + server.config.filename = std::string(argv[1]); + } else { + logwrite(function, "ERROR: no configuration file specified"); server.exit_cleanly(); - } - else { - log_tostderr = stderrstr; - message.str(""); message << "config:" << server.config.param[entry] << "=" << server.config.arg[entry]; - logwrite( function, message.str() ); - server.camera.async.enqueue( message.str() ); - } } - // Specifies time zone for logging only, local|UTC - // - if ( server.config.param[entry] == "TM_ZONE_LOG" ) { - if ( server.config.arg[entry] != "UTC" && server.config.arg[entry] != "local" ) { - message.str(""); message << "ERROR invalid TM_ZONE_LOG=" << server.config.arg[entry] << ": expected UTC|local"; - logwrite( function, message.str() ); + if (server.config.read_config(server.config) != NO_ERROR) { + // read configuration file specified on command line + logwrite(function, "ERROR: unable to configure system"); server.exit_cleanly(); - } - log_tmzone = server.config.arg[entry]; - message.str(""); message << "config:" << server.config.param[entry] << "=" << server.config.arg[entry]; - logwrite( function, message.str() ); - server.camera.async.enqueue( message.str() ); } - // Specifies time zone for everything else, local|UTC + // camerad would like a few configuration keys before the daemon starts up // - if ( server.config.param[entry] == "TM_ZONE" ) { - if ( server.config.arg[entry] != "UTC" && server.config.arg[entry] != "local" ) { - message.str(""); message << "ERROR invalid TM_ZONE=" << server.config.arg[entry] << ": expected UTC|local"; - logwrite( function, message.str() ); + for (int entry = 0; entry < server.config.n_entries; entry++) { + if (server.config.param[entry] == "LOGPATH") log_path = server.config.arg[entry]; // where to write log files + + if (server.config.param[entry] == "LOGSTDERR") { + // should I log also to stderr? + std::string stderrstr = server.config.arg[entry]; + std::transform(stderrstr.begin(), stderrstr.end(), stderrstr.begin(), ::tolower); + + if (stderrstr != "true" && stderrstr != "false") { + message.str(""); + message << "ERROR unknown LOGSTDERR=\"" << stderrstr << "\": expected true|false"; + logwrite(function, message.str()); + server.exit_cleanly(); + } else { + log_tostderr = stderrstr; + message.str(""); + message << "config:" << server.config.param[entry] << "=" << server.config.arg[entry]; + logwrite(function, message.str()); + server.camera.async.enqueue(message.str()); + } + } + + // Specifies time zone for logging only, local|UTC + // + if (server.config.param[entry] == "TM_ZONE_LOG") { + if (server.config.arg[entry] != "UTC" && server.config.arg[entry] != "local") { + message.str(""); + message << "ERROR invalid TM_ZONE_LOG=" << server.config.arg[entry] << ": expected UTC|local"; + logwrite(function, message.str()); + server.exit_cleanly(); + } + log_tmzone = server.config.arg[entry]; + message.str(""); + message << "config:" << server.config.param[entry] << "=" << server.config.arg[entry]; + logwrite(function, message.str()); + server.camera.async.enqueue(message.str()); + } + + // Specifies time zone for everything else, local|UTC + // + if (server.config.param[entry] == "TM_ZONE") { + if (server.config.arg[entry] != "UTC" && server.config.arg[entry] != "local") { + message.str(""); + message << "ERROR invalid TM_ZONE=" << server.config.arg[entry] << ": expected UTC|local"; + logwrite(function, message.str()); + server.exit_cleanly(); + } + message.str(""); + message << "TM_ZONE=" << server.config.arg[entry] << "//time zone"; + server.systemkeys.addkey(message.str()); + tmzone_cfg = server.config.arg[entry]; + message.str(""); + message << "config:" << server.config.param[entry] << "=" << server.config.arg[entry]; + logwrite(function, message.str()); + server.camera.async.enqueue(message.str()); + } + + // Sets TZ environment variable (important for local time zone) + // + if (server.config.param[entry] == "TZ_ENV") { + setenv("TZ", server.config.arg[entry].c_str(), 1); + tzset(); + message.str(""); + message << "config:" << server.config.param[entry] << "=" << server.config.arg[entry]; + logwrite(function, message.str()); + server.camera.async.enqueue(message.str()); + } + } + + if (log_path.empty()) { + logwrite(function, "ERROR LOGPATH not specified in configuration file"); + server.exit_cleanly(); + } + + if ((init_log(Camera::DAEMON_NAME, log_path, log_tostderr, log_tmzone) != 0)) { + // initialize the logging system + std::cerr << get_timestamp(log_tmzone) << " (" << function << ") ERROR unable to initialize logging system\n"; server.exit_cleanly(); - } - message.str(""); message << "TM_ZONE=" << server.config.arg[entry] << "//time zone"; - server.systemkeys.addkey( message.str() ); - tmzone_cfg = server.config.arg[entry]; - message.str(""); message << "config:" << server.config.param[entry] << "=" << server.config.arg[entry]; - logwrite( function, message.str() ); - server.camera.async.enqueue( message.str() ); } - // Sets TZ environment variable (important for local time zone) + if (log_tostderr.empty()) { + logwrite(function, "LOGSTDERR=(empty): logs will be echoed to stderr"); + } + + // log and add server build date to system keys db // - if (server.config.param[entry] == "TZ_ENV") { - setenv( "TZ", server.config.arg[entry].c_str(), 1 ); - tzset(); - message.str(""); message << "config:" << server.config.param[entry] << "=" << server.config.arg[entry]; - logwrite( function, message.str() ); - server.camera.async.enqueue( message.str() ); + message.str(""); + message << "this version built " << BUILD_DATE << " " << BUILD_TIME; + logwrite(function, message.str()); + + message.str(""); + message << "CAMD_VER=" << BUILD_DATE << " " << BUILD_TIME << " // camerad build date"; + server.systemkeys.addkey(message.str()); + + message.str(""); + message << server.config.n_entries << " lines read from " << server.config.filename; + logwrite(function, message.str()); + + if (ret == NO_ERROR) ret = server.configure_server(); + // get needed values out of read-in configuration file for the server + if (ret == NO_ERROR) ret = server.configure_controller(); + // get needed values out of read-in configuration file for the controller + + if (ret != NO_ERROR) { + logwrite(function, "ERROR: unable to configure system"); + server.exit_cleanly(); + } + + if (server.nbport == -1 || server.blkport == -1) { + logwrite(function, "ERROR: server ports not configured"); + server.exit_cleanly(); } - } - - if ( log_path.empty() ) { - logwrite( function, "ERROR LOGPATH not specified in configuration file" ); - server.exit_cleanly(); - } - - if ( ( init_log( Camera::DAEMON_NAME, log_path, log_tostderr, log_tmzone ) != 0 ) ) { // initialize the logging system - std::cerr << get_timestamp(log_tmzone) << " (" << function << ") ERROR unable to initialize logging system\n"; - server.exit_cleanly(); - } - - if ( log_tostderr.empty() ) { - logwrite( function, "LOGSTDERR=(empty): logs will be echoed to stderr" ); - } - - // log and add server build date to system keys db - // - message.str(""); message << "this version built " << BUILD_DATE << " " << BUILD_TIME; - logwrite(function, message.str()); - - message.str(""); message << "CAMD_VER=" << BUILD_DATE << " " << BUILD_TIME << " // camerad build date"; - server.systemkeys.addkey( message.str() ); - - message.str(""); message << server.config.n_entries << " lines read from " << server.config.filename; - logwrite(function, message.str()); - - if (ret==NO_ERROR) ret=server.configure_server(); // get needed values out of read-in configuration file for the server - if (ret==NO_ERROR) ret=server.configure_controller(); // get needed values out of read-in configuration file for the controller - - if (ret != NO_ERROR) { - logwrite(function, "ERROR: unable to configure system"); - server.exit_cleanly(); - } - - if (server.nbport == -1 || server.blkport == -1) { - logwrite(function, "ERROR: server ports not configured"); - server.exit_cleanly(); - } - - // This will pre-thread N_THREADS threads. - // The 0th thread is reserved for the blocking port, and the rest are for the non-blocking port. - // Each thread gets a socket object. All of the socket objects are stored in a vector container. - // The blocking thread socket object is of course unique. - // For the non-blocking thread socket objects, create a listening socket with one object, - // then the remaining objects are copies of the first. - // - // TcpSocket objects are instantiated with (PORT#, BLOCKING_STATE, POLL_TIMEOUT_MSEC, THREAD_ID#) - // - std::vector socklist; // create a vector container to hold N_THREADS TcpSocket objects - socklist.reserve(N_THREADS); - - Network::TcpSocket sck(server.blkport, true, -1, 0); // instantiate TcpSocket object with blocking port - if ( sck.Listen() < 0 ) { // create a listening socket - logwrite( function, "ERROR could not create listening socket" ); - server.exit_cleanly(); - } - socklist.push_back(sck); // add it to the socklist vector - std::thread(block_main, socklist[0]).detach(); // spawn a thread to handle requests on this socket - - // pre-thread N_THREADS-1 detached threads to handle requests on the non-blocking port - // thread #0 is reserved for the blocking port (above) - // - for (int i=1; i socklist; // create a vector container to hold N_THREADS TcpSocket objects + socklist.reserve(N_THREADS); + + Network::TcpSocket sck(server.blkport, true, -1, 0); // instantiate TcpSocket object with blocking port + if (sck.Listen() < 0) { + // create a listening socket + logwrite(function, "ERROR could not create listening socket"); server.exit_cleanly(); - } - socklist.push_back(sck); } - else { // subsequent socket objects are copies of the first - Network::TcpSocket sck = socklist[1]; // copy the first one, which has a valid listening socket - sck.id = i; - socklist.push_back(sck); + socklist.push_back(sck); // add it to the socklist vector + std::thread(block_main, socklist[0]).detach(); // spawn a thread to handle requests on this socket + + // pre-thread N_THREADS-1 detached threads to handle requests on the non-blocking port + // thread #0 is reserved for the blocking port (above) + // + for (int i = 1; i < N_THREADS; i++) { + // create N_THREADS-1 non-blocking socket objects + if (i == 1) { + // first one only + Network::TcpSocket sck(server.nbport, false, CONN_TIMEOUT, i); + // instantiate TcpSocket object, non-blocking port, CONN_TIMEOUT timeout + if (sck.Listen() < 0) { + // create a listening socket + logwrite(function, "ERROR could not create listening socket"); + server.exit_cleanly(); + } + socklist.push_back(sck); + } else { + // subsequent socket objects are copies of the first + Network::TcpSocket sck = socklist[1]; // copy the first one, which has a valid listening socket + sck.id = i; + socklist.push_back(sck); + } + std::thread(thread_main, socklist[i]).detach(); // spawn a thread to handle each non-blocking socket request } - std::thread(thread_main, socklist[i]).detach(); // spawn a thread to handle each non-blocking socket request - } - // Instantiate a multicast UDP object and spawn a thread to send asynchronous messages - // - Network::UdpSocket async(server.asyncport, server.asyncgroup); - std::thread(async_main, async).detach(); + // Instantiate a multicast UDP object and spawn a thread to send asynchronous messages + // + Network::UdpSocket async(server.asyncport, server.asyncgroup); + std::thread(async_main, async).detach(); - // thread to start a new logbook each day - // - std::thread( new_log_day ).detach(); + // thread to start a new logbook each day + // + std::thread(new_log_day).detach(); - for (;;) pause(); // main thread suspends - return 0; + for (;;) pause(); // main thread suspends + return 0; } + /** main *********************************************************************/ @@ -280,13 +301,14 @@ int main(int argc, char **argv) { * is set by init_log. * */ -void new_log_day() { - while (true) { - std::this_thread::sleep_for( std::chrono::seconds( nextday ) ); - close_log(); - init_log( Camera::DAEMON_NAME, log_path, log_tostderr, log_tmzone ); - } +void new_log_day() { + while (true) { + std::this_thread::sleep_for(std::chrono::seconds(nextday)); + close_log(); + init_log(Camera::DAEMON_NAME, log_path, log_tostderr, log_tmzone); + } } + /** new_log_day **************************************************************/ @@ -304,13 +326,14 @@ void new_log_day() { * */ void block_main(Network::TcpSocket sock) { - while (true) { - sock.Accept(); - doit(sock); // call function to do the work - sock.Close(); - } - return; + while (true) { + sock.Accept(); + doit(sock); // call function to do the work + sock.Close(); + } + return; } + /** block_main ***************************************************************/ @@ -332,15 +355,16 @@ void block_main(Network::TcpSocket sock) { * */ void thread_main(Network::TcpSocket sock) { - while (true) { - server.conn_mutex.lock(); - sock.Accept(); - server.conn_mutex.unlock(); - doit(sock); // call function to do the work - sock.Close(); - } - return; + while (true) { + server.conn_mutex.lock(); + sock.Accept(); + server.conn_mutex.unlock(); + doit(sock); // call function to do the work + sock.Close(); + } + return; } + /** thread_main **************************************************************/ @@ -356,33 +380,36 @@ void thread_main(Network::TcpSocket sock) { * */ void async_main(Network::UdpSocket sock) { - std::string function = "Camera::async_main"; - int retval; - - retval = sock.Create(); // create the UDP socket - if (retval < 0) { - logwrite(function, "error creating UDP multicast socket for asynchronous messages"); - server.exit_cleanly(); // do not continue on error - } - if (retval==1) { // exit this thread but continue with server - logwrite(function, "asyncrhonous message port disabled by request"); - } - - while (true) { - std::string message = server.camera.async.dequeue(); // get the latest message from the queue (blocks) - retval = sock.Send(message); // transmit the message + std::string function = "Camera::async_main"; + int retval; + + retval = sock.Create(); // create the UDP socket if (retval < 0) { - std::stringstream errstm; - errstm << "error sending UDP message: " << message; - logwrite(function, errstm.str()); + logwrite(function, "error creating UDP multicast socket for asynchronous messages"); + server.exit_cleanly(); // do not continue on error + } + if (retval == 1) { + // exit this thread but continue with server + logwrite(function, "asyncrhonous message port disabled by request"); } - if (message=="exit") { // terminate this thread - sock.Close(); - return; + + while (true) { + std::string message = server.camera.async.dequeue(); // get the latest message from the queue (blocks) + retval = sock.Send(message); // transmit the message + if (retval < 0) { + std::stringstream errstm; + errstm << "error sending UDP message: " << message; + logwrite(function, errstm.str()); + } + if (message == "exit") { + // terminate this thread + sock.Close(); + return; + } } - } - return; + return; } + /** async_main ***************************************************************/ @@ -395,206 +422,209 @@ void async_main(Network::UdpSocket sock) { * * stays open until closed by client * - * commands come in the form: + * commands come in the form: * [all|] [_BLOCK_] [] * */ void doit(Network::TcpSocket sock) { - std::string function = "Camera::doit"; - char buf[BUFSIZE+1]; - long ret; - std::stringstream message; - std::string cmd, args; // arg string is everything after command - std::vector tokens; - - bool connection_open=true; - - message.str(""); message << "thread " << sock.id << " accepted connection on fd " << sock.getfd(); - logwrite( function, message.str() ); - - while (connection_open) { - memset(buf, '\0', BUFSIZE); // init buffers - - // Wait (poll) connected socket for incoming data... - // - int pollret; - if ( ( pollret=sock.Poll() ) <= 0 ) { - if (pollret==0) { - message.str(""); message << "Poll timeout on fd " << sock.getfd() << " thread " << sock.id; - logwrite(function, message.str()); - } - if (pollret <0) { - message.str(""); message << "Poll error on fd " << sock.getfd() << " thread " << sock.id << ": " << strerror(errno); - logwrite(function, message.str()); - } - break; // this will close the connection - } - - // Data available, now read from connected socket... - // - std::string sbuf; - char delim='\n'; - if ( ( ret=sock.Read( sbuf, delim ) ) <= 0 ) { - if (ret<0) { // could be an actual read error - message.str(""); message << "Read error on fd " << sock.getfd() << ": " << strerror(errno); logwrite(function, message.str()); - } - if (ret==0) { - message.str(""); message << "timeout reading from fd " << sock.getfd(); - logwrite( function, message.str() ); - } - break; // Breaking out of the while loop will close the connection. - // This probably means that the client has terminated abruptly, - // having sent FIN but not stuck around long enough - // to accept CLOSE and give the LAST_ACK. - } - - // convert the input buffer into a string and remove any trailing linefeed - // and carriage return - // - sbuf.erase(std::remove(sbuf.begin(), sbuf.end(), '\r' ), sbuf.end()); - sbuf.erase(std::remove(sbuf.begin(), sbuf.end(), '\n' ), sbuf.end()); - - if (sbuf.empty()) {sock.Write("\n"); continue;} // acknowledge empty command so client doesn't time out - - try { - std::size_t cmd_sep = sbuf.find_first_of(" "); // find the first space, which separates command from argument list - - cmd = sbuf.substr(0, cmd_sep); // cmd is everything up until that space - - if (cmd.empty()) {sock.Write("\n"); continue;} // acknowledge empty command so client doesn't time out - - if (cmd_sep == std::string::npos) { // If no space was found, - args=""; // then the arg list is empty, - } - else { - args= sbuf.substr(cmd_sep+1); // otherwise args is everything after that space. - } - - message.str(""); message << "thread " << sock.id << " received command on fd " << sock.getfd() << ": " << cmd << " " << args; - logwrite(function, message.str()); - } - catch ( std::runtime_error &e ) { - std::stringstream errstream; errstream << e.what(); - message.str(""); message << "error parsing arguments: " << errstream.str(); - logwrite(function, message.str()); - ret = -1; - } - catch ( ... ) { - message.str(""); message << "unknown error parsing arguments: " << args; - logwrite(function, message.str()); - ret = -1; - } - - /** - * process commands here - */ - ret = NOTHING; - std::string retstring; // string for return the value (where needed) - - if (cmd=="exit") { - server.camera.async.enqueue("exit"); // shutdown the async message thread if running - server.exit_cleanly(); // shutdown the server - } - else - if (cmd=="config") { // report the config file used for camerad - std::stringstream cfg; - cfg << "CONFIG:" << server.config.filename; - server.camera.async.enqueue( cfg.str() ); - sock.Write( server.config.filename ); - sock.Write( " " ); - ret = NO_ERROR; - } - else - if (cmd=="open") { - ret = server.connect_controller(args); - } - else - if (cmd=="close") { - ret = server.disconnect_controller(); - } - else - if (cmd=="load") { - if (args.empty()) ret = server.load_firmware(retstring); - else ret = server.load_firmware(args, retstring); - if (!retstring.empty()) { sock.Write(retstring); sock.Write(" "); } - } - else - if (cmd=="basename") { - ret = server.camera.basename(args, retstring); - sock.Write(retstring); - sock.Write(" "); - } - else - if (cmd=="imnum") { - ret = server.camera.imnum(args, retstring); - if (!retstring.empty()) { sock.Write(retstring); sock.Write(" "); } - } - else - if (cmd=="imdir") { - ret = server.camera.imdir(args, retstring); - sock.Write(retstring); - sock.Write(" "); - } - else - if (cmd=="autodir") { - ret = server.camera.autodir(args, retstring); - if (!retstring.empty()) { sock.Write(retstring); sock.Write(" "); } - } - else - if (cmd=="datacube") { - ret = server.camera.datacube(args, retstring); - sock.Write(retstring); - sock.Write(" "); - } - else - if (cmd=="longerror") { - ret = server.camera.longerror(args, retstring); - sock.Write(retstring); - sock.Write(" "); - } - else - if (cmd=="preexposures") { - ret = server.camera_info.pre_exposures( args, retstring ); - sock.Write( retstring ); - sock.Write( " " ); - } - else - if (cmd=="cubeamps") { - ret = server.camera.cubeamps(args, retstring); - sock.Write(retstring); - sock.Write(" "); - } - else - if (cmd=="fitsnaming") { - ret = server.camera.fitsnaming(args, retstring); - if (!retstring.empty()) { sock.Write(retstring); sock.Write(" "); } - } - else - if (cmd=="shutter") { - ret = server.shutter(args, retstring); - if (!retstring.empty()) { sock.Write(retstring); sock.Write(" "); } - } - else - if (cmd=="writekeys") { - ret = server.camera.writekeys(args, retstring); - if (!retstring.empty()) { sock.Write(retstring); sock.Write(" "); } - } - else - if (cmd=="key") { - if (args.compare(0, 4, "list")==0) { - logwrite( function, "systemkeys:" ); ret = server.systemkeys.listkeys(); - logwrite( function, "userkeys:" ); ret = server.userkeys.listkeys(); - } - else { - ret = server.userkeys.addkey(args); - if ( ret != NO_ERROR ) server.camera.log_error( function, "bad syntax" ); - } - } - else - if (cmd=="abort") { - server.camera.abort(); - ret = 0; - } + std::string function = "Camera::doit"; + char buf[BUFSIZE + 1]; + long ret; + std::stringstream message; + std::string cmd, args; // arg string is everything after command + std::vector tokens; + + bool connection_open = true; + + message.str(""); + message << "thread " << sock.id << " accepted connection on fd " << sock.getfd(); + logwrite(function, message.str()); + + while (connection_open) { + memset(buf, '\0', BUFSIZE); // init buffers + + // Wait (poll) connected socket for incoming data... + // + int pollret; + if ((pollret = sock.Poll()) <= 0) { + if (pollret == 0) { + message.str(""); + message << "Poll timeout on fd " << sock.getfd() << " thread " << sock.id; + logwrite(function, message.str()); + } + if (pollret < 0) { + message.str(""); + message << "Poll error on fd " << sock.getfd() << " thread " << sock.id << ": " << strerror(errno); + logwrite(function, message.str()); + } + break; // this will close the connection + } + + // Data available, now read from connected socket... + // + std::string sbuf; + char delim = '\n'; + if ((ret = sock.Read(sbuf, delim)) <= 0) { + if (ret < 0) { + // could be an actual read error + message.str(""); + message << "Read error on fd " << sock.getfd() << ": " << strerror(errno); + logwrite(function, message.str()); + } + if (ret == 0) { + message.str(""); + message << "timeout reading from fd " << sock.getfd(); + logwrite(function, message.str()); + } + break; // Breaking out of the while loop will close the connection. + // This probably means that the client has terminated abruptly, + // having sent FIN but not stuck around long enough + // to accept CLOSE and give the LAST_ACK. + } + + // convert the input buffer into a string and remove any trailing linefeed + // and carriage return + // + sbuf.erase(std::remove(sbuf.begin(), sbuf.end(), '\r'), sbuf.end()); + sbuf.erase(std::remove(sbuf.begin(), sbuf.end(), '\n'), sbuf.end()); + + if (sbuf.empty()) { + sock.Write("\n"); + continue; + } // acknowledge empty command so client doesn't time out + + try { + std::size_t cmd_sep = sbuf.find_first_of(" "); + // find the first space, which separates command from argument list + + cmd = sbuf.substr(0, cmd_sep); // cmd is everything up until that space + + if (cmd.empty()) { + sock.Write("\n"); + continue; + } // acknowledge empty command so client doesn't time out + + if (cmd_sep == std::string::npos) { + // If no space was found, + args = ""; // then the arg list is empty, + } else { + args = sbuf.substr(cmd_sep + 1); // otherwise args is everything after that space. + } + + message.str(""); + message << "thread " << sock.id << " received command on fd " << sock.getfd() << ": " << cmd << " " << args; + logwrite(function, message.str()); + } catch (std::runtime_error &e) { + std::stringstream errstream; + errstream << e.what(); + message.str(""); + message << "error parsing arguments: " << errstream.str(); + logwrite(function, message.str()); + ret = -1; + } + catch (...) { + message.str(""); + message << "unknown error parsing arguments: " << args; + logwrite(function, message.str()); + ret = -1; + } + + /** + * process commands here + */ + ret = NOTHING; + std::string retstring; // string for return the value (where needed) + + if (cmd == "exit") { + server.camera.async.enqueue("exit"); // shutdown the async message thread if running + server.exit_cleanly(); // shutdown the server + } else if (cmd == "config") { + // report the config file used for camerad + std::stringstream cfg; + cfg << "CONFIG:" << server.config.filename; + server.camera.async.enqueue(cfg.str()); + sock.Write(server.config.filename); + sock.Write(" "); + ret = NO_ERROR; + } else if (cmd == "open") { + ret = server.connect_controller(args); + } else if (cmd == "close") { + ret = server.disconnect_controller(); + } else if (cmd == "load") { + if (args.empty()) ret = server.load_firmware(retstring); + else ret = server.load_firmware(args, retstring); + if (!retstring.empty()) { + sock.Write(retstring); + sock.Write(" "); + } + } else if (cmd == "basename") { + ret = server.camera.basename(args, retstring); + sock.Write(retstring); + sock.Write(" "); + } else if (cmd == "imnum") { + ret = server.camera.imnum(args, retstring); + if (!retstring.empty()) { + sock.Write(retstring); + sock.Write(" "); + } + } else if (cmd == "imdir") { + ret = server.camera.imdir(args, retstring); + sock.Write(retstring); + sock.Write(" "); + } else if (cmd == "autodir") { + ret = server.camera.autodir(args, retstring); + if (!retstring.empty()) { + sock.Write(retstring); + sock.Write(" "); + } + } else if (cmd == "datacube") { + ret = server.camera.datacube(args, retstring); + sock.Write(retstring); + sock.Write(" "); + } else if (cmd == "longerror") { + ret = server.camera.longerror(args, retstring); + sock.Write(retstring); + sock.Write(" "); + } else if (cmd == "preexposures") { + ret = server.camera_info.pre_exposures(args, retstring); + sock.Write(retstring); + sock.Write(" "); + } else if (cmd == "cubeamps") { + ret = server.camera.cubeamps(args, retstring); + sock.Write(retstring); + sock.Write(" "); + } else if (cmd == "fitsnaming") { + ret = server.camera.fitsnaming(args, retstring); + if (!retstring.empty()) { + sock.Write(retstring); + sock.Write(" "); + } + } else if (cmd == "shutter") { + ret = server.shutter(args, retstring); + if (!retstring.empty()) { + sock.Write(retstring); + sock.Write(" "); + } + } else if (cmd == "writekeys") { + ret = server.camera.writekeys(args, retstring); + if (!retstring.empty()) { + sock.Write(retstring); + sock.Write(" "); + } + } else if (cmd == "key") { + if (args.compare(0, 4, "list") == 0) { + logwrite(function, "systemkeys:"); + ret = server.systemkeys.listkeys(); + logwrite(function, "userkeys:"); + ret = server.userkeys.listkeys(); + } else { + ret = server.userkeys.addkey(args); + if (ret != NO_ERROR) server.camera.log_error(function, "bad syntax"); + } + } else if (cmd == "abort") { + server.camera.abort(); + ret = 0; + } #ifdef ASTROCAM else if (cmd=="isopen") { @@ -648,169 +678,159 @@ void doit(Network::TcpSocket sock) { if (!retstring.empty()) { sock.Write(retstring); sock.Write(" "); } } #endif - else - if (cmd=="roi") { - ret = server.region_of_interest( args, retstring ); - if (!retstring.empty()) { sock.Write(retstring); sock.Write(" "); } - } - else - if (cmd=="isloaded") { - retstring = server.firmwareloaded ? "true" : "false"; - sock.Write(retstring); - sock.Write(" "); + else if (cmd == "roi") { + ret = server.region_of_interest(args, retstring); + if (!retstring.empty()) { + sock.Write(retstring); + sock.Write(" "); + } + } else if (cmd == "isloaded") { + retstring = server.firmwareloaded ? "true" : "false"; + sock.Write(retstring); + sock.Write(" "); + ret = NO_ERROR; + } else if (cmd == "mode") { + if (args.empty()) { + // no argument means asking for current mode + if (server.modeselected) { ret = NO_ERROR; - } - else - if (cmd=="mode") { - if (args.empty()) { // no argument means asking for current mode - if (server.modeselected) { - ret=NO_ERROR; - sock.Write(server.camera_info.current_observing_mode); sock.Write(" "); - } - else ret=ERROR; // no mode selected returns an error - } - else ret = server.set_camera_mode(args); - } - else - if (cmd=="getp") { - ret = server.get_parameter(args, retstring); - if (!retstring.empty()) { sock.Write(retstring); sock.Write(" "); } - } - else - if (cmd=="setp") { - ret = server.set_parameter(args); - } - else - if (cmd=="loadtiming") { - if (args.empty()) ret = server.load_timing(retstring); - else ret = server.load_timing(args, retstring); - if (!retstring.empty()) { sock.Write(retstring); sock.Write(" "); } - } - else - if (cmd=="inreg") { - ret = server.inreg(args); - } - else - if (cmd=="printstatus") { - ret = server.get_frame_status(); - if (ret==NO_ERROR) server.print_frame_status(); - } - else - if (cmd=="readframe") { - ret = server.read_frame(); - } - else - if (cmd=="writeframe") { - ret = server.write_frame(); - } - else - if (cmd=="cds") { - ret = server.cds(args, retstring); - if (!retstring.empty()) { sock.Write(retstring); sock.Write(" "); } - } - else - if (cmd=="heater") { - ret = server.heater(args, retstring); - if (!retstring.empty()) { sock.Write(retstring); sock.Write(" "); } - } - else - if (cmd=="sensor") { - ret = server.sensor(args, retstring); - if (!retstring.empty()) { sock.Write(retstring); sock.Write(" "); } - } - else - if (cmd=="longexposure") { - ret = server.longexposure(args, retstring); - if (!retstring.empty()) { sock.Write(retstring); sock.Write(" "); } - } - else - if (cmd=="hdrshift") { - ret = server.hdrshift(args, retstring); - if (!retstring.empty()) { sock.Write(retstring); sock.Write(" "); } - } - else - if (cmd=="trigin") { - ret = server.trigin(args); - } -#endif - else - if (cmd=="expose") { - ret = server.expose(args); - } - else - if (cmd=="exptime") { - // Neither controller allows fractional exposure times - // so catch that here. - // - if ( args.find(".") != std::string::npos ) { - ret = ERROR; - logwrite(function, "ERROR: fractional exposure times not allowed"); - // empty the args string so that a call to exptime returns the current exptime - // - args=""; - server.exptime(args, retstring); - } - else { ret = server.exptime(args, retstring); } - if (!retstring.empty()) { sock.Write(retstring); sock.Write(" "); } - } - else - if (cmd=="bias") { - ret = server.bias(args, retstring); - if (!retstring.empty()) { sock.Write(retstring); sock.Write(" "); } - } - else - if (cmd=="echo") { - sock.Write(args); - sock.Write("\n"); - } - else - if (cmd=="interface") { - ret = server.interface(retstring); - sock.Write(retstring); + sock.Write(server.camera_info.current_observing_mode); sock.Write(" "); - } - else - if (cmd=="test") { - ret = server.test(args, retstring); - if (!retstring.empty()) { sock.Write(retstring); sock.Write(" "); } - } - else - if (cmd=="native") { - try { - std::transform( args.begin(), args.end(), args.begin(), ::toupper ); // make uppercase - } - catch (...) { - logwrite(function, "error converting command to uppercase"); - ret=ERROR; - } + } else ret = ERROR; // no mode selected returns an error + } else ret = server.set_camera_mode(args); + } else if (cmd == "getp") { + ret = server.get_parameter(args, retstring); + if (!retstring.empty()) { + sock.Write(retstring); + sock.Write(" "); + } + } else if (cmd == "setp") { + ret = server.set_parameter(args); + } else if (cmd == "loadtiming") { + if (args.empty()) ret = server.load_timing(retstring); + else ret = server.load_timing(args, retstring); + if (!retstring.empty()) { + sock.Write(retstring); + sock.Write(" "); + } + } else if (cmd == "inreg") { + ret = server.inreg(args); + } else if (cmd == "printstatus") { + ret = server.get_frame_status(); + if (ret == NO_ERROR) server.print_frame_status(); + } else if (cmd == "readframe") { + ret = server.read_frame(); + } else if (cmd == "writeframe") { + ret = server.write_frame(); + } else if (cmd == "cds") { + ret = server.cds(args, retstring); + if (!retstring.empty()) { + sock.Write(retstring); + sock.Write(" "); + } + } else if (cmd == "heater") { + ret = server.heater(args, retstring); + if (!retstring.empty()) { + sock.Write(retstring); + sock.Write(" "); + } + } else if (cmd == "sensor") { + ret = server.sensor(args, retstring); + if (!retstring.empty()) { + sock.Write(retstring); + sock.Write(" "); + } + } else if (cmd == "longexposure") { + ret = server.longexposure(args, retstring); + if (!retstring.empty()) { + sock.Write(retstring); + sock.Write(" "); + } + } else if (cmd == "hdrshift") { + ret = server.hdrshift(args, retstring); + if (!retstring.empty()) { + sock.Write(retstring); + sock.Write(" "); + } + } else if (cmd == "trigin") { + ret = server.trigin(args); + } +#endif + else if (cmd == "expose") { + ret = server.expose(args); + } else if (cmd == "exptime") { + // Neither controller allows fractional exposure times + // so catch that here. + // + if (args.find(".") != std::string::npos) { + ret = ERROR; + logwrite(function, "ERROR: fractional exposure times not allowed"); + // empty the args string so that a call to exptime returns the current exptime + // + args = ""; + server.exptime(args, retstring); + } else { ret = server.exptime(args, retstring); } + if (!retstring.empty()) { + sock.Write(retstring); + sock.Write(" "); + } + } else if (cmd == "bias") { + ret = server.bias(args, retstring); + if (!retstring.empty()) { + sock.Write(retstring); + sock.Write(" "); + } + } else if (cmd == "echo") { + sock.Write(args); + sock.Write("\n"); + } else if (cmd == "interface") { + ret = server.interface(retstring); + sock.Write(retstring); + sock.Write(" "); + } else if (cmd == "test") { + ret = server.test(args, retstring); + if (!retstring.empty()) { + sock.Write(retstring); + sock.Write(" "); + } + } else if (cmd == "native") { + try { + std::transform(args.begin(), args.end(), args.begin(), ::toupper); // make uppercase + } catch (...) { + logwrite(function, "error converting command to uppercase"); + ret = ERROR; + } #ifdef ASTROCAM ret = server.native(args, retstring); if (!retstring.empty()) { sock.Write(retstring); sock.Write(" "); } #endif #ifdef STA_ARCHON - ret = server.native(args); + ret = server.native(args); #endif - } - - // if no matching command found - // - else { message.str(""); message << "ERROR unrecognized command: " << cmd; - logwrite( function, message.str() ); - ret=ERROR; - } - - if (ret != NOTHING) { - std::string retstr=(ret==0?"DONE\n":"ERROR\n"); - if ( ret==0 ) retstr="DONE\n"; else retstr="ERROR" + server.camera.get_longerror() + "\n"; - if (sock.Write(retstr)<0) connection_open=false; + } + + // if no matching command found + // + else { + message.str(""); + message << "ERROR unrecognized command: " << cmd; + logwrite(function, message.str()); + ret = ERROR; + } + + if (ret != NOTHING) { + std::string retstr = (ret == 0 ? "DONE\n" : "ERROR\n"); + if (ret == 0) retstr = "DONE\n"; + else retstr = "ERROR" + server.camera.get_longerror() + "\n"; + if (sock.Write(retstr) < 0) connection_open = false; + } + + if (!sock.isblocking()) break; // Non-blocking connection exits immediately. + // Keep blocking connection open for interactive session. } - if (!sock.isblocking()) break; // Non-blocking connection exits immediately. - // Keep blocking connection open for interactive session. - } - - sock.Close(); - return; + sock.Close(); + return; } -/** doit *********************************************************************/ +/** doit *********************************************************************/ diff --git a/camerad/camerad.h b/camerad/camerad.h index 9a40b36c..a1e6a693 100644 --- a/camerad/camerad.h +++ b/camerad/camerad.h @@ -31,176 +31,177 @@ #include "network.h" #define N_THREADS 10 //!< total number of threads spawned by server, one for blocking and the remainder for non-blocking + #define BUFSIZE 1024 //!< size of the input command buffer #define CONN_TIMEOUT 3000 //nonblocking_socket); - close(this->blocking_socket); - close_log(); // close the logfile, if open - } - - std::string asyncgroup; //!< asynchronous multicast group - - int nonblocking_socket; - int blocking_socket; - - Network::TcpSocket nonblocking; - - std::mutex conn_mutex; //!< mutex to protect against simultaneous access to Accept() - - /***** Camera::Server::exit_cleanly *************************************/ - /** - * @brief handles ctrl-C and exits - * @return nothing - * - */ - void exit_cleanly(void) { - std::string function = "Camera::Server::exit_cleanly"; - this->disconnect_controller(); - logwrite(function, "server exiting"); - exit(EXIT_SUCCESS); - } - /***** Camera::Server::exit_cleanly *************************************/ - - - /***** Camera::Server::configure_server *********************************/ - /** - * @brief parse server-related keys from the configuration file - * @details the config file was read by server.config.read_config() in main() - * @return ERROR or NO_ERROR - * - */ - long configure_server() { - std::string function = "Camera::Server::configure_server"; - std::stringstream message; - int applied=0; - long error; - - // loop through the entries in the configuration file, stored in config class - // - for (int entry=0; entry < this->config.n_entries; entry++) { - - // NBPORT - if (config.param[entry].compare(0, 6, "NBPORT")==0) { - int port; - try { - port = std::stoi( config.arg[entry] ); - } - catch (std::invalid_argument &) { - this->camera.log_error( function, "bad NBPORT: unable to convert to integer" ); - return(ERROR); - } - catch (std::out_of_range &) { - this->camera.log_error( function, "NBPORT number out of integer range" ); - return(ERROR); - } - this->nbport = port; - message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite( function, message.str() ); - this->camera.async.enqueue( message.str() ); - applied++; - } - - // BLKPORT - if (config.param[entry].compare(0, 7, "BLKPORT")==0) { - int port; - try { - port = std::stoi( config.arg[entry] ); - } - catch (std::invalid_argument &) { - this->camera.log_error( function, "bad BLKPORT: unable to convert to integer" ); - return(ERROR); - } - catch (std::out_of_range &) { - this->camera.log_error( function, "BLKPORT number out of integer range" ); - return(ERROR); - } - this->blkport = port; - message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite( function, message.str() ); - this->camera.async.enqueue( message.str() ); - applied++; - } - - // ASYNCPORT - if (config.param[entry].compare(0, 9, "ASYNCPORT")==0) { - int port; - try { - port = std::stoi( config.arg[entry] ); - } - catch (std::invalid_argument &) { - this->camera.log_error( function, "bad ASYNCPORT: unable to convert to integer" ); - return(ERROR); - } - catch (std::out_of_range &) { - this->camera.log_error( function, "ASYNCPORT number out of integer range" ); - return(ERROR); - } - this->asyncport = port; - message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite( function, message.str() ); - this->camera.async.enqueue( message.str() ); - applied++; - } - - // ASYNCGROUP - if (config.param[entry].compare(0, 10, "ASYNCGROUP")==0) { - this->asyncgroup = config.arg[entry]; - message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite( function, message.str() ); - this->camera.async.enqueue( message.str() ); - applied++; - } - - // LONGERROR - if (config.param[entry].compare(0, 9, "LONGERROR")==0) { - std::string dontcare; - if ( this->camera.longerror( config.arg[entry], dontcare ) == ERROR ) { - this->camera.log_error( function, "setting longerror" ); - return( ERROR ); + int nbport; //!< non-blocking port + int blkport; //!< blocking port + int asyncport; //!< asynchronous message port + + Server() : nbport(-1), blkport(-1), asyncport(-1) { + } + + ~Server() { + close(this->nonblocking_socket); + close(this->blocking_socket); + close_log(); // close the logfile, if open + } + + std::string asyncgroup; //!< asynchronous multicast group + + int nonblocking_socket; + int blocking_socket; + + Network::TcpSocket nonblocking; + + std::mutex conn_mutex; //!< mutex to protect against simultaneous access to Accept() + + /***** Camera::Server::exit_cleanly *************************************/ + /** + * @brief handles ctrl-C and exits + * @return nothing + * + */ + void exit_cleanly(void) { + std::string function = "Camera::Server::exit_cleanly"; + this->disconnect_controller(); + logwrite(function, "server exiting"); + exit(EXIT_SUCCESS); + } + + /***** Camera::Server::exit_cleanly *************************************/ + + + /***** Camera::Server::configure_server *********************************/ + /** + * @brief parse server-related keys from the configuration file + * @details the config file was read by server.config.read_config() in main() + * @return ERROR or NO_ERROR + * + */ + long configure_server() { + std::string function = "Camera::Server::configure_server"; + std::stringstream message; + int applied = 0; + long error; + + // loop through the entries in the configuration file, stored in config class + // + for (int entry = 0; entry < this->config.n_entries; entry++) { + // NBPORT + if (config.param[entry].compare(0, 6, "NBPORT") == 0) { + int port; + try { + port = std::stoi(config.arg[entry]); + } catch (std::invalid_argument &) { + this->camera.log_error(function, "bad NBPORT: unable to convert to integer"); + return (ERROR); + } + catch (std::out_of_range &) { + this->camera.log_error(function, "NBPORT number out of integer range"); + return (ERROR); + } + this->nbport = port; + message.str(""); + message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite(function, message.str()); + this->camera.async.enqueue(message.str()); + applied++; + } + + // BLKPORT + if (config.param[entry].compare(0, 7, "BLKPORT") == 0) { + int port; + try { + port = std::stoi(config.arg[entry]); + } catch (std::invalid_argument &) { + this->camera.log_error(function, "bad BLKPORT: unable to convert to integer"); + return (ERROR); + } + catch (std::out_of_range &) { + this->camera.log_error(function, "BLKPORT number out of integer range"); + return (ERROR); + } + this->blkport = port; + message.str(""); + message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite(function, message.str()); + this->camera.async.enqueue(message.str()); + applied++; + } + + // ASYNCPORT + if (config.param[entry].compare(0, 9, "ASYNCPORT") == 0) { + int port; + try { + port = std::stoi(config.arg[entry]); + } catch (std::invalid_argument &) { + this->camera.log_error(function, "bad ASYNCPORT: unable to convert to integer"); + return (ERROR); + } + catch (std::out_of_range &) { + this->camera.log_error(function, "ASYNCPORT number out of integer range"); + return (ERROR); + } + this->asyncport = port; + message.str(""); + message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite(function, message.str()); + this->camera.async.enqueue(message.str()); + applied++; + } + + // ASYNCGROUP + if (config.param[entry].compare(0, 10, "ASYNCGROUP") == 0) { + this->asyncgroup = config.arg[entry]; + message.str(""); + message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite(function, message.str()); + this->camera.async.enqueue(message.str()); + applied++; + } + + // LONGERROR + if (config.param[entry].compare(0, 9, "LONGERROR") == 0) { + std::string dontcare; + if (this->camera.longerror(config.arg[entry], dontcare) == ERROR) { + this->camera.log_error(function, "setting longerror"); + return (ERROR); + } + message.str(""); + message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite(function, message.str()); + this->camera.async.enqueue(message.str()); + applied++; + } + } // end loop through the entries in the configuration file + + message.str(""); + if (applied == 0) { + message << "ERROR: "; + error = ERROR; + } else { + error = NO_ERROR; } - message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite( function, message.str() ); - this->camera.async.enqueue( message.str() ); - applied++; - } - - } // end loop through the entries in the configuration file - - message.str(""); - if (applied==0) { - message << "ERROR: "; - error = ERROR; - } - else { - error = NO_ERROR; - } - message << "applied " << applied << " configuration lines to server"; - error==NO_ERROR ? logwrite(function, message.str()) : this->camera.log_error( function, message.str() ); - return error; - } - /***** Camera::Server::configure_server *********************************/ - - }; // end class Server + message << "applied " << applied << " configuration lines to server"; + error == NO_ERROR ? logwrite(function, message.str()) : this->camera.log_error(function, message.str()); + return error; + } + /***** Camera::Server::configure_server *********************************/ + }; // end class Server } // end namespace Camera diff --git a/camerad/fits.h b/camerad/fits.h index 4a341383..ec4f11a9 100644 --- a/camerad/fits.h +++ b/camerad/fits.h @@ -22,26 +22,27 @@ #include "utilities.h" #include "logentry.h" -const int FITS_WRITE_WAIT = 5000; /// approx time (in msec) to wait for a frame to be written +const int FITS_WRITE_WAIT = 5000; /// approx time (in msec) to wait for a frame to be written class FITS_file { - private: - std::atomic threadcount; /// keep track of number of write_image_thread threads - std::atomic framen; /// internal frame counter for data cubes - std::atomic writing_file; /// semaphore indicates file is being written - std::atomic error; /// indicates an error occured in a file writing thread - std::atomic file_open; /// semaphore indicates file is open - - std::mutex fits_mutex; /// used to block writing_file semaphore in multiple threads - std::unique_ptr pFits; /// pointer to FITS data container - CCfits::ExtHDU* imageExt; /// image extension header unit +private: + std::atomic threadcount; /// keep track of number of write_image_thread threads + std::atomic framen; /// internal frame counter for data cubes + std::atomic writing_file; /// semaphore indicates file is being written + std::atomic error; /// indicates an error occured in a file writing thread + std::atomic file_open; /// semaphore indicates file is open + + std::mutex fits_mutex; /// used to block writing_file semaphore in multiple threads + std::unique_ptr pFits; /// pointer to FITS data container + CCfits::ExtHDU *imageExt; /// image extension header unit std::string fits_name; - public: - bool iserror() { return this->error; }; /// allows outsiders access to errors that occurred in a fits writing thread - bool isopen() { return this->file_open; }; /// allows outsiders access file open status +public: + bool iserror() { return this->error; }; /// allows outsiders access to errors that occurred in a fits writing thread + bool isopen() { return this->file_open; }; /// allows outsiders access file open status - FITS_file() : threadcount(0), framen(0), writing_file(false), error(false), file_open(false) { } + FITS_file() : threadcount(0), framen(0), writing_file(false), error(false), file_open(false) { + } /**************** FITS_file::open_file ************************************/ /** @@ -54,105 +55,113 @@ class FITS_file { * primary header data to it. * */ - long open_file( bool writekeys, Camera::Information & info ) { - std::string function = "FITS_file::open_file"; - std::stringstream message; - - long axes[2]; // local variable of image axes size - int num_axis; // local variable for number of axes + long open_file(bool writekeys, Camera::Information &info) { + std::string function = "FITS_file::open_file"; + std::stringstream message; - const std::lock_guard lock(this->fits_mutex); + long axes[2]; // local variable of image axes size + int num_axis; // local variable for number of axes - // This is probably a programming error, if file_open is true here - // - if (this->file_open) { - message.str(""); message << "ERROR: FITS file \"" << info.fits_name << "\" already open"; - logwrite(function, message.str()); - return (ERROR); - } + const std::lock_guard lock(this->fits_mutex); - // Check that we can write the file, because CCFits will crash if it cannot - // - std::ofstream checkfile ( info.fits_name.c_str() ); - if ( checkfile.is_open() ) { - checkfile.close(); - std::remove( info.fits_name.c_str() ); - } - else { - message.str(""); message << "ERROR unable to create file \"" << info.fits_name << "\""; - logwrite(function, message.str()); - return(ERROR); - } - - if (info.iscube) { // special num_axis, axes for data cube - num_axis = 0; - axes[0] = 0; - axes[1] = 0; - } - else { // or regular for flat fits files - num_axis = 2; - axes[0] = info.axes[0]; - axes[1] = info.axes[1]; - } - - if (!info.type_set) { // This is a programming error, means datatype is uninitialized. - logwrite(function, "ERROR: FITS datatype is uninitialized. Call set_axes()"); - } - - try { - // Create a new FITS object, specifying the data type and axes for the primary image. - // Simultaneously create the corresponding file. + // This is probably a programming error, if file_open is true here // - this->pFits.reset( new CCfits::FITS(info.fits_name, info.datatype, num_axis, axes) ); - this->file_open = true; // file is open now - this->make_camera_header(info); + if (this->file_open) { + message.str(""); + message << "ERROR: FITS file \"" << info.fits_name << "\" already open"; + logwrite(function, message.str()); + return (ERROR); + } - // Iterate through the system-defined FITS keyword databases and add them to the primary header. + // Check that we can write the file, because CCFits will crash if it cannot // - Common::FitsKeys::fits_key_t::iterator keyit; - for (keyit = info.systemkeys.keydb.begin(); - keyit != info.systemkeys.keydb.end(); - keyit++) { - this->add_key(keyit->second.keyword, keyit->second.keytype, keyit->second.keyvalue, keyit->second.keycomment); + std::ofstream checkfile(info.fits_name.c_str()); + if (checkfile.is_open()) { + checkfile.close(); + std::remove(info.fits_name.c_str()); + } else { + message.str(""); + message << "ERROR unable to create file \"" << info.fits_name << "\""; + logwrite(function, message.str()); + return (ERROR); } - // If specified, iterate through the user-defined FITS keyword databases and add them to the primary header. - // - if ( writekeys ) { - logwrite( function, "writing user-defined keys before exposure" ); - for (keyit = info.userkeys.keydb.begin(); - keyit != info.userkeys.keydb.end(); - keyit++) { - this->add_key(keyit->second.keyword, keyit->second.keytype, keyit->second.keyvalue, keyit->second.keycomment); - } + if (info.iscube) { + // special num_axis, axes for data cube + num_axis = 0; + axes[0] = 0; + axes[1] = 0; + } else { + // or regular for flat fits files + num_axis = 2; + axes[0] = info.axes[0]; + axes[1] = info.axes[1]; } - } - catch (CCfits::FITS::CantCreate){ - message.str(""); message << "ERROR: unable to open FITS file \"" << info.fits_name << "\""; - logwrite(function, message.str()); - return(ERROR); - } - catch (...) { - message.str(""); message << "unknown error opening FITS file \"" << info.fits_name << "\""; - logwrite(function, message.str()); - return(ERROR); - } - message.str(""); message << "opened file \"" << info.fits_name << "\" for FITS write"; - logwrite(function, message.str()); + if (!info.type_set) { + // This is a programming error, means datatype is uninitialized. + logwrite(function, "ERROR: FITS datatype is uninitialized. Call set_axes()"); + } + + try { + // Create a new FITS object, specifying the data type and axes for the primary image. + // Simultaneously create the corresponding file. + // + this->pFits.reset(new CCfits::FITS(info.fits_name, info.datatype, num_axis, axes)); + this->file_open = true; // file is open now + this->make_camera_header(info); + + // Iterate through the system-defined FITS keyword databases and add them to the primary header. + // + Common::FitsKeys::fits_key_t::iterator keyit; + for (keyit = info.systemkeys.keydb.begin(); + keyit != info.systemkeys.keydb.end(); + keyit++) { + this->add_key(keyit->second.keyword, keyit->second.keytype, keyit->second.keyvalue, + keyit->second.keycomment); + } + + // If specified, iterate through the user-defined FITS keyword databases and add them to the primary header. + // + if (writekeys) { + logwrite(function, "writing user-defined keys before exposure"); + for (keyit = info.userkeys.keydb.begin(); + keyit != info.userkeys.keydb.end(); + keyit++) { + this->add_key(keyit->second.keyword, keyit->second.keytype, keyit->second.keyvalue, + keyit->second.keycomment); + } + } + } catch (CCfits::FITS::CantCreate) { + message.str(""); + message << "ERROR: unable to open FITS file \"" << info.fits_name << "\""; + logwrite(function, message.str()); + return (ERROR); + } + catch (...) { + message.str(""); + message << "unknown error opening FITS file \"" << info.fits_name << "\""; + logwrite(function, message.str()); + return (ERROR); + } + + message.str(""); + message << "opened file \"" << info.fits_name << "\" for FITS write"; + logwrite(function, message.str()); - // must reset variables as when container was constructed - // each time a new file is opened - // - this->threadcount = 0; - this->framen = 0; - this->writing_file = false; - this->error = false; + // must reset variables as when container was constructed + // each time a new file is opened + // + this->threadcount = 0; + this->framen = 0; + this->writing_file = false; + this->error = false; - this->fits_name = info.fits_name; + this->fits_name = info.fits_name; - return (0); + return (0); } + /**************** FITS_file::open_file ************************************/ @@ -167,58 +176,61 @@ class FITS_file { * Nothing called returns anything so this doesn't return anything. * */ - void close_file( bool writekeys, Camera::Information & info ) { - std::string function = "FITS_file::close_file"; - std::stringstream message; + void close_file(bool writekeys, Camera::Information &info) { + std::string function = "FITS_file::close_file"; + std::stringstream message; - // Nothing to do if not open - // - if ( ! this->file_open ) { + // Nothing to do if not open + // + if (!this->file_open) { #ifdef LOGLEVEL_DEBUG logwrite(function, "[DEBUG] no open FITS file to close"); #endif - return; - } - - // Write the user keys on close, if specified - // - if ( writekeys ) { - logwrite( function, "writing user-defined keys after exposure" ); - Common::FitsKeys::fits_key_t::iterator keyit; - for (keyit = info.userkeys.keydb.begin(); - keyit != info.userkeys.keydb.end(); - keyit++) { - this->add_key(keyit->second.keyword, keyit->second.keytype, keyit->second.keyvalue, keyit->second.keycomment); + return; } - } - try { - // Add a header keyword for the time the file was written (right now!) + // Write the user keys on close, if specified // - this->pFits->pHDU().addKey("DATE", get_timestamp(), "FITS file write time"); + if (writekeys) { + logwrite(function, "writing user-defined keys after exposure"); + Common::FitsKeys::fits_key_t::iterator keyit; + for (keyit = info.userkeys.keydb.begin(); + keyit != info.userkeys.keydb.end(); + keyit++) { + this->add_key(keyit->second.keyword, keyit->second.keytype, keyit->second.keyvalue, + keyit->second.keycomment); + } + } - // Write the checksum - // - this->pFits->pHDU().writeChecksum(); + try { + // Add a header keyword for the time the file was written (right now!) + // + this->pFits->pHDU().addKey("DATE", get_timestamp(), "FITS file write time"); + + // Write the checksum + // + this->pFits->pHDU().writeChecksum(); + + // Deallocate the CCfits object and close the FITS file + // + this->pFits->destroy(); + } catch (CCfits::FitsError &error) { + message.str(""); + message << "ERROR writing checksum and closing file: " << error.message(); + logwrite(function, message.str()); + this->file_open = false; // must set this false on exception + } - // Deallocate the CCfits object and close the FITS file + // Let the world know that the file is closed // - this->pFits->destroy(); - } - catch (CCfits::FitsError& error){ - message.str(""); message << "ERROR writing checksum and closing file: " << error.message(); - logwrite(function, message.str()); - this->file_open = false; // must set this false on exception - } - - // Let the world know that the file is closed - // - this->file_open = false; + this->file_open = false; - message.str(""); message << this->fits_name << " closed"; - logwrite(function, message.str()); - this->fits_name=""; + message.str(""); + message << this->fits_name << " closed"; + logwrite(function, message.str()); + this->fits_name = ""; } + /**************** FITS_file::close_file ***********************************/ @@ -233,87 +245,90 @@ class FITS_file { * This function spawns a thread to write the image data to disk * */ - template - long write_image(T* data, Camera::Information& info) { - std::string function = "FITS::write_image"; - std::stringstream message; - - // The file must have been opened first - // - if ( !this->file_open ) { - message.str(""); message << "ERROR: FITS file \"" << info.fits_name << "\" not open"; - logwrite(function, message.str()); - return (ERROR); - } + template + long write_image(T *data, Camera::Information &info) { + std::string function = "FITS::write_image"; + std::stringstream message; - // copy the data into an array of the appropriate type //TODO use multiple threads for this?? - // - std::valarray array( info.section_size ); - for ( long i = 0; i < info.section_size; i++ ) { - array[i] = data[i]; - } + // The file must have been opened first + // + if (!this->file_open) { + message.str(""); + message << "ERROR: FITS file \"" << info.fits_name << "\" not open"; + logwrite(function, message.str()); + return (ERROR); + } - // Use a lambda expression to properly spawn a thread without having to - // make it static. Each thread gets a pointer to the current object this-> - // which must continue to exist until all of the threads terminate. - // That is ensured by keeping threadcount, incremented for each thread - // spawned and decremented on return, and not returning from this function - // until threadcount is zero. - // - this->threadcount++; // increment threadcount for each thread spawned + // copy the data into an array of the appropriate type //TODO use multiple threads for this?? + // + std::valarray array(info.section_size); + for (long i = 0; i < info.section_size; i++) { + array[i] = data[i]; + } + + // Use a lambda expression to properly spawn a thread without having to + // make it static. Each thread gets a pointer to the current object this-> + // which must continue to exist until all of the threads terminate. + // That is ensured by keeping threadcount, incremented for each thread + // spawned and decremented on return, and not returning from this function + // until threadcount is zero. + // + this->threadcount++; // increment threadcount for each thread spawned #ifdef LOGLEVEL_DEBUG message.str(""); - message << "[DEBUG] threadcount=" << this->threadcount << " iscube=" << info.iscube << " section_size=" << info.section_size + message << "[DEBUG] threadcount=" << this->threadcount << " iscube=" << info.iscube << " section_size=" << info.section_size << ". spawning image writing thread for frame " << this->framen << " of " << info.fits_name; logwrite(function, message.str()); #endif - std::thread([&]() { // create the detached thread here - if (info.iscube) { - this->write_cube_thread(array, info, this); - } - else { - this->write_image_thread(array, info, this); - } - std::lock_guard lock(this->fits_mutex); // lock and - this->threadcount--; // decrement threadcount - }).detach(); + std::thread([&]() { + // create the detached thread here + if (info.iscube) { + this->write_cube_thread(array, info, this); + } else { + this->write_image_thread(array, info, this); + } + std::lock_guard lock(this->fits_mutex); // lock and + this->threadcount--; // decrement threadcount + }).detach(); #ifdef LOGLEVEL_DEBUG message.str(""); message << "[DEBUG] spawned image writing thread for frame " << this->framen << " of " << info.fits_name; logwrite(function, message.str()); #endif - // wait for all threads to complete - // - int last_threadcount = this->threadcount; - int wait = FITS_WRITE_WAIT; - while (this->threadcount > 0) { - usleep(1000); - if (this->threadcount >= last_threadcount) { // threads are not completing - wait--; // start decrementing wait timer - } - else { // a thread was completed so things are still working - last_threadcount = this->threadcount; // reset last threadcount - wait = FITS_WRITE_WAIT; // reset wait timer - } - if (wait < 0) { - message.str(""); message << "ERROR: timeout waiting for threads." - << " threadcount=" << threadcount - << " extension=" << info.extension - << " framen=" << this->framen - << " file=" << info.fits_name; - logwrite(function, message.str()); - this->writing_file = false; - return (ERROR); + // wait for all threads to complete + // + int last_threadcount = this->threadcount; + int wait = FITS_WRITE_WAIT; + while (this->threadcount > 0) { + usleep(1000); + if (this->threadcount >= last_threadcount) { + // threads are not completing + wait--; // start decrementing wait timer + } else { + // a thread was completed so things are still working + last_threadcount = this->threadcount; // reset last threadcount + wait = FITS_WRITE_WAIT; // reset wait timer + } + if (wait < 0) { + message.str(""); + message << "ERROR: timeout waiting for threads." + << " threadcount=" << threadcount + << " extension=" << info.extension + << " framen=" << this->framen + << " file=" << info.fits_name; + logwrite(function, message.str()); + this->writing_file = false; + return (ERROR); + } } - } - if (this->error) { - message.str(""); - message << "an error occured in one of the FITS writing threads for " << info.fits_name; - logwrite(function, message.str()); - } + if (this->error) { + message.str(""); + message << "an error occured in one of the FITS writing threads for " << info.fits_name; + logwrite(function, message.str()); + } #ifdef LOGLEVEL_DEBUG else { message.str(""); @@ -322,8 +337,9 @@ class FITS_file { } #endif - return ( this->error ? ERROR : NO_ERROR ); + return (this->error ? ERROR : NO_ERROR); } + /**************** FITS_file::write_image **********************************/ @@ -340,53 +356,55 @@ class FITS_file { * and must be spawned by write_image. * */ - template + template void write_image_thread(std::valarray &data, Camera::Information &info, FITS_file *self) { - std::string function = "FITS_file::write_image_thread"; - std::stringstream message; - - // This makes the thread wait while another thread is writing images. This - // function is really for single image writing, it's here just in case. - // - int wait = FITS_WRITE_WAIT; - while (self->writing_file) { - usleep(1000); - if (--wait < 0) { - message.str(""); message << "ERROR: timeout waiting for last frame to complete. " - << "unable to write " << info.fits_name; - logwrite(function, message.str()); - self->writing_file = false; - self->error = true; // tells the calling function that I had an error - return; + std::string function = "FITS_file::write_image_thread"; + std::stringstream message; + + // This makes the thread wait while another thread is writing images. This + // function is really for single image writing, it's here just in case. + // + int wait = FITS_WRITE_WAIT; + while (self->writing_file) { + usleep(1000); + if (--wait < 0) { + message.str(""); + message << "ERROR: timeout waiting for last frame to complete. " + << "unable to write " << info.fits_name; + logwrite(function, message.str()); + self->writing_file = false; + self->error = true; // tells the calling function that I had an error + return; + } } - } - // Set the FITS system to verbose mode so it writes error messages - // - CCfits::FITS::setVerboseMode(true); - - // Lock the mutex and set the semaphore for file writing - // - const std::lock_guard lock(self->fits_mutex); - self->writing_file = true; - - // write the primary image into the FITS file - // - try { - long fpixel(1); // start with the first pixel always - self->pFits->pHDU().write( fpixel, info.section_size, data ); - self->pFits->flush(); // make sure the image is written to disk - } - catch (CCfits::FitsError& error){ - message.str(""); message << "FITS file error thrown: " << error.message(); - logwrite(function, message.str()); - self->writing_file = false; - self->error = true; // tells the calling function that I had an error - return; - } + // Set the FITS system to verbose mode so it writes error messages + // + CCfits::FITS::setVerboseMode(true); + + // Lock the mutex and set the semaphore for file writing + // + const std::lock_guard lock(self->fits_mutex); + self->writing_file = true; + + // write the primary image into the FITS file + // + try { + long fpixel(1); // start with the first pixel always + self->pFits->pHDU().write(fpixel, info.section_size, data); + self->pFits->flush(); // make sure the image is written to disk + } catch (CCfits::FitsError &error) { + message.str(""); + message << "FITS file error thrown: " << error.message(); + logwrite(function, message.str()); + self->writing_file = false; + self->error = true; // tells the calling function that I had an error + return; + } - self->writing_file = false; + self->writing_file = false; } + /**************** FITS_file::write_image_thread ***************************/ @@ -403,118 +421,121 @@ class FITS_file { * and must be spawned by write_image. * */ - template + template void write_cube_thread(std::valarray &data, Camera::Information &info, FITS_file *self) { - std::string function = "FITS_file::write_cube_thread"; - std::stringstream message; + std::string function = "FITS_file::write_cube_thread"; + std::stringstream message; #ifdef LOGLEVEL_DEBUG message.str(""); message << "[DEBUG] info.extension=" << info.extension << " this->framen=" << this->framen; logwrite( function, message.str() ); #endif - // This makes the thread wait while another thread is writing images. This - // thread will only start writing once the extension number matches the - // number of frames already written. - // - int last_threadcount = this->threadcount; - int wait = FITS_WRITE_WAIT; - while (info.extension != this->framen) { - usleep(1000); - if (this->threadcount >= last_threadcount) { // threads are not completing - wait--; // start decrementing wait timer - } - else { // a thread was completed so things are still working - last_threadcount = this->threadcount; // reset last threadcount - wait = FITS_WRITE_WAIT; // reset wait timer - } - if (wait < 0) { - message.str(""); message << "ERROR: timeout waiting for frame write." - << " threadcount=" << threadcount - << " extension=" << info.extension - << " framen=" << this->framen; - logwrite(function, message.str()); - self->writing_file = false; - self->error = true; // tells the calling function that I had an error - return; - } - } - - // Set the FITS system to verbose mode so it writes error messages - // - CCfits::FITS::setVerboseMode(true); - - // Lock the mutex and set the semaphore for file writing - // - const std::lock_guard lock(self->fits_mutex); - self->writing_file = true; - - // write the primary image into the FITS file - // - try { - long fpixel(1); // start with the first pixel always - std::vector axes(2); // addImage() wants a vector - axes[0]=info.axes[0]; - axes[1]=info.axes[1]; - - // create the extension name - // This shows up as keyword EXTNAME and in DS9's "display header" + // This makes the thread wait while another thread is writing images. This + // thread will only start writing once the extension number matches the + // number of frames already written. // - std::string extname = std::to_string( info.extension+1 ); - - message.str(""); message << "adding " << axes[0] << " x " << axes[1] - << " frame to extension " << extname << " in file " << info.fits_name; - logwrite(function, message.str()); + int last_threadcount = this->threadcount; + int wait = FITS_WRITE_WAIT; + while (info.extension != this->framen) { + usleep(1000); + if (this->threadcount >= last_threadcount) { + // threads are not completing + wait--; // start decrementing wait timer + } else { + // a thread was completed so things are still working + last_threadcount = this->threadcount; // reset last threadcount + wait = FITS_WRITE_WAIT; // reset wait timer + } + if (wait < 0) { + message.str(""); + message << "ERROR: timeout waiting for frame write." + << " threadcount=" << threadcount + << " extension=" << info.extension + << " framen=" << this->framen; + logwrite(function, message.str()); + self->writing_file = false; + self->error = true; // tells the calling function that I had an error + return; + } + } - // Add the extension here + // Set the FITS system to verbose mode so it writes error messages // - this->imageExt = self->pFits->addImage(extname, info.datatype, axes); + CCfits::FITS::setVerboseMode(true); - // Add extension-only keys now + // Lock the mutex and set the semaphore for file writing // - if (info.datatype == SHORT_IMG) { - this->imageExt->addKey("BZERO", 32768, "offset for signed short int"); - this->imageExt->addKey("BSCALE", 1, "scaling factor"); - } + const std::lock_guard lock(self->fits_mutex); + self->writing_file = true; - // Add AMPSEC keys + // write the primary image into the FITS file // - if ( info.amp_section.size() > 0 ) { - try { - int x1 = info.amp_section.at( info.extension ).at( 0 ); - int x2 = info.amp_section.at( info.extension ).at( 1 ); - int y1 = info.amp_section.at( info.extension ).at( 2 ); - int y2 = info.amp_section.at( info.extension ).at( 3 ); - - message.str(""); message << "[" << x1 << ":" << x2 << "," << y1 << ":" << y2 << "]"; - this->imageExt->addKey( "AMPSEC", message.str(), "amplifier section" ); - } - catch ( std::out_of_range & ) { - logwrite( function, "ERROR: no amplifier section referenced for this extension" ); - } - } - else { - logwrite( function, "no AMPSEC key: missing amplifier section information" ); + try { + long fpixel(1); // start with the first pixel always + std::vector axes(2); // addImage() wants a vector + axes[0] = info.axes[0]; + axes[1] = info.axes[1]; + + // create the extension name + // This shows up as keyword EXTNAME and in DS9's "display header" + // + std::string extname = std::to_string(info.extension + 1); + + message.str(""); + message << "adding " << axes[0] << " x " << axes[1] + << " frame to extension " << extname << " in file " << info.fits_name; + logwrite(function, message.str()); + + // Add the extension here + // + this->imageExt = self->pFits->addImage(extname, info.datatype, axes); + + // Add extension-only keys now + // + if (info.datatype == SHORT_IMG) { + this->imageExt->addKey("BZERO", 32768, "offset for signed short int"); + this->imageExt->addKey("BSCALE", 1, "scaling factor"); + } + + // Add AMPSEC keys + // + if (info.amp_section.size() > 0) { + try { + int x1 = info.amp_section.at(info.extension).at(0); + int x2 = info.amp_section.at(info.extension).at(1); + int y1 = info.amp_section.at(info.extension).at(2); + int y2 = info.amp_section.at(info.extension).at(3); + + message.str(""); + message << "[" << x1 << ":" << x2 << "," << y1 << ":" << y2 << "]"; + this->imageExt->addKey("AMPSEC", message.str(), "amplifier section"); + } catch (std::out_of_range &) { + logwrite(function, "ERROR: no amplifier section referenced for this extension"); + } + } else { + logwrite(function, "no AMPSEC key: missing amplifier section information"); + } + + // Write and flush to make sure image is written to disk + // + this->imageExt->write(fpixel, info.section_size, data); + self->pFits->flush(); + } catch (CCfits::FitsError &error) { + message.str(""); + message << "FITS file error thrown: " << error.message(); + logwrite(function, message.str()); + self->writing_file = false; + self->error = true; // tells the calling function that I had an error + return; } - // Write and flush to make sure image is written to disk + // increment number of frames written // - this->imageExt->write( fpixel, info.section_size, data ); - self->pFits->flush(); - } - catch (CCfits::FitsError& error){ - message.str(""); message << "FITS file error thrown: " << error.message(); - logwrite(function, message.str()); + this->framen++; self->writing_file = false; - self->error = true; // tells the calling function that I had an error - return; - } - - // increment number of frames written - // - this->framen++; - self->writing_file = false; } + /**************** FITS_file::write_cube_thread ****************************/ @@ -531,18 +552,19 @@ class FITS_file { * */ void make_camera_header(Camera::Information &info) { - std::string function = "FITS_file::make_camera_header"; - std::stringstream message; - try { - // To put just the filename into the header (and not the path), find the last slash - // and substring from there to the end. - // - } - catch (CCfits::FitsError & err) { - message.str(""); message << "error creating FITS header: " << err.message(); - logwrite(function, message.str()); - } + std::string function = "FITS_file::make_camera_header"; + std::stringstream message; + try { + // To put just the filename into the header (and not the path), find the last slash + // and substring from there to the end. + // + } catch (CCfits::FitsError &err) { + message.str(""); + message << "error creating FITS header: " << err.message(); + logwrite(function, message.str()); + } } + /**************** FITS_file::make_camera_header ***************************/ @@ -559,42 +581,39 @@ class FITS_file { * Uses CCFits */ void add_key(std::string keyword, std::string type, std::string value, std::string comment) { - std::string function = "FITS_file::add_key"; - std::stringstream message; - - // The file must have been opened first - // - if ( !this->file_open ) { - logwrite(function, "ERROR: no fits file open!"); - return; - } + std::string function = "FITS_file::add_key"; + std::stringstream message; - try { - if (type.compare("BOOL") == 0) { - bool boolvalue = ( value == "T" ? true : false ); - this->pFits->pHDU().addKey( keyword, boolvalue, comment ); - } - else if (type.compare("INT") == 0) { - this->pFits->pHDU().addKey(keyword, std::stoi(value), comment); - } - else if (type.compare("FLOAT") == 0) { - this->pFits->pHDU().addKey(keyword, std::stof(value), comment); - } - else if (type.compare("STRING") == 0) { - this->pFits->pHDU().addKey(keyword, value, comment); + // The file must have been opened first + // + if (!this->file_open) { + logwrite(function, "ERROR: no fits file open!"); + return; } - else { - message.str(""); message << "ERROR unknown type: " << type << " for user keyword: " << keyword << "=" << value - << ": expected {INT,FLOAT,STRING,BOOL}"; - logwrite(function, message.str()); + + try { + if (type.compare("BOOL") == 0) { + bool boolvalue = (value == "T" ? true : false); + this->pFits->pHDU().addKey(keyword, boolvalue, comment); + } else if (type.compare("INT") == 0) { + this->pFits->pHDU().addKey(keyword, std::stoi(value), comment); + } else if (type.compare("FLOAT") == 0) { + this->pFits->pHDU().addKey(keyword, std::stof(value), comment); + } else if (type.compare("STRING") == 0) { + this->pFits->pHDU().addKey(keyword, value, comment); + } else { + message.str(""); + message << "ERROR unknown type: " << type << " for user keyword: " << keyword << "=" << value + << ": expected {INT,FLOAT,STRING,BOOL}"; + logwrite(function, message.str()); + } + } catch (CCfits::FitsError &err) { + message.str(""); + message << "ERROR adding key " << keyword << "=" << value << " / " << comment << " (" << type << ") : " + << err.message(); + logwrite(function, message.str()); } - } - catch (CCfits::FitsError & err) { - message.str(""); message << "ERROR adding key " << keyword << "=" << value << " / " << comment << " (" << type << ") : " - << err.message(); - logwrite(function, message.str()); - } } - /**************** FITS_file::add_key **************************************/ + /**************** FITS_file::add_key **************************************/ }; diff --git a/camerad/generic.cpp b/camerad/generic.cpp index 1984e8b1..8cf0d316 100644 --- a/camerad/generic.cpp +++ b/camerad/generic.cpp @@ -24,22 +24,21 @@ #include "logentry.h" namespace Archon { - - /**************** Archon::Interface::region_of_interest *****************/ - /** - * @fn region_of_interest - * @brief define a region of interest - * @param[in] args - * @param[out] retstring - * @return - * - */ - long Interface::region_of_interest( std::string args, std::string &retstring ) { + /**************** Archon::Interface::region_of_interest *****************/ + /** + * @fn region_of_interest + * @brief define a region of interest + * @param[in] args + * @param[out] retstring + * @return + * + */ + long Interface::region_of_interest(std::string args, std::string &retstring) { std::string function = "Archon::Interface::region_of_interest"; std::stringstream message; - this->camera.log_error( function, "ROI not supported" ); - return( ERROR ); - } - /**************** Archon::Interface::region_of_interest *****************/ + this->camera.log_error(function, "ROI not supported"); + return (ERROR); + } + /**************** Archon::Interface::region_of_interest *****************/ } diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 91d516dd..8668967b 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -8,11 +8,11 @@ cmake_minimum_required(VERSION 3.12) set(PROJECT_UTILS_DIR ${PROJECT_BASE_DIR}/common) -include_directories( ${PROJECT_BASE_DIR}/common ) -include_directories( ${PROJECT_BASE_DIR}/utils ) # needed for logentry +include_directories(${PROJECT_BASE_DIR}/common) +include_directories(${PROJECT_BASE_DIR}/utils) # needed for logentry -add_definitions( -Wall -ansi -O2 -Wno-variadic-macros -std=c++17 -ggdb ) +add_definitions(-Wall -ansi -O2 -Wno-variadic-macros -std=c++17 -ggdb) add_library(common STATIC ${PROJECT_UTILS_DIR}/common.cpp - ) +) diff --git a/common/common.cpp b/common/common.cpp index 5860c24f..31805902 100644 --- a/common/common.cpp +++ b/common/common.cpp @@ -8,263 +8,271 @@ #include "common.h" namespace Common { - - /** Common::Queue::enqueue **************************************************/ - /** - * @fn enqueue - * @brief puts a message into the queue - * @param std::string message - * @return none - * - */ - void Queue::enqueue(std::string message) { - std::lock_guard lock(queue_mutex); - message_queue.push(message); - notifier.notify_one(); - return; - } - /** Common::Queue::enqueue **************************************************/ - - - /** Common::Queue::dequeue **************************************************/ - /** - * @fn dequeue - * @brief pops the first message off the queue - * @param none - * @return std::string message - * - * Get the "front"-element. - * If the queue is empty, wait untill an element is avaiable. - * - */ - std::string Queue::dequeue(void) { - std::unique_lock lock(queue_mutex); - while(message_queue.empty()) { - notifier.wait(lock); // release lock as long as the wait and reaquire it afterwards. - } - std::string message = message_queue.front(); - message_queue.pop(); - return message; - } - /** Common::Queue::dequeue **************************************************/ - - - /** Common::FitsKeys::get_keytype *******************************************/ - /** - * @fn get_keytype - * @brief return the keyword type based on the keyvalue - * @param std::string value - * @return std::string type: "BOOL", "STRING", "DOUBLE", "INT" - * - * This function looks at the contents of the value string to determine if it - * contains an INT, DOUBLE, BOOL or STRING, and returns a string identifying the type. - * That type is used in FITS_file::add_user_key() for adding keywords to the header. - * - */ - std::string FitsKeys::get_keytype(std::string keyvalue) { - std::size_t pos(0); - - // if the entire string is either (exactly) T or F then it's a boolean - if ( keyvalue == "T" || keyvalue == "F" ) { - return std::string( "BOOL" ); + /** Common::Queue::enqueue **************************************************/ + /** + * @fn enqueue + * @brief puts a message into the queue + * @param std::string message + * @return none + * + */ + void Queue::enqueue(std::string message) { + std::lock_guard lock(queue_mutex); + message_queue.push(message); + notifier.notify_one(); + return; } - // skip the whitespaces - pos = keyvalue.find_first_not_of(' '); - if (pos == keyvalue.size()) return std::string("STRING"); // all spaces, so it's a string - - // check the significand - if (keyvalue[pos] == '+' || keyvalue[pos] == '-') ++pos; // skip the sign if exist - - // count the number of digits and number of decimal points - int n_nm, n_pt; - for (n_nm = 0, n_pt = 0; std::isdigit(keyvalue[pos]) || keyvalue[pos] == '.'; ++pos) { - keyvalue[pos] == '.' ? ++n_pt : ++n_nm; + /** Common::Queue::enqueue **************************************************/ + + + /** Common::Queue::dequeue **************************************************/ + /** + * @fn dequeue + * @brief pops the first message off the queue + * @param none + * @return std::string message + * + * Get the "front"-element. + * If the queue is empty, wait untill an element is avaiable. + * + */ + std::string Queue::dequeue(void) { + std::unique_lock lock(queue_mutex); + while (message_queue.empty()) { + notifier.wait(lock); // release lock as long as the wait and reaquire it afterwards. + } + std::string message = message_queue.front(); + message_queue.pop(); + return message; } - if (n_pt>1 || n_nm<1 || pos 1 || n_nm < 1 || pos < keyvalue.size()) { + // no more than one point, no numbers, or a non-digit character + return std::string("STRING"); // then it's a string + } + + // skip the trailing whitespaces + while (keyvalue[pos] == ' ') { + ++pos; + } + + std::string check_type; + + if (pos == keyvalue.size()) { + // If it's an INT or DOUBLE, don't return that type until it has been checked, below + // + if (keyvalue.find(".") == std::string::npos) // all numbers and no decimals, it's an integer + check_type = "INT"; + else // otherwise numbers with a decimal, it's a float + check_type = "DOUBLE"; + } else return std::string("STRING"); // lastly, must be a string + + // If it's an INT or a DOUBLE then try to convert the value to INT or DOUBLE. + // If that conversion fails then set the type to STRING. + // + try { + if (check_type == "INT") std::stoi(keyvalue); + if (check_type == "DOUBLE") std::stod(keyvalue); + } catch (std::invalid_argument &) { + return std::string("STRING"); + } + catch (std::out_of_range &) { + return std::string("STRING"); + } + return check_type; } - // skip the trailing whitespaces - while (keyvalue[pos] == ' ') { - ++pos; + /** Common::FitsKeys::get_keytype *******************************************/ + + + /** Common::FitsKeys::listkeys **********************************************/ + /** + * @fn listkeys + * @brief list FITS keywords in internal database + * @param none + * @return NO_ERROR + * + */ + long FitsKeys::listkeys() { + std::string function = "Common::FitsKeys::listkeys"; + std::stringstream message; + fits_key_t::iterator keyit; + for (keyit = this->keydb.begin(); + keyit != this->keydb.end(); + keyit++) { + message.str(""); + message << keyit->second.keyword << " = " << keyit->second.keyvalue; + if (!keyit->second.keycomment.empty()) message << " // " << keyit->second.keycomment; + message << " (" << keyit->second.keytype << ")"; + logwrite(function, message.str()); + } + return NO_ERROR; } - std::string check_type; - - if (pos == keyvalue.size()) { - // If it's an INT or DOUBLE, don't return that type until it has been checked, below - // - if (keyvalue.find(".") == std::string::npos) // all numbers and no decimals, it's an integer - check_type = "INT"; - else // otherwise numbers with a decimal, it's a float - check_type = "DOUBLE"; - } - else return std::string("STRING"); // lastly, must be a string - - // If it's an INT or a DOUBLE then try to convert the value to INT or DOUBLE. - // If that conversion fails then set the type to STRING. - // - try { - if ( check_type == "INT" ) std::stoi( keyvalue ); - if ( check_type == "DOUBLE" ) std::stod( keyvalue ); - } - catch ( std::invalid_argument & ) { - return std::string( "STRING" ); - } - catch ( std::out_of_range & ) { - return std::string( "STRING" ); - } - return check_type; - - } - /** Common::FitsKeys::get_keytype *******************************************/ - - - /** Common::FitsKeys::listkeys **********************************************/ - /** - * @fn listkeys - * @brief list FITS keywords in internal database - * @param none - * @return NO_ERROR - * - */ - long FitsKeys::listkeys() { - std::string function = "Common::FitsKeys::listkeys"; - std::stringstream message; - fits_key_t::iterator keyit; - for (keyit = this->keydb.begin(); - keyit != this->keydb.end(); - keyit++) { - message.str(""); - message << keyit->second.keyword << " = " << keyit->second.keyvalue; - if ( ! keyit->second.keycomment.empty() ) message << " // " << keyit->second.keycomment; - message << " (" << keyit->second.keytype << ")"; - logwrite(function, message.str()); - } - return NO_ERROR; - } - /** Common::FitsKeys::listkeys **********************************************/ - - - /** Common::FitsKeys::addkey ************************************************/ - /** - * @fn addkey - * @brief add FITS keyword to internal database - * @param std::string arg - * @return ERROR for improper input arg, otherwise NO_ERROR - * - * Expected format of input arg is KEYWORD=VALUE//COMMENT - * where COMMENT is optional. KEYWORDs are automatically converted to uppercase. - * - * Internal database is Common::FitsKeys::keydb - * - */ - long FitsKeys::addkey(std::string arg) { - std::string function = "Common::FitsKeys::addkey"; - std::stringstream message; - std::vector tokens; - std::string keyword, keystring, keyvalue, keytype, keycomment; - std::string comment_separator = "//"; - - // There must be one equal '=' sign in the incoming string, so that will make two tokens here - // - Tokenize(arg, tokens, "="); - if (tokens.size() != 2) { - logwrite( function, "missing or too many '=': expected KEYWORD=VALUE//COMMENT (optional comment)" ); - return ERROR; - } - - keyword = tokens[0].substr(0,8); // truncate keyword to 8 characters - keyword = keyword.erase(keyword.find_last_not_of(" ")+1); // remove trailing spaces from keyword - std::locale loc; - for (std::string::size_type ii=0; iikeydb.find(keyword); - if (ii==this->keydb.end()) { - message.str(""); message << "keyword " << keyword << " not found"; - logwrite(function, message.str()); - } - else { - this->keydb.erase(ii); - message.str(""); message << "keyword " << keyword << " erased"; - logwrite(function, message.str()); - } - return NO_ERROR; - } - - // check for instances of the comment separator in keycomment - // - if (keycomment.find(comment_separator) != std::string::npos) { - message.str(""); message << "ERROR: FITS comment delimiter: found too many instancces of " << comment_separator << " in keycomment"; - logwrite( function, message.str() ); - return NO_ERROR; - } - - // insert new entry into the database - // - this->keydb[keyword].keyword = keyword; - this->keydb[keyword].keytype = this->get_keytype(keyvalue); - this->keydb[keyword].keyvalue = keyvalue; - this->keydb[keyword].keycomment = keycomment; + /** Common::FitsKeys::listkeys **********************************************/ + + + /** Common::FitsKeys::addkey ************************************************/ + /** + * @fn addkey + * @brief add FITS keyword to internal database + * @param std::string arg + * @return ERROR for improper input arg, otherwise NO_ERROR + * + * Expected format of input arg is KEYWORD=VALUE//COMMENT + * where COMMENT is optional. KEYWORDs are automatically converted to uppercase. + * + * Internal database is Common::FitsKeys::keydb + * + */ + long FitsKeys::addkey(std::string arg) { + std::string function = "Common::FitsKeys::addkey"; + std::stringstream message; + std::vector tokens; + std::string keyword, keystring, keyvalue, keytype, keycomment; + std::string comment_separator = "//"; + + // There must be one equal '=' sign in the incoming string, so that will make two tokens here + // + Tokenize(arg, tokens, "="); + if (tokens.size() != 2) { + logwrite(function, "missing or too many '=': expected KEYWORD=VALUE//COMMENT (optional comment)"); + return ERROR; + } + + keyword = tokens[0].substr(0, 8); // truncate keyword to 8 characters + keyword = keyword.erase(keyword.find_last_not_of(" ") + 1); // remove trailing spaces from keyword + std::locale loc; + for (std::string::size_type ii = 0; ii < keyword.length(); ++ii) { + // Convert keyword to upper case: + keyword[ii] = std::toupper(keyword[ii], loc); // prevents duplications in STL map + } + keystring = tokens[1]; // tokenize the rest in a moment + + size_t pos = keystring.find(comment_separator); // location of the comment separator + keyvalue = keystring.substr(0, pos); // keyvalue is everything up to comment + keyvalue = keyvalue.erase(0, keyvalue.find_first_not_of(" ")); // remove leading spaces from keyvalue + keyvalue = keyvalue.erase(keyvalue.find_last_not_of(" ") + 1); // remove trailing spaces from keyvalue + if (pos != std::string::npos) { + keycomment = keystring.erase(0, pos + comment_separator.length()); + keycomment = keycomment.erase(0, keycomment.find_first_not_of(" ")); + // remove leading spaces from keycomment + } + + // Delete the keydb entry for associated keyword if the keyvalue is a sole period '.' + // + if (keyvalue == ".") { + fits_key_t::iterator ii = this->keydb.find(keyword); + if (ii == this->keydb.end()) { + message.str(""); + message << "keyword " << keyword << " not found"; + logwrite(function, message.str()); + } else { + this->keydb.erase(ii); + message.str(""); + message << "keyword " << keyword << " erased"; + logwrite(function, message.str()); + } + return NO_ERROR; + } + + // check for instances of the comment separator in keycomment + // + if (keycomment.find(comment_separator) != std::string::npos) { + message.str(""); + message << "ERROR: FITS comment delimiter: found too many instancces of " << comment_separator << + " in keycomment"; + logwrite(function, message.str()); + return NO_ERROR; + } + + // insert new entry into the database + // + this->keydb[keyword].keyword = keyword; + this->keydb[keyword].keytype = this->get_keytype(keyvalue); + this->keydb[keyword].keyvalue = keyvalue; + this->keydb[keyword].keycomment = keycomment; #ifdef LOGLEVEL_DEBUG message.str(""); message << "[DEBUG] added key: " << keyword << "=" << keyvalue << " (" << this->keydb[keyword].keytype << ") // " << keycomment; logwrite( function, message.str() ); #endif - return NO_ERROR; - } - /** Common::FitsKeys::addkey ************************************************/ - - - /***** Common::FitsKeys::delkey *********************************************/ - /** - * @brief delete FITS keyword from internal database - * @param[in] keyword - * @return ERROR for improper input arg, otherwise NO_ERROR - * - */ - long FitsKeys::delkey( std::string keyword ) { - std::string function = "Common::FitsKeys::delkey"; - std::stringstream message; - - try { - std::transform( keyword.begin(), keyword.end(), keyword.begin(), ::toupper ); // make uppercase - } - catch (...) { - message.str(""); message << "converting keyword " << keyword << " to uppercase"; - logwrite( function, message.str() ); - return ERROR; + return NO_ERROR; } - // Delete the keydb entry for associated keyword (if found). - // Don't report anything if keyword is not found. - // - fits_key_t::iterator keyit = this->keydb.find( keyword ); - if ( keyit != this->keydb.end() ) { - this->keydb.erase( keyit ); - message.str(""); message << "keyword " << keyword << " erased"; - logwrite( function, message.str() ); + /** Common::FitsKeys::addkey ************************************************/ + + + /***** Common::FitsKeys::delkey *********************************************/ + /** + * @brief delete FITS keyword from internal database + * @param[in] keyword + * @return ERROR for improper input arg, otherwise NO_ERROR + * + */ + long FitsKeys::delkey(std::string keyword) { + std::string function = "Common::FitsKeys::delkey"; + std::stringstream message; + + try { + std::transform(keyword.begin(), keyword.end(), keyword.begin(), ::toupper); // make uppercase + } catch (...) { + message.str(""); + message << "converting keyword " << keyword << " to uppercase"; + logwrite(function, message.str()); + return ERROR; + } + + // Delete the keydb entry for associated keyword (if found). + // Don't report anything if keyword is not found. + // + fits_key_t::iterator keyit = this->keydb.find(keyword); + if (keyit != this->keydb.end()) { + this->keydb.erase(keyit); + message.str(""); + message << "keyword " << keyword << " erased"; + logwrite(function, message.str()); + } + return NO_ERROR; } - return NO_ERROR; - } - /***** Common::FitsKeys::delkey *********************************************/ + /***** Common::FitsKeys::delkey *********************************************/ } diff --git a/common/common.h b/common/common.h index 09245ff4..c987a17d 100644 --- a/common/common.h +++ b/common/common.h @@ -23,97 +23,93 @@ const long BUSY = 2; const long TIMEOUT = 3; namespace Common { - - /**************** Common::FitsKeys ******************************************/ - /* - * @class FitsKeys - * @brief provides a user-defined keyword database - * - */ - class FitsKeys { + /**************** Common::FitsKeys ******************************************/ + /* + * @class FitsKeys + * @brief provides a user-defined keyword database + * + */ + class FitsKeys { private: public: - FitsKeys() = default; - - std::string get_keytype(std::string keyvalue); /// return type of keyword based on value - long listkeys(); /// list FITS keys in the internal database - long addkey(std::string arg); /// add FITS key to the internal database - long delkey(std::string arg); /// delete FITS key from the internal database - void erasedb() { this->keydb.clear(); }; /// erase the entire contents of the internal database - - /***** Common::FitsKeys::addkey *****************************************/ - /** - * @brief template function adds FITS keyword to internal database - * @details no parsing is done here - * @param[in] keyword keyword name - * @param[in] value value for keyword, any type - * @param[in] comment comment - * @param[in] prec decimal places of precision - * @return ERROR or NO_ERROR - * - * This function is overloaded. - * - * This version adds the keyword=value//comment directly into the - * database with no parsing. The database stores only strings so - * the type variable preserves the type and informs the FITS writer. - * - * The template input "tval" here allows passing in any type of value, - * and the type will be set by the actual input type. However, if - * the input type is a string then an attempt will be made to infer the - * type from the string (this allows passing in a string "12.345" and - * have it be converted to a double. - * - * The prec argument sets the displayed precision of double or float - * types if passed in as a double or float. Default is 8 if not supplied. - * - * The overloading for type bool is because for the template class, - * since T can be evaluated as string, it can't be treated as bool, - * so this is the easiest way to extract the bool value. - * - */ - inline long addkey( const std::string &key, bool bval, const std::string &comment ) { - return addkey( key, (bval?"T":"F"), comment, 0 ); - } - template long addkey( const std::string &key, T tval, const std::string &comment ) { - return addkey( key, tval, comment, 8 ); - } - template long addkey( const std::string &key, T tval, const std::string &comment, int prec ) { - std::stringstream val; - std::string type; - - // "convert" the type value into a string via the appropriate stream - // and set the type string - // - if ( std::is_same::value ) { - val << std::fixed << std::setprecision( prec ) << tval; - type = "DOUBLE"; - } - else - if ( std::is_same::value ) { - val << std::fixed << std::setprecision( prec ) << tval; - type = "FLOAT"; + FitsKeys() = default; + + std::string get_keytype(std::string keyvalue); /// return type of keyword based on value + long listkeys(); /// list FITS keys in the internal database + long addkey(std::string arg); /// add FITS key to the internal database + long delkey(std::string arg); /// delete FITS key from the internal database + void erasedb() { this->keydb.clear(); }; /// erase the entire contents of the internal database + + /***** Common::FitsKeys::addkey *****************************************/ + /** + * @brief template function adds FITS keyword to internal database + * @details no parsing is done here + * @param[in] keyword keyword name + * @param[in] value value for keyword, any type + * @param[in] comment comment + * @param[in] prec decimal places of precision + * @return ERROR or NO_ERROR + * + * This function is overloaded. + * + * This version adds the keyword=value//comment directly into the + * database with no parsing. The database stores only strings so + * the type variable preserves the type and informs the FITS writer. + * + * The template input "tval" here allows passing in any type of value, + * and the type will be set by the actual input type. However, if + * the input type is a string then an attempt will be made to infer the + * type from the string (this allows passing in a string "12.345" and + * have it be converted to a double. + * + * The prec argument sets the displayed precision of double or float + * types if passed in as a double or float. Default is 8 if not supplied. + * + * The overloading for type bool is because for the template class, + * since T can be evaluated as string, it can't be treated as bool, + * so this is the easiest way to extract the bool value. + * + */ + inline long addkey(const std::string &key, bool bval, const std::string &comment) { + return addkey(key, (bval ? "T" : "F"), comment, 0); } - else - if ( std::is_same::value ) { - val << tval; - type = "INT"; - } - else - if ( std::is_same::value ) { - val << tval; - type = "LONG"; - } - else { - val << tval; - type = this->get_keytype( val.str() ); // try to infer the type from any string + + template + long addkey(const std::string &key, T tval, const std::string &comment) { + return addkey(key, tval, comment, 8); } - // insert new entry into the database - // - this->keydb[key].keyword = key; - this->keydb[key].keytype = type; - this->keydb[key].keyvalue = val.str(); - this->keydb[key].keycomment = comment; + template + long addkey(const std::string &key, T tval, const std::string &comment, int prec) { + std::stringstream val; + std::string type; + + // "convert" the type value into a string via the appropriate stream + // and set the type string + // + if (std::is_same::value) { + val << std::fixed << std::setprecision(prec) << tval; + type = "DOUBLE"; + } else if (std::is_same::value) { + val << std::fixed << std::setprecision(prec) << tval; + type = "FLOAT"; + } else if (std::is_same::value) { + val << tval; + type = "INT"; + } else if (std::is_same::value) { + val << tval; + type = "LONG"; + } else { + val << tval; + type = this->get_keytype(val.str()); // try to infer the type from any string + } + + // insert new entry into the database + // + this->keydb[key].keyword = key; + this->keydb[key].keytype = type; + this->keydb[key].keyvalue = val.str(); + this->keydb[key].keycomment = comment; #ifdef LOGLEVEL_DEBUG std::string function = "Common::FitsKeys::addkey"; @@ -121,85 +117,90 @@ namespace Common { message << "[DEBUG] added key: " << key << "=" << tval << " (" << this->keydb[key].keytype << ") // " << comment; logwrite( function, message.str() ); #endif - return( NO_ERROR ); - } - /***** Common::FitsKeys::addkey *****************************************/ - - - typedef struct { /// structure of FITS keyword internal database - std::string keyword; - std::string keytype; - std::string keyvalue; - std::string keycomment; - } user_key_t; - - typedef std::map fits_key_t; /// STL map for the actual keyword database - - fits_key_t keydb; /// keyword database - - // Find all entries in the keyword database which start with the search_for string, - // return a vector of iterators. - // - std::vector< fits_key_t::const_iterator > FindKeys( std::string search_for ) { - std::vector< fits_key_t::const_iterator > vec; - for ( auto it = this->keydb.lower_bound( search_for ); - it != std::end( this->keydb ) && it->first.compare( 0, search_for.size(), search_for ) == 0; - ++it ) { - vec.push_back( it ); + return (NO_ERROR); } - return( vec ); - } - // Find and remove all entries in the keyword database which match the search_for string. - // - void EraseKeys( std::string search_for ) { - std::string function = "FitsKeys::EraseKeys"; - std::stringstream message; - std::vector< fits_key_t::const_iterator > erasevec; + /***** Common::FitsKeys::addkey *****************************************/ + + + typedef struct { + /// structure of FITS keyword internal database + std::string keyword; + std::string keytype; + std::string keyvalue; + std::string keycomment; + } user_key_t; + + typedef std::map fits_key_t; /// STL map for the actual keyword database - // get a vector of iterators for all the keys matching the search string + fits_key_t keydb; /// keyword database + + // Find all entries in the keyword database which start with the search_for string, + // return a vector of iterators. // - erasevec = this->FindKeys( search_for ); + std::vector FindKeys(std::string search_for) { + std::vector vec; + for (auto it = this->keydb.lower_bound(search_for); + it != std::end(this->keydb) && it->first.compare(0, search_for.size(), search_for) == 0; + ++it) { + vec.push_back(it); + } + return (vec); + } + + // Find and remove all entries in the keyword database which match the search_for string. + // + void EraseKeys(std::string search_for) { + std::string function = "FitsKeys::EraseKeys"; + std::stringstream message; + std::vector erasevec; + + // get a vector of iterators for all the keys matching the search string + // + erasevec = this->FindKeys(search_for); #ifdef LOGLEVEL_DEBUG message.str(""); message << "[DEBUG] found " << erasevec.size() << " entries matching \"" << search_for << "*\""; logwrite( function, message.str() ); #endif - // loop through that vector and erase the selected iterators from the database - // - for ( auto vec : erasevec ) { + // loop through that vector and erase the selected iterators from the database + // + for (auto vec: erasevec) { #ifdef LOGLEVEL_DEBUG message.str(""); message << "[DEBUG] erasing " << vec->first; logwrite( function, message.str() ); #endif - this->keydb.erase( vec ); + this->keydb.erase(vec); + } + return; } - return; - } - }; - /**************** Common::FitsKeys ******************************************/ - - - /**************** Common::Queue *********************************************/ - /** - * @class Queue - * @brief provides a thread-safe messaging queue - * - */ - class Queue { + }; + + /**************** Common::FitsKeys ******************************************/ + + + /**************** Common::Queue *********************************************/ + /** + * @class Queue + * @brief provides a thread-safe messaging queue + * + */ + class Queue { private: - std::queue message_queue; - mutable std::mutex queue_mutex; - std::condition_variable notifier; - bool is_running; + std::queue message_queue; + mutable std::mutex queue_mutex; + std::condition_variable notifier; + bool is_running; + public: - Queue(void) : is_running(false) { } + Queue(void) : is_running(false) { + } - void service_running(bool state) { this->is_running = state; }; /// set service running - bool service_running() { return this->is_running; }; /// is the service running? + void service_running(bool state) { this->is_running = state; }; /// set service running + bool service_running() { return this->is_running; }; /// is the service running? - void enqueue(std::string message); /// push an element into the queue. - std::string dequeue(void); /// pop an element from the queue - }; - /**************** Common::Queue *********************************************/ + void enqueue(std::string message); /// push an element into the queue. + std::string dequeue(void); /// pop an element from the queue + }; + /**************** Common::Queue *********************************************/ } diff --git a/emulator/CMakeLists.txt b/emulator/CMakeLists.txt index cfdc4ea4..9ce39832 100644 --- a/emulator/CMakeLists.txt +++ b/emulator/CMakeLists.txt @@ -2,46 +2,46 @@ # emulator/CMakeLists.txt # ---------------------------------------------------------------------------- -cmake_minimum_required( VERSION 3.12 ) - -set( EMULATOR_DIR ${PROJECT_BASE_DIR}/emulator ) - -include_directories( ${PROJECT_BASE_DIR}/utils - ${PROJECT_BASE_DIR}/camerad - ${PROJECT_BASE_DIR}/common ) - -add_definitions( -Wall -ansi -O2 -Wno-variadic-macros -std=c++17 -ggdb ) - -if( ${INTERFACE_TYPE} STREQUAL "Archon" ) - add_definitions(-DSTA_ARCHON) - set( EMULATOR_TARGET emulatorArchon ) - list(APPEND EMULATOR_SOURCE - "${EMULATOR_DIR}/emulator-archon.cpp" - ) - add_library(emulatorInterface STATIC - ${EMULATOR_DIR}/emulator-archon.cpp - ) - -add_library(${EMULATOR_TARGET} ${EMULATOR_SOURCE}) - -target_include_directories(${EMULATOR_TARGET} PUBLIC ${PROJECT_INCL_DIR}) - -add_executable(emulator - ${EMULATOR_DIR}/emulator-server.cpp - ) - -target_link_libraries(emulator - network - logentry - utilities - common - camera - ${EMULATOR_TARGET} - ${CMAKE_THREAD_LIBS_INIT} - ) -elseif( ${INTERFACE_TYPE} STREQUAL "AstroCam" ) - message( STATUS "emulator not implemented for AstroCam" ) -else() - message( FATAL_ERROR " unrecognized or empty INTERFACE_TYPE \"" ${INTERFACE_TYPE} "\": expected Archon or AstroCam" ) -endif() +cmake_minimum_required(VERSION 3.12) + +set(EMULATOR_DIR ${PROJECT_BASE_DIR}/emulator) + +include_directories(${PROJECT_BASE_DIR}/utils + ${PROJECT_BASE_DIR}/camerad + ${PROJECT_BASE_DIR}/common) + +add_definitions(-Wall -ansi -O2 -Wno-variadic-macros -std=c++17 -ggdb) + +if (${INTERFACE_TYPE} STREQUAL "Archon") + add_definitions(-DSTA_ARCHON) + set(EMULATOR_TARGET emulatorArchon) + list(APPEND EMULATOR_SOURCE + "${EMULATOR_DIR}/emulator-archon.cpp" + ) + add_library(emulatorInterface STATIC + ${EMULATOR_DIR}/emulator-archon.cpp + ) + + add_library(${EMULATOR_TARGET} ${EMULATOR_SOURCE}) + + target_include_directories(${EMULATOR_TARGET} PUBLIC ${PROJECT_INCL_DIR}) + + add_executable(emulator + ${EMULATOR_DIR}/emulator-server.cpp + ) + + target_link_libraries(emulator + network + logentry + utilities + common + camera + ${EMULATOR_TARGET} + ${CMAKE_THREAD_LIBS_INIT} + ) +elseif (${INTERFACE_TYPE} STREQUAL "AstroCam") + message(STATUS "emulator not implemented for AstroCam") +else () + message(FATAL_ERROR " unrecognized or empty INTERFACE_TYPE \"" ${INTERFACE_TYPE} "\": expected Archon or AstroCam") +endif () diff --git a/emulator/emulator-archon.cpp b/emulator/emulator-archon.cpp index 8260f89d..2b3c2a9b 100644 --- a/emulator/emulator-archon.cpp +++ b/emulator/emulator-archon.cpp @@ -1,829 +1,830 @@ #include "emulator-archon.h" -namespace Archon { - - // Archon::Interface constructor - // - Interface::Interface() { - struct timespec ts; // container for the time - if ( clock_gettime( CLOCK_REALTIME, &ts ) != 0 ) { - this->init_time = 0; - } - else { - this->init_time = 1E8 * ( ts.tv_sec + (ts.tv_nsec / 1E9) ); - } - this->modtype.resize( nmods ); - this->modversion.resize( nmods ); - this->poweron = false; - this->bigbuf = false; - - this->image.framen = 0; - this->image.activebufs = -1; - this->image.taplines = -1; - this->image.linecount = -1; - this->image.pixelcount = -1; - this->image.readtime = -1; - this->image.exptime = 0; - this->image.exposure_factor = 1000; // default is msec - - this->frame.index = 0; - this->frame.frame = 0; - this->frame.rbuf = 0; - this->frame.wbuf = 0; - this->frame.timer = ""; - - this->frame.bufsample.resize( Archon::nbufs ); - this->frame.bufcomplete.resize( Archon::nbufs ); - this->frame.bufmode.resize( Archon::nbufs ); - this->frame.bufbase.resize( Archon::nbufs ); - this->frame.bufframen.resize( Archon::nbufs ); - this->frame.bufwidth.resize( Archon::nbufs ); - this->frame.bufheight.resize( Archon::nbufs ); - this->frame.bufpixels.resize( Archon::nbufs ); - this->frame.buflines.resize( Archon::nbufs ); - this->frame.bufrawblocks.resize( Archon::nbufs ); - this->frame.bufrawlines.resize( Archon::nbufs ); - this->frame.bufrawoffset.resize( Archon::nbufs ); - this->frame.buftimestamp.resize( Archon::nbufs ); - this->frame.bufretimestamp.resize( Archon::nbufs ); - this->frame.buffetimestamp.resize( Archon::nbufs ); -// this->frame.bufreatimestamp.resize( Archon::nbufs ); // TODO future -// this->frame.buffeatimestamp.resize( Archon::nbufs ); -// this->frame.bufrebtimestamp.resize( Archon::nbufs ); -// this->frame.buffebtimestamp.resize( Archon::nbufs ); - } - - // Archon::Interface deconstructor - // - Interface::~Interface() { - } - - /**************** Interface::configure_controller ***************************/ - /** - * @fn configure_controller - * @brief get configuration parameters from .cfg file - * @param none - * @return ERROR or NO_ERROR - * - * Called at startup to read the configuration file. - * - */ - long Interface::configure_controller() { - std::string function = "(Archon::Interface::configure_controller) "; - - // loop through the entries in the configuration file, stored in config class +namespace Archon { + // Archon::Interface constructor // - for ( int entry=0; entry < this->config.n_entries; entry++ ) { - - try { - if ( config.param.at(entry).compare(0, 15, "EMULATOR_SYSTEM")==0 ) { - this->systemfile = config.arg.at(entry); - } - if ( config.param.at(entry).compare(0, 12, "READOUT_TIME")==0 ) { - this->image.readtime = std::stoi( config.arg.at(entry) ); + Interface::Interface() { + struct timespec ts; // container for the time + if (clock_gettime(CLOCK_REALTIME, &ts) != 0) { + this->init_time = 0; + } else { + this->init_time = 1E8 * (ts.tv_sec + (ts.tv_nsec / 1E9)); } - if ( config.param.at(entry).compare(0, 12, "EXPOSE_PARAM")==0) { - this->exposeparam = config.arg[entry]; - } - } - catch( std::invalid_argument & ) { - std::cerr << function << "ERROR: invalid argument parsing entry " << entry << " of " << this->config.n_entries << "\n"; - return( ERROR ); - } - catch( std::out_of_range & ) { - std::cerr << function << "ERROR: value out of range parsing entry " << entry << " of " << this->config.n_entries << "\n"; - return( ERROR ); - } - catch( ... ) { - std::cerr << function << "unknown error parsing entry " << entry << " of " << this->config.n_entries << "\n"; - return( ERROR ); - } - } - std::cerr << function << "complete" << "\n"; - - return( NO_ERROR ); - } - /**************** Interface::configure_controller ***************************/ - - - /**************** Interface::system_report **********************************/ - /** - * @fn system_report - * @brief handles the incoming SYSTEM command - * @param buf, incoming command string - * @param &retstring, reference to string for return values - * @return ERROR or NO_ERROR - * - * This reads the emulated system information from a file specified in - * the configuration file by EMULATOR_SYSTEM. - * - */ - long Interface::system_report(std::string buf, std::string &retstring) { - std::string function = "(Archon::Interface::system_report) "; - std::fstream filestream; - std::string line; - std::vector tokens; - - // need system file + this->modtype.resize(nmods); + this->modversion.resize(nmods); + this->poweron = false; + this->bigbuf = false; + + this->image.framen = 0; + this->image.activebufs = -1; + this->image.taplines = -1; + this->image.linecount = -1; + this->image.pixelcount = -1; + this->image.readtime = -1; + this->image.exptime = 0; + this->image.exposure_factor = 1000; // default is msec + + this->frame.index = 0; + this->frame.frame = 0; + this->frame.rbuf = 0; + this->frame.wbuf = 0; + this->frame.timer = ""; + + this->frame.bufsample.resize(Archon::nbufs); + this->frame.bufcomplete.resize(Archon::nbufs); + this->frame.bufmode.resize(Archon::nbufs); + this->frame.bufbase.resize(Archon::nbufs); + this->frame.bufframen.resize(Archon::nbufs); + this->frame.bufwidth.resize(Archon::nbufs); + this->frame.bufheight.resize(Archon::nbufs); + this->frame.bufpixels.resize(Archon::nbufs); + this->frame.buflines.resize(Archon::nbufs); + this->frame.bufrawblocks.resize(Archon::nbufs); + this->frame.bufrawlines.resize(Archon::nbufs); + this->frame.bufrawoffset.resize(Archon::nbufs); + this->frame.buftimestamp.resize(Archon::nbufs); + this->frame.bufretimestamp.resize(Archon::nbufs); + this->frame.buffetimestamp.resize(Archon::nbufs); + // this->frame.bufreatimestamp.resize( Archon::nbufs ); // TODO future + // this->frame.buffeatimestamp.resize( Archon::nbufs ); + // this->frame.bufrebtimestamp.resize( Archon::nbufs ); + // this->frame.buffebtimestamp.resize( Archon::nbufs ); + } + + // Archon::Interface deconstructor // - if ( this->systemfile.empty() ) { - std::cerr << function << "ERROR: missing EMULATOR_SYSTEM from configuration file\n"; - return( ERROR ); - } + Interface::~Interface() { + } + + /**************** Interface::configure_controller ***************************/ + /** + * @fn configure_controller + * @brief get configuration parameters from .cfg file + * @param none + * @return ERROR or NO_ERROR + * + * Called at startup to read the configuration file. + * + */ + long Interface::configure_controller() { + std::string function = "(Archon::Interface::configure_controller) "; + + // loop through the entries in the configuration file, stored in config class + // + for (int entry = 0; entry < this->config.n_entries; entry++) { + try { + if (config.param.at(entry).compare(0, 15, "EMULATOR_SYSTEM") == 0) { + this->systemfile = config.arg.at(entry); + } + if (config.param.at(entry).compare(0, 12, "READOUT_TIME") == 0) { + this->image.readtime = std::stoi(config.arg.at(entry)); + } + if (config.param.at(entry).compare(0, 12, "EXPOSE_PARAM") == 0) { + this->exposeparam = config.arg[entry]; + } + } catch (std::invalid_argument &) { + std::cerr << function << "ERROR: invalid argument parsing entry " << entry << " of " << this->config. + n_entries << "\n"; + return (ERROR); + } + catch (std::out_of_range &) { + std::cerr << function << "ERROR: value out of range parsing entry " << entry << " of " << this->config. + n_entries << "\n"; + return (ERROR); + } + catch (...) { + std::cerr << function << "unknown error parsing entry " << entry << " of " << this->config.n_entries << + "\n"; + return (ERROR); + } + } - // try to open the file - // - try { - filestream.open( this->systemfile, std::ios_base::in ); - } - catch(...) { - std::cerr << function << "ERROR: opening system file: " << this->systemfile << ": " << std::strerror(errno) << "\n"; - return( ERROR ); - } + std::cerr << function << "complete" << "\n"; - if ( ! filestream.is_open() || ! filestream.good() ) { - std::cerr << function << " ERROR: system file: " << this->systemfile << " not open\n"; - return( ERROR ); + return (NO_ERROR); } - while ( getline( filestream, line ) ) { + /**************** Interface::configure_controller ***************************/ - if ( line == "[SYSTEM]" ) continue; - retstring += line + " "; + /**************** Interface::system_report **********************************/ + /** + * @fn system_report + * @brief handles the incoming SYSTEM command + * @param buf, incoming command string + * @param &retstring, reference to string for return values + * @return ERROR or NO_ERROR + * + * This reads the emulated system information from a file specified in + * the configuration file by EMULATOR_SYSTEM. + * + */ + long Interface::system_report(std::string buf, std::string &retstring) { + std::string function = "(Archon::Interface::system_report) "; + std::fstream filestream; + std::string line; + std::vector tokens; - Tokenize( line, tokens, "_=" ); - if ( tokens.size() != 3 ) continue; + // need system file + // + if (this->systemfile.empty()) { + std::cerr << function << "ERROR: missing EMULATOR_SYSTEM from configuration file\n"; + return (ERROR); + } - int module=0; - std::string version=""; + // try to open the file + // + try { + filestream.open(this->systemfile, std::ios_base::in); + } catch (...) { + std::cerr << function << "ERROR: opening system file: " << this->systemfile << ": " << std::strerror(errno) + << "\n"; + return (ERROR); + } - // get the type of each module from MODn_TYPE - // - try { + if (!filestream.is_open() || !filestream.good()) { + std::cerr << function << " ERROR: system file: " << this->systemfile << " not open\n"; + return (ERROR); + } - if ( tokens.at(0).compare( 0, 9, "BACKPLANE" ) == 0 ) { - if ( tokens.at(1) == "VERSION" ) this->backplaneversion = tokens.at(2); - continue; + while (getline(filestream, line)) { + if (line == "[SYSTEM]") continue; + + retstring += line + " "; + + Tokenize(line, tokens, "_="); + if (tokens.size() != 3) continue; + + int module = 0; + std::string version = ""; + + // get the type of each module from MODn_TYPE + // + try { + if (tokens.at(0).compare(0, 9, "BACKPLANE") == 0) { + if (tokens.at(1) == "VERSION") this->backplaneversion = tokens.at(2); + continue; + } + + if ((tokens.at(0).compare(0, 3, "MOD") == 0) && (tokens.at(1) == "TYPE")) { + module = std::stoi(tokens.at(0).substr(3)); + if ((module > 0) && (module <= nmods)) { + this->modtype.at(module - 1) = std::stoi(tokens.at(2)); + } else { + std::cerr << function << "ERROR: module " << module << " outside range {0:" << nmods << "}\n"; + return (ERROR); + } + } + + // get the module version + // + if ((tokens.at(0).compare(0, 3, "MOD") == 0) && (tokens.at(1) == "VERSION")) { + module = std::stoi(tokens.at(0).substr(3)); + if ((module > 0) && (module <= nmods)) { + this->modversion.at(module - 1) = tokens.at(2); + } else { + std::cerr << function << "ERROR: module " << module << " outside range {0:" << nmods << "}\n"; + return (ERROR); + } + } else { + continue; + } + } catch (std::invalid_argument &) { + std::cerr << function << "ERROR: invalid argument, unable to convert module or type\n"; + return (ERROR); + } + catch (std::out_of_range &) { + std::cerr << function << "ERROR: value out of range, unable to convert module or type\n"; + return (ERROR); + } + catch (...) { + std::cerr << function << "unknown error converting module or type\n"; + return (ERROR); + } } - if ( ( tokens.at(0).compare(0,3,"MOD")==0 ) && ( tokens.at(1)=="TYPE" ) ) { - module = std::stoi( tokens.at(0).substr(3) ); - if ( ( module > 0 ) && ( module <= nmods ) ) { - this->modtype.at( module-1 ) = std::stoi( tokens.at(2) ); - } - else { - std::cerr << function << "ERROR: module " << module << " outside range {0:" << nmods << "}\n"; - return( ERROR ); - } + return (NO_ERROR); + } + + /**************** Interface::system_report **********************************/ + + + /**************** Interface::status_report ****8*****************************/ + /** + * @fn status_report + * @brief + * @param + * @return ERROR or NO_ERROR + * + */ + long Interface::status_report(std::string &retstring) { + std::string function = "(Archon::Interface::status_report) "; + std::stringstream statstr; + + statstr << "VALID=" << 1 << " " + << "COUNT=" << 1 << " " + << "LOG=" << 0 << " " + << "POWER=" << this->poweron << " " + << "POWERGOOD=" << 1 << " " + << "OVERHEAT=" << 0 << " " + << "BACKPLANE_TEMP=" << 40 << " " + << "P2V5_V=" << 2.5 << " " + << "P2V5_I=" << 0 << " " + << "P5V_V=" << 5.0 << " " + << "P5V_I=" << 0 << " " + << "P6V_V=" << 6.0 << " " + << "P6V_I=" << 0 << " " + << "N6V_V=" << -6.0 << " " + << "N6V_I=" << 0 << " " + << "P17V_V=" << 17.0 << " " + << "P17V_I=" << 0 << " " + << "N17V_V=" << -17.0 << " " + << "N17V_I=" << 0 << " " + << "P35V_V=" << 35.0 << " " + << "P35V_I=" << 0 << " " + << "N35V_V=" << -35.0 << " " + << "N35V_I=" << 0 << " " + << "P100V_V=" << 100.0 << " " + << "P100V_I=" << 0 << " " + << "N100V_V=" << -100.0 << " " + << "N100V_I=" << 0 << " " + << "USER_V=" << 0 << " " + << "USER_I=" << 0 << " " + << "HEATER_V=" << 0 << " " + << "HEATER_I=" << 0 << " " + << "FANTACH=" << 0; + retstring = statstr.str(); + return (NO_ERROR); + } + + /**************** Interface::status_report ****8*****************************/ + + + /**************** Interface::timer_report *****8*****************************/ + /** + * @fn timer_report + * @brief returns the "Archon TIMER" + * @param &retstring, reference to string for return value + * @return ERROR or NO_ERROR + * + * Returns the Archon TIMER which is a 64 bit hex representation of + * the Archon time, counted in 10 nsec ticks. + * + */ + long Interface::timer_report(std::string &retstring) { + std::string function = "(Archon::Interface::timer_report) "; + struct timespec data; // container for the time + unsigned long long tm; // 64 bit int time in 10 nsec + std::stringstream tmstr; // padded hex representation + + if (clock_gettime(CLOCK_REALTIME, &data) != 0) return (ERROR); + tm = 1E8 * (data.tv_sec + (data.tv_nsec / 1E9)) - this->init_time; + tmstr << std::uppercase << std::setfill('0') << std::setw(16) << std::hex << tm; + retstring = tmstr.str(); + return (NO_ERROR); + } + + unsigned long Interface::get_timer() { + struct timespec data; // container for the time + unsigned long long tm; // 64 bit int time in 10 nsec + if (clock_gettime(CLOCK_REALTIME, &data) != 0) return (0); + tm = 1E8 * (data.tv_sec + (data.tv_nsec / 1E9)) - this->init_time; + return (tm); + } + + /**************** Interface::timer_report *****8*****************************/ + + + /**************** Interface::frame_report *****8*****************************/ + /** + * @fn frame_report + * @brief + * @param + * @return ERROR or NO_ERROR + * + */ + long Interface::frame_report(std::string &retstring) { + std::string function = "(Archon::Interface::frame_report) "; + std::string timenow; + std::stringstream framestr; + + if (this->image.activebufs == -1) { + std::cerr << function << + "ERROR: activebufs undefined. Check that an ACF was loaded and that it contains BIGBUF=x\n"; + return (ERROR); } - // get the module version - // - if ( ( tokens.at(0).compare(0,3,"MOD")==0 ) && ( tokens.at(1) == "VERSION" ) ) { - module = std::stoi( tokens.at(0).substr(3) ); - if ( ( module > 0 ) && ( module <= nmods ) ) { - this->modversion.at( module-1 ) = tokens.at(2); - } - else { - std::cerr << function << "ERROR: module " << module << " outside range {0:" << nmods << "}\n"; - return( ERROR ); - } + this->timer_report(this->frame.timer); + + framestr << "TIMER=" << this->frame.timer << " " + << "RBUF=" << this->frame.rbuf << " " + << "WBUF=" << this->frame.wbuf << " "; + + int bufn; + try { + for (bufn = 0; bufn < this->image.activebufs; bufn++) { + framestr << "BUF" << bufn + 1 << "SAMPLE=" << this->frame.bufsample.at(bufn) << " " + << "BUF" << bufn + 1 << "COMPLETE=" << this->frame.bufcomplete.at(bufn) << " " + << "BUF" << bufn + 1 << "MODE=" << this->frame.bufmode.at(bufn) << " " + << "BUF" << bufn + 1 << "BASE=" << this->frame.bufbase.at(bufn) << " " + << "BUF" << bufn + 1 << "FRAME=" << this->frame.bufframen.at(bufn) << " " + << "BUF" << bufn + 1 << "WIDTH=" << this->frame.bufwidth.at(bufn) << " " + << "BUF" << bufn + 1 << "HEIGHT=" << this->frame.bufheight.at(bufn) << " " + << "BUF" << bufn + 1 << "PIXELS=" << this->frame.bufpixels.at(bufn) << " " + << "BUF" << bufn + 1 << "LINES=" << this->frame.buflines.at(bufn) << " " + << "BUF" << bufn + 1 << "RAWBLOCKS=" << this->frame.bufrawblocks.at(bufn) << " " + << "BUF" << bufn + 1 << "RAWLINES=" << this->frame.bufrawlines.at(bufn) << " " + << "BUF" << bufn + 1 << "RAWOFFSET=" << this->frame.bufrawoffset.at(bufn) << " " + << "BUF" << bufn + 1 << "TIMESTAMP=" << this->frame.buftimestamp.at(bufn) << " " + << "BUF" << bufn + 1 << "RETIMESTAMP=" << this->frame.bufretimestamp.at(bufn) << " " + << "BUF" << bufn + 1 << "FETIMESTAMP=" << this->frame.buffetimestamp.at(bufn) << " " + // << "BUF" << bufn+1 << "REATIMESTAMP=" << " " + // << "BUF" << bufn+1 << "FEATIMESTAMP=" << " " + // << "BUF" << bufn+1 << "REBTIMESTAMP=" << " " + // << "BUF" << bufn+1 << "FEBTIMESTAMP=" << " " + ; + } + } catch (std::invalid_argument &) { + std::cerr << function << "ERROR: invalid buffer number " << bufn + 1 << "\n"; + return (ERROR); } - else { - continue; + catch (std::out_of_range &) { + std::cerr << function << "ERROR: buffer number " << bufn + 1 << " out of range {1:" << this->image. + activebufs << "}\n"; + return (ERROR); + } + catch (...) { + std::cerr << function << "unknown error accessing buffer number " << bufn + 1 << "\n"; + return (ERROR); } - } - catch( std::invalid_argument & ) { - std::cerr << function << "ERROR: invalid argument, unable to convert module or type\n"; - return( ERROR ); - } - catch( std::out_of_range & ) { - std::cerr << function << "ERROR: value out of range, unable to convert module or type\n"; - return( ERROR ); - } - catch( ... ) { - std::cerr << function << "unknown error converting module or type\n"; - return( ERROR ); - } - } + retstring = framestr.str(); + retstring = retstring.erase(retstring.find_last_not_of(" ") + 1); + // remove trailing space added because of for loop + + return (NO_ERROR); + } + + /**************** Interface::frame_report *****8*****************************/ + + + /**************** Interface:fetch_data **************************************/ + /** + * @fn fetch_data + * @brief + * @param cmd, incoming command string + * @return ERROR or NO_ERROR + * + */ + long Interface::fetch_data(std::string ref, 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 = NULL; + + if (cmd.length() != 21) { + // must be "FETCHxxxxxxxxyyyyyyyy", 21 chars + std::cerr << function << "ERROR: expecting form FETCHxxxxxxxxyyyyyyyy but got \"" << cmd << "\"\n"; + return (ERROR); + } - return( NO_ERROR ); - } - /**************** Interface::system_report **********************************/ - - - /**************** Interface::status_report ****8*****************************/ - /** - * @fn status_report - * @brief - * @param - * @return ERROR or NO_ERROR - * - */ - long Interface::status_report(std::string &retstring) { - std::string function = "(Archon::Interface::status_report) "; - std::stringstream statstr; - - statstr << "VALID=" << 1 << " " - << "COUNT=" << 1 << " " - << "LOG=" << 0 << " " - << "POWER=" << this->poweron << " " - << "POWERGOOD=" << 1 << " " - << "OVERHEAT=" << 0 << " " - << "BACKPLANE_TEMP=" << 40 << " " - << "P2V5_V=" << 2.5 << " " - << "P2V5_I=" << 0 << " " - << "P5V_V=" << 5.0 << " " - << "P5V_I=" << 0 << " " - << "P6V_V=" << 6.0 << " " - << "P6V_I=" << 0 << " " - << "N6V_V=" << -6.0 << " " - << "N6V_I=" << 0 << " " - << "P17V_V=" << 17.0 << " " - << "P17V_I=" << 0 << " " - << "N17V_V=" << -17.0 << " " - << "N17V_I=" << 0 << " " - << "P35V_V=" << 35.0 << " " - << "P35V_I=" << 0 << " " - << "N35V_V=" << -35.0 << " " - << "N35V_I=" << 0 << " " - << "P100V_V=" << 100.0 << " " - << "P100V_I=" << 0 << " " - << "N100V_V=" << -100.0 << " " - << "N100V_I=" << 0 << " " - << "USER_V=" << 0 << " " - << "USER_I=" << 0 << " " - << "HEATER_V=" << 0 << " " - << "HEATER_I=" << 0 << " " - << "FANTACH=" << 0 - ; - retstring = statstr.str(); - return( NO_ERROR ); - } - /**************** Interface::status_report ****8*****************************/ - - - /**************** Interface::timer_report *****8*****************************/ - /** - * @fn timer_report - * @brief returns the "Archon TIMER" - * @param &retstring, reference to string for return value - * @return ERROR or NO_ERROR - * - * Returns the Archon TIMER which is a 64 bit hex representation of - * the Archon time, counted in 10 nsec ticks. - * - */ - long Interface::timer_report(std::string &retstring) { - std::string function = "(Archon::Interface::timer_report) "; - struct timespec data; // container for the time - unsigned long long tm; // 64 bit int time in 10 nsec - std::stringstream tmstr; // padded hex representation - - if ( clock_gettime( CLOCK_REALTIME, &data ) != 0 ) return( ERROR ); - tm = 1E8 * ( data.tv_sec + (data.tv_nsec / 1E9) ) - this->init_time; - tmstr << std::uppercase << std::setfill('0') << std::setw(16) << std::hex << tm; - retstring = tmstr.str(); - return( NO_ERROR ); - } - unsigned long Interface::get_timer() { - struct timespec data; // container for the time - unsigned long long tm; // 64 bit int time in 10 nsec - if ( clock_gettime( CLOCK_REALTIME, &data ) != 0 ) return( 0 ); - tm = 1E8 * ( data.tv_sec + (data.tv_nsec / 1E9) ) - this->init_time; - return( tm ); - } - /**************** Interface::timer_report *****8*****************************/ - - - /**************** Interface::frame_report *****8*****************************/ - /** - * @fn frame_report - * @brief - * @param - * @return ERROR or NO_ERROR - * - */ - long Interface::frame_report(std::string &retstring) { - std::string function = "(Archon::Interface::frame_report) "; - std::string timenow; - std::stringstream framestr; - - if ( this->image.activebufs == -1 ) { - std::cerr << function << "ERROR: activebufs undefined. Check that an ACF was loaded and that it contains BIGBUF=x\n"; - return( ERROR ); - } + try { + std::stringstream hexblocks; + hexblocks << std::hex << "0x" << cmd.substr(13); + hexblocks >> reqblocks; + } catch (std::invalid_argument &) { + std::cerr << function << "ERROR: invalid argument parsing " << cmd << "\n"; + return (ERROR); + } + catch (std::out_of_range &) { + std::cerr << function << "ERROR: value out of range parsing " << cmd << "\n"; + return (ERROR); + } + catch (...) { + std::cerr << function << "unknown error parsing " << cmd << "\n"; + return (ERROR); + } - this->timer_report( this->frame.timer ); - - framestr << "TIMER=" << this->frame.timer << " " - << "RBUF=" << this->frame.rbuf << " " - << "WBUF=" << this->frame.wbuf << " "; - - int bufn; - try { - for ( bufn = 0; bufn < this->image.activebufs; bufn++ ) { - framestr << "BUF" << bufn+1 << "SAMPLE=" << this->frame.bufsample.at(bufn) << " " - << "BUF" << bufn+1 << "COMPLETE=" << this->frame.bufcomplete.at(bufn) << " " - << "BUF" << bufn+1 << "MODE=" << this->frame.bufmode.at(bufn) << " " - << "BUF" << bufn+1 << "BASE=" << this->frame.bufbase.at(bufn) << " " - << "BUF" << bufn+1 << "FRAME=" << this->frame.bufframen.at(bufn) << " " - << "BUF" << bufn+1 << "WIDTH=" << this->frame.bufwidth.at(bufn) << " " - << "BUF" << bufn+1 << "HEIGHT=" << this->frame.bufheight.at(bufn) << " " - << "BUF" << bufn+1 << "PIXELS=" << this->frame.bufpixels.at(bufn) << " " - << "BUF" << bufn+1 << "LINES=" << this->frame.buflines.at(bufn) << " " - << "BUF" << bufn+1 << "RAWBLOCKS=" << this->frame.bufrawblocks.at(bufn) << " " - << "BUF" << bufn+1 << "RAWLINES=" << this->frame.bufrawlines.at(bufn) << " " - << "BUF" << bufn+1 << "RAWOFFSET=" << this->frame.bufrawoffset.at(bufn) << " " - << "BUF" << bufn+1 << "TIMESTAMP=" << this->frame.buftimestamp.at(bufn) << " " - << "BUF" << bufn+1 << "RETIMESTAMP=" << this->frame.bufretimestamp.at(bufn) << " " - << "BUF" << bufn+1 << "FETIMESTAMP=" << this->frame.buffetimestamp.at(bufn) << " " -// << "BUF" << bufn+1 << "REATIMESTAMP=" << " " -// << "BUF" << bufn+1 << "FEATIMESTAMP=" << " " -// << "BUF" << bufn+1 << "REBTIMESTAMP=" << " " -// << "BUF" << bufn+1 << "FEBTIMESTAMP=" << " " - ; - } - } - catch( std::invalid_argument & ) { - std::cerr << function << "ERROR: invalid buffer number " << bufn+1 << "\n"; - return( ERROR ); - } - catch( std::out_of_range & ) { - std::cerr << function << "ERROR: buffer number " << bufn+1 << " out of range {1:" << this->image.activebufs << "}\n"; - return( ERROR ); - } - catch( ... ) { - std::cerr << function << "unknown error accessing buffer number " << bufn+1 << "\n"; - return( ERROR ); - } + image_data = new char[reqblocks * BLOCK_LEN]; - retstring = framestr.str(); - retstring = retstring.erase(retstring.find_last_not_of(" ")+1); // remove trailing space added because of for loop - - return( NO_ERROR ); - } - /**************** Interface::frame_report *****8*****************************/ - - - /**************** Interface:fetch_data **************************************/ - /** - * @fn fetch_data - * @brief - * @param cmd, incoming command string - * @return ERROR or NO_ERROR - * - */ - long Interface::fetch_data(std::string ref, 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=NULL; - - if ( cmd.length() != 21 ) { // must be "FETCHxxxxxxxxyyyyyyyy", 21 chars - std::cerr << function << "ERROR: expecting form FETCHxxxxxxxxyyyyyyyy but got \"" << cmd << "\"\n"; - return( ERROR ); - } + std::srand(time(NULL)); + for (unsigned int i = 0; i < (reqblocks * BLOCK_LEN) / 2; i += 10) { + image_data[i] = rand() % 40000 + 30000; + } - try { - std::stringstream hexblocks; - hexblocks << std::hex << "0x" << cmd.substr(13); - hexblocks >> reqblocks; - } - catch( std::invalid_argument & ) { - std::cerr << function << "ERROR: invalid argument parsing " << cmd << "\n"; - return( ERROR ); - } - catch( std::out_of_range & ) { - std::cerr << function << "ERROR: value out of range parsing " << cmd << "\n"; - return( ERROR ); - } - catch( ... ) { - std::cerr << function << "unknown error parsing " << cmd << "\n"; - return( ERROR ); - } + std::string header = "<" + ref + ":"; + totalbyteswritten = 0; + + std::cerr << function << "host requested " << std::dec << reqblocks << " (0x" << std::hex << reqblocks << + ") blocks \n"; + + std::cerr << function << "writing bytes: "; + + for (block = 0; block < reqblocks; block++) { + sock.Write(header); + byteswritten = 0; + do { + int retval = 0; + towrite = BLOCK_LEN - byteswritten; + if ((retval = sock.Write(image_data, towrite)) > 0) { + byteswritten += retval; + totalbyteswritten += retval; + std::cerr << std::dec << std::setw(10) << totalbyteswritten << "\b\b\b\b\b\b\b\b\b\b"; + } + } while (byteswritten < BLOCK_LEN); + } + std::cerr << std::dec << std::setw(10) << totalbyteswritten << " complete\n"; - image_data = new char[reqblocks * BLOCK_LEN]; + delete[] image_data; - std::srand( time( NULL ) ); - for ( unsigned int i=0; i<(reqblocks*BLOCK_LEN)/2; i+=10 ) { - image_data[i] = rand() % 40000 + 30000; + return (NO_ERROR); } - std::string header = "<" + ref + ":"; - totalbyteswritten = 0; + /**************** Interface:fetch_data **************************************/ - std::cerr << function << "host requested " << std::dec << reqblocks << " (0x" << std::hex << reqblocks << ") blocks \n"; - std::cerr << function << "writing bytes: "; + /**************** Interface:wconfig *****************************************/ + /** + * @fn wconfig + * @brief handles the incoming WCONFIG command + * @param buf, incoming command string + * @return ERROR or NO_ERROR + * + * Writes to emulated configuration memory, which is just an STL map + * indexed by line number, so that lookups can be performed later. + * + */ + long Interface::wconfig(std::string buf) { + std::string function = "(Archon::Interface::wconfig) "; + std::string line; + std::string linenumber; - for ( block = 0; block < reqblocks; block++ ) { - sock.Write(header); - byteswritten = 0; - do { - int retval=0; - towrite = BLOCK_LEN - byteswritten; - if ( ( retval = sock.Write(image_data, towrite) ) > 0 ) { - byteswritten += retval; - totalbyteswritten += retval; - std::cerr << std::dec << std::setw(10) << totalbyteswritten << "\b\b\b\b\b\b\b\b\b\b"; + // check for minimum length or absence of an equal sign + // + if ((buf.length() < 14) || // minimum string is "WCONFIGxxxxT=T", 14 chars + (buf.find("=") == std::string::npos)) { + // must have equal sign + std::cerr << function << "ERROR: expecting form WCONFIGxxxxT=T but got \"" << buf << "\"\n"; + return (ERROR); } - } while ( byteswritten < BLOCK_LEN ); - } - std::cerr << std::dec << std::setw(10) << totalbyteswritten << " complete\n"; - - delete[] image_data; - - return( NO_ERROR ); - } - /**************** Interface:fetch_data **************************************/ - - - /**************** Interface:wconfig *****************************************/ - /** - * @fn wconfig - * @brief handles the incoming WCONFIG command - * @param buf, incoming command string - * @return ERROR or NO_ERROR - * - * Writes to emulated configuration memory, which is just an STL map - * indexed by line number, so that lookups can be performed later. - * - */ - long Interface::wconfig(std::string buf) { - std::string function = "(Archon::Interface::wconfig) "; - std::string line; - std::string linenumber; - - // check for minimum length or absence of an equal sign - // - if ( ( buf.length() < 14 ) || // minimum string is "WCONFIGxxxxT=T", 14 chars - ( buf.find("=") == std::string::npos ) ) { // must have equal sign - std::cerr << function << "ERROR: expecting form WCONFIGxxxxT=T but got \"" << buf << "\"\n"; - return( ERROR ); - } - try { - linenumber = buf.substr(7, 4); // extract the line number (keep as string) - line = buf.substr(11); // everything else - - // If this is a PARAMETERn=ParameterName=value KEY=VALUE pair... - // - if ( ( line.compare(0, 11, "PARAMETERS=" ) != 0 ) && // not the PARAMETERS=xx line - ( line.compare(0, 9, "PARAMETER" ) == 0 ) ) { // but must start with "PARAMETER" - - std::vector tokens; - Tokenize( line, tokens, "=" ); // separate into PARAMETERn, ParameterName, value tokens + try { + linenumber = buf.substr(7, 4); // extract the line number (keep as string) + line = buf.substr(11); // everything else + + // If this is a PARAMETERn=ParameterName=value KEY=VALUE pair... + // + if ((line.compare(0, 11, "PARAMETERS=") != 0) && // not the PARAMETERS=xx line + (line.compare(0, 9, "PARAMETER") == 0)) { + // but must start with "PARAMETER" + + std::vector tokens; + Tokenize(line, tokens, "="); // separate into PARAMETERn, ParameterName, value tokens + + if (tokens.size() != 3) { + // malformed line + std::cerr << function << "ERROR: expected 3 tokens but got \"" << line << "\"\n"; + return (ERROR); + } + + std::stringstream paramnamevalue; + paramnamevalue << tokens.at(1) << "=" << tokens.at(2); // reassmeble ParameterName=value string + + // build an STL map "configmap" indexed on linenumber because that's what Archon uses + // + this->configmap[linenumber].line = linenumber; // configuration line number + this->configmap[linenumber].key = tokens.at(0); // configuration key + this->configmap[linenumber].value = paramnamevalue.str(); // configuration value for PARAMETERn + + // std::cerr << function << buf << " <<< stored parameter in configmap[ " << linenumber + // << " ].key=" << this->configmap[ linenumber ].key + // << " .value=" << this->configmap[ linenumber ].value + // << "\n"; + + // build an STL map "parammap" indexed on ParameterName so that FASTPREPPARAM can lookup by the actual name + // + this->parammap[tokens.at(1)].key = tokens.at(0); // PARAMETERn + this->parammap[tokens.at(1)].name = tokens.at(1); // ParameterName + this->parammap[tokens.at(1)].value = tokens.at(2); // value + this->parammap[tokens.at(1)].line = linenumber; // line number + } - if ( tokens.size() != 3 ) { // malformed line - std::cerr << function << "ERROR: expected 3 tokens but got \"" << line << "\"\n"; - return( ERROR ); + // ...otherwise, for all other KEY=VALUE pairs, there is only the value and line number + // to be indexed by the key. Some lines may be equal to blank, e.g. "CONSTANTx=" so that + // only one token is made + // + else { + std::vector tokens; + std::string key, value; + // Tokenize will return a size=1 even if there are no delimiters, + // so work around this by first checking for delimiters + // before calling Tokenize. + // + if (line.find_first_of("=", 0) == std::string::npos) { + return (ERROR); + } + Tokenize(line, tokens, "="); // separate into KEY, VALUE tokens + if (tokens.size() == 0) { + return (ERROR); // nothing to do here if no tokens (ie no "=") + } + if (tokens.size() > 0) { + // at least one token is the key + key = tokens.at(0); // KEY + value = ""; // VALUE can be empty (e.g. labels not required) + this->configmap[linenumber].line = linenumber; + this->configmap[linenumber].key = key; + this->configmap[linenumber].value = value; + } + if (tokens.size() > 1) { + // if a second token then that's the value + value = tokens.at(1); // VALUE (there is a second token) + this->configmap[linenumber].value = tokens.at(1); + } + + // Some keywords in this category are used to assign certain class variables + // + if (key == "BIGBUF") { + this->bigbuf = (std::stoi(value) == 1 ? true : false); + this->image.activebufs = (this->bigbuf ? 2 : 3); + this->frame.bufbase.at(0) = 0xA0000000; + if (this->bigbuf) { + this->frame.bufbase.at(1) = 0xD0000000; + } else { + this->frame.bufbase.at(1) = 0xC0000000; + this->frame.bufbase.at(2) = 0xE0000000; + } + } else if (key == "TAPLINES") { + this->image.taplines = std::stoi(value); + } else if (key == "PIXELCOUNT") { + this->image.pixelcount = std::stoi(value); + } else if (key == "LINECOUNT") { + this->image.linecount = std::stoi(value); + } + } // end else + } catch (std::invalid_argument &) { + std::cerr << function << "ERROR: invalid argument parsing line: " << line << "\n"; + return (ERROR); + } + catch (std::out_of_range &) { + std::cerr << function << "ERROR: value out of range parsing line: " << line << "\n"; + return (ERROR); + } + catch (...) { + std::cerr << function << "unknown error parsing line: " << line << "\n"; + return (ERROR); } - std::stringstream paramnamevalue; - paramnamevalue << tokens.at(1) << "=" << tokens.at(2); // reassmeble ParameterName=value string + return (NO_ERROR); + } - // build an STL map "configmap" indexed on linenumber because that's what Archon uses - // - this->configmap[ linenumber ].line = linenumber; // configuration line number - this->configmap[ linenumber ].key = tokens.at(0); // configuration key - this->configmap[ linenumber ].value = paramnamevalue.str(); // configuration value for PARAMETERn + /**************** Interface:wconfig *****************************************/ -// std::cerr << function << buf << " <<< stored parameter in configmap[ " << linenumber -// << " ].key=" << this->configmap[ linenumber ].key -// << " .value=" << this->configmap[ linenumber ].value -// << "\n"; - // build an STL map "parammap" indexed on ParameterName so that FASTPREPPARAM can lookup by the actual name - // - this->parammap[ tokens.at(1) ].key = tokens.at(0); // PARAMETERn - this->parammap[ tokens.at(1) ].name = tokens.at(1); // ParameterName - this->parammap[ tokens.at(1) ].value = tokens.at(2); // value - this->parammap[ tokens.at(1) ].line = linenumber; // line number - - } - - // ...otherwise, for all other KEY=VALUE pairs, there is only the value and line number - // to be indexed by the key. Some lines may be equal to blank, e.g. "CONSTANTx=" so that - // only one token is made - // - else { - std::vector tokens; - std::string key, value; - // Tokenize will return a size=1 even if there are no delimiters, - // so work around this by first checking for delimiters - // before calling Tokenize. - // - if (line.find_first_of("=", 0) == std::string::npos) { - return( ERROR ); + /**************** Interface::rconfig ****************************************/ + /** + * @fn rconfig + * @brief handles the incoming RCONFIG command + * @param buf, incoming command string + * @param &retstring, reference to string for return values + * @return ERROR or NO_ERROR + * + */ + long Interface::rconfig(std::string buf, std::string &retstring) { + std::string function = "(Archon::Interface::rconfig) "; + std::string linenumber; + + if (buf.length() != 11) { + // check for minimum length "RCONFIGxxxx", 11 chars + return (ERROR); } - Tokenize(line, tokens, "="); // separate into KEY, VALUE tokens - if (tokens.size() == 0) { - return( ERROR ); // nothing to do here if no tokens (ie no "=") + + try { + std::stringstream retss; + linenumber = buf.substr(7, 4); // extract the line number + if (this->configmap.find(linenumber) != this->configmap.end()) { + retss << this->configmap[linenumber].key; + retss << "="; + retss << this->configmap[linenumber].value; + retstring = retss.str(); + } else { + std::cerr << function << "ERROR: line " << linenumber << " not found in configuration memory\n"; + return (ERROR); + } + } catch (std::invalid_argument &) { + std::cerr << function << "ERROR: invalid argument\n"; + return (ERROR); } - if (tokens.size() > 0 ) { // at least one token is the key - key = tokens.at(0); // KEY - value = ""; // VALUE can be empty (e.g. labels not required) - this->configmap[ linenumber ].line = linenumber; - this->configmap[ linenumber ].key = key; - this->configmap[ linenumber ].value = value; + catch (std::out_of_range &) { + std::cerr << function << "ERROR: value out of range\n"; + return (ERROR); } - if (tokens.size() > 1 ) { // if a second token then that's the value - value = tokens.at(1); // VALUE (there is a second token) - this->configmap[ linenumber ].value = tokens.at(1); + catch (...) { + std::cerr << function << "unknown error\n"; + return (ERROR); } + return (NO_ERROR); + } - // Some keywords in this category are used to assign certain class variables - // - if ( key == "BIGBUF" ) { - this->bigbuf = ( std::stoi(value)==1 ? true : false ); - this->image.activebufs = ( this->bigbuf ? 2 : 3 ); - this->frame.bufbase.at(0) = 0xA0000000; - if ( this->bigbuf ) { - this->frame.bufbase.at(1) = 0xD0000000; - } - else { - this->frame.bufbase.at(1) = 0xC0000000; - this->frame.bufbase.at(2) = 0xE0000000; - } - } + /**************** Interface::rconfig ****************************************/ - else if ( key == "TAPLINES" ) { - this->image.taplines = std::stoi(value); - } - else if ( key == "PIXELCOUNT" ) { - this->image.pixelcount = std::stoi(value); - } + /**************** Interface::write_parameter ********************************/ + /** + * @fn write_parameter + * @brief writes parameter to emulated configuration memory + * @param buf + * @return ERROR or NO_ERROR + * + * "buf" contains the space-delimited string " " + * where is to be assigned to the parameter . + * + */ + long Interface::write_parameter(std::string buf) { + std::string function = "(Archon::Interface::write_parameter) "; + std::vector tokens; + std::string key = ""; + std::string value = ""; + std::string line = ""; + + Tokenize(buf, tokens, " "); - else if ( key == "LINECOUNT" ) { - this->image.linecount = std::stoi(value); + // must have two tokens only, + // + if (tokens.size() != 2) { + std::cerr << function << "ERROR: expected but received " << buf << "\n"; + return (ERROR); } - } // end else - } - catch( std::invalid_argument & ) { - std::cerr << function << "ERROR: invalid argument parsing line: " << line << "\n"; - return( ERROR ); - } - catch( std::out_of_range & ) { - std::cerr << function << "ERROR: value out of range parsing line: " << line << "\n"; - return( ERROR ); - } - catch( ... ) { - std::cerr << function << "unknown error parsing line: " << line << "\n"; - return( ERROR ); - } + // assign the key ("paramname") and value ("value") from the two tokens + // + try { + key = tokens.at(0); + value = tokens.at(1); + + // When an exposure is started there will be a command to write + // a non-zero number to the exposeparam. Catch that here and + // start an exposure thread. + // + if ((key == this->exposeparam) && (std::stoi(value) > 0)) { + int numexpose = std::stoi(value); + // spawn a thread to mimic readout and create the data + std::thread(dothread_expose, std::ref(this->frame), std::ref(this->image), numexpose).detach(); + } - return( NO_ERROR ); - } - /**************** Interface:wconfig *****************************************/ - - - /**************** Interface::rconfig ****************************************/ - /** - * @fn rconfig - * @brief handles the incoming RCONFIG command - * @param buf, incoming command string - * @param &retstring, reference to string for return values - * @return ERROR or NO_ERROR - * - */ - long Interface::rconfig(std::string buf, std::string &retstring) { - std::string function = "(Archon::Interface::rconfig) "; - std::string linenumber; - - if ( buf.length() != 11 ) { // check for minimum length "RCONFIGxxxx", 11 chars - return( ERROR ); - } + // Catch the write to exptime and save the value in a class variable. + // + if (key == "exptime") { + this->image.exptime = std::stoi(value); + std::cerr << function << "exptime = " << this->image.exptime << (this->image.exposure_factor == 1 + ? " sec" + : " msec") << "\n"; + } - try { - std::stringstream retss; - linenumber = buf.substr(7, 4); // extract the line number - if ( this->configmap.find( linenumber) != this->configmap.end() ) { - retss << this->configmap[ linenumber ].key; - retss << "="; - retss << this->configmap[ linenumber ].value; - retstring = retss.str(); - } - else { - std::cerr << function << "ERROR: line " << linenumber << " not found in configuration memory\n"; - return( ERROR ); - } - } - catch( std::invalid_argument & ) { - std::cerr << function << "ERROR: invalid argument\n"; - return( ERROR ); - } - catch( std::out_of_range & ) { - std::cerr << function << "ERROR: value out of range\n"; - return( ERROR ); - } - catch( ... ) { - std::cerr << function << "unknown error\n"; - return( ERROR ); - } - return( NO_ERROR ); - } - /**************** Interface::rconfig ****************************************/ - - - /**************** Interface::write_parameter ********************************/ - /** - * @fn write_parameter - * @brief writes parameter to emulated configuration memory - * @param buf - * @return ERROR or NO_ERROR - * - * "buf" contains the space-delimited string " " - * where is to be assigned to the parameter . - * - */ - long Interface::write_parameter(std::string buf) { - std::string function = "(Archon::Interface::write_parameter) "; - std::vector tokens; - std::string key=""; - std::string value=""; - std::string line=""; - - Tokenize( buf, tokens, " " ); - - // must have two tokens only, - // - if ( tokens.size() != 2 ) { - std::cerr << function << "ERROR: expected but received " << buf << "\n"; - return( ERROR ); - } + // Catch the write to longexposure which affects the class variable exposure_factor + // exposure_factor=1000 when longexposure=0 (msec) + // exposure_factor=1 when longexposure=1 (sec) + // + if (key == "longexposure") { + int longexposure = std::stoi(value); + this->image.exposure_factor = (longexposure == 1 ? 1 : 1000); + std::cerr << function << "exptime = " << this->image.exptime << (this->image.exposure_factor == 1 + ? " sec" + : " msec") << "\n"; + } + } catch (std::out_of_range &) { + std::cerr << function << "ERROR: token value out of range, unable to extract key, value pair\n"; + return (ERROR); + } + catch (...) { + std::cerr << function << "unknown error extracting key, value pair\n"; + return (ERROR); + } - // assign the key ("paramname") and value ("value") from the two tokens - // - try { - key = tokens.at(0); - value = tokens.at(1); - - // When an exposure is started there will be a command to write - // a non-zero number to the exposeparam. Catch that here and - // start an exposure thread. - // - if ( ( key == this->exposeparam ) && ( std::stoi( value ) > 0 ) ) { - int numexpose = std::stoi( value ); - // spawn a thread to mimic readout and create the data - std::thread(dothread_expose, std::ref(this->frame), std::ref(this->image), numexpose).detach(); - } - - // Catch the write to exptime and save the value in a class variable. - // - if ( key == "exptime" ) { - this->image.exptime = std::stoi( value ); - std::cerr << function << "exptime = " << this->image.exptime << ( this->image.exposure_factor == 1 ? " sec" : " msec" ) << "\n"; - } - - // Catch the write to longexposure which affects the class variable exposure_factor - // exposure_factor=1000 when longexposure=0 (msec) - // exposure_factor=1 when longexposure=1 (sec) - // - if ( key == "longexposure" ) { - int longexposure = std::stoi( value ); - this->image.exposure_factor = ( longexposure == 1 ? 1 : 1000 ); - std::cerr << function << "exptime = " << this->image.exptime << ( this->image.exposure_factor == 1 ? " sec" : " msec" ) << "\n"; - } - } - catch( std::out_of_range & ) { - std::cerr << function << "ERROR: token value out of range, unable to extract key, value pair\n"; - return( ERROR ); - } - catch( ... ) { - std::cerr << function << "unknown error extracting key, value pair\n"; - return( ERROR ); - } + if (key.empty() || value.empty()) { + // should be impossible + std::cerr << function << "ERROR: key or value cannot be empty\n"; + return (ERROR); + } else - if ( key.empty() || value.empty() ) { // should be impossible - std::cerr << function << "ERROR: key or value cannot be empty\n"; - return( ERROR ); - } + // Locate the parameter name ("key") in the parammap + // in order to get the line number. + // + if (this->parammap.find(key) == this->parammap.end()) { + std::cerr << function << "ERROR: " << key << " not found in parammap\n"; + return (ERROR); + } - else + // Assign the new value to the configmap (this is the "configuration memory"). + // + 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?? + } - // Locate the parameter name ("key") in the parammap - // in order to get the line number. - // - if ( this->parammap.find( key ) == this->parammap.end() ) { - std::cerr << function << "ERROR: " << key << " not found in parammap\n"; - return( ERROR ); + return (NO_ERROR); } - // Assign the new value to the configmap (this is the "configuration memory"). - // - 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?? - } + /**************** Interface::write_parameter ********************************/ - return( NO_ERROR ); - } - /**************** Interface::write_parameter ********************************/ + /**************** Interface::dothread_expose ********************************/ + /** + * @fn dothread_expose + * @brief + * @param numexpose + * @return none + * + */ + void Interface::dothread_expose(frame_data_t &frame, image_t &image, int numexpose) { + std::string function = "(Archon::Interface::dothread_expose) "; - /**************** Interface::dothread_expose ********************************/ - /** - * @fn dothread_expose - * @brief - * @param numexpose - * @return none - * - */ - void Interface::dothread_expose(frame_data_t &frame, image_t &image, int numexpose) { - std::string function = "(Archon::Interface::dothread_expose) "; + // should be impossible + // + if (numexpose == 0) { + std::cerr << function << "ERROR: need non-zero number of exposures\n"; + return; + } - // should be impossible - // - if ( numexpose == 0 ) { - std::cerr << function << "ERROR: need non-zero number of exposures\n"; - return; - } + for (int num = 0; num < numexpose; num++) { + // emulate an exposure delay + // + if (image.exptime > 0) { + double time_now = get_clock_time(); // in seconds + double time_start = time_now; + double time_end = time_start + image.exptime / image.exposure_factor; + double progress = 0; + double elapsed = 0; + std::cerr << function << "exposure progress: "; + while ((time_now - (image.exptime / image.exposure_factor + time_start) < 0)) { + usleep(1000); + time_now = get_clock_time(); + elapsed = 1000. * (get_clock_time() - time_start); + progress = elapsed / (1000. * (time_end - time_start)); + if (progress < 0 || progress > 1) progress = 1; + std::cerr << std::setw(3) << (int) (100 * progress) << "\%\b\b\b\b"; + } + std::cerr << "100\%\n"; + } - for ( int num = 0; num < numexpose; num++ ) { - - // emulate an exposure delay - // - if ( image.exptime > 0 ) { - double time_now = get_clock_time(); // in seconds - double time_start = time_now; - double time_end = time_start + image.exptime/image.exposure_factor; - double progress = 0; - double elapsed = 0; - std::cerr << function << "exposure progress: "; - while ( ( time_now - (image.exptime/image.exposure_factor + time_start) < 0 ) ) { - usleep( 1000 ); - time_now = get_clock_time(); - elapsed = 1000. * (get_clock_time() - time_start); - progress = elapsed / (1000.*(time_end - time_start)); - if ( progress < 0 || progress > 1 ) progress = 1; - std::cerr << std::setw(3) << (int)(100*progress) << "\%\b\b\b\b"; - } - std::cerr << "100\%\n"; - } - - // frame.frame is the 1-based frame buffer number to write to now - // and frame.index is the 0-based index of this frame buffer number - // increment each time - // cycle back to 1 if greater than the number of active buffers - // - frame.frame++; - if ( frame.frame > image.activebufs ) frame.frame = 1; - frame.index = frame.frame - 1; - - // compute line time as 90% of the read time, rounded down to nearest 100 usec - // - int linetime = (int)std::floor( 10 * image.readtime / image.linecount ); // line time in 10 msec - linetime *= 90; // line time in usec (here's the 90%) - - // initialize random seed for data - // - std::srand( time( NULL ) ); - - try { - frame.bufpixels.at( frame.index ) = 0; - frame.buflines.at( frame.index ) = 0; - frame.bufcomplete.at( frame.index ) = 0; - - int i=0; - - std::cerr << function << "readout line: "; - for ( frame.buflines.at(frame.index) = 0; frame.buflines.at(frame.index) < image.linecount; frame.buflines.at(frame.index)++ ) { - for ( frame.bufpixels.at(frame.index)= 0; frame.bufpixels.at(frame.index) < image.pixelcount; frame.bufpixels.at(frame.index)++ ) { - for ( int tap = 0; tap < image.taplines; tap++ ) { -// frame.buffer.at( i ) = rand() % 40000 + 30000; // random number between {30k:40k} - i++; + // frame.frame is the 1-based frame buffer number to write to now + // and frame.index is the 0-based index of this frame buffer number + // increment each time + // cycle back to 1 if greater than the number of active buffers + // + frame.frame++; + if (frame.frame > image.activebufs) frame.frame = 1; + frame.index = frame.frame - 1; + + // compute line time as 90% of the read time, rounded down to nearest 100 usec + // + int linetime = (int) std::floor(10 * image.readtime / image.linecount); // line time in 10 msec + linetime *= 90; // line time in usec (here's the 90%) + + // initialize random seed for data + // + std::srand(time(NULL)); + + try { + frame.bufpixels.at(frame.index) = 0; + frame.buflines.at(frame.index) = 0; + frame.bufcomplete.at(frame.index) = 0; + + int i = 0; + + std::cerr << function << "readout line: "; + for (frame.buflines.at(frame.index) = 0; frame.buflines.at(frame.index) < image.linecount; frame. + buflines.at(frame.index)++) { + for (frame.bufpixels.at(frame.index) = 0; frame.bufpixels.at(frame.index) < image.pixelcount; frame. + bufpixels.at(frame.index)++) { + for (int tap = 0; tap < image.taplines; tap++) { + // frame.buffer.at( i ) = rand() % 40000 + 30000; // random number between {30k:40k} + i++; + } + // frame.bufpixels.at( frame.index )++; + } + // frame.buflines.at( frame.index )++; + std::cerr << std::dec << std::setw(6) << frame.buflines.at(frame.index) << "\b\b\b\b\b\b"; + usleep(linetime); + } + std::cerr << std::dec << std::setw(6) << frame.buflines.at(frame.index) << " complete\n"; + frame.bufcomplete.at(frame.index) = 1; + image.framen++; + frame.bufframen.at(frame.index) = image.framen; + } catch (std::out_of_range &) { + std::cerr << function << "ERROR: frame.index=" << frame.index << " out of range\n"; + return; + } + catch (...) { + std::cerr << function << "unknown error using frame index " << frame.index << "\n"; + return; } -// frame.bufpixels.at( frame.index )++; - } -// frame.buflines.at( frame.index )++; - std::cerr << std::dec << std::setw(6) << frame.buflines.at(frame.index) << "\b\b\b\b\b\b"; - usleep( linetime ); } - std::cerr << std::dec << std::setw(6) << frame.buflines.at(frame.index) << " complete\n"; - frame.bufcomplete.at( frame.index ) = 1; - image.framen++; - frame.bufframen.at( frame.index ) = image.framen; - } - catch( std::out_of_range & ) { - std::cerr << function << "ERROR: frame.index=" << frame.index << " out of range\n"; - return; - } - catch( ... ) { - std::cerr << function << "unknown error using frame index " << frame.index << "\n"; - return; - } } - } - /**************** Interface::dothread_expose ********************************/ + /**************** Interface::dothread_expose ********************************/ } diff --git a/emulator/emulator-archon.h b/emulator/emulator-archon.h index 6d71eb7f..ce1c0cd1 100644 --- a/emulator/emulator-archon.h +++ b/emulator/emulator-archon.h @@ -25,202 +25,208 @@ #define BLOCK_LEN 1024 //!< Archon block size in bytes namespace Archon { - - // Archon hardware-based constants. - // These shouldn't change unless there is a significant hardware change. - // - const int nbufs = 3; //!< total number of frame buffers //TODO rename to maxnbufs? - const int nmods = 12; //!< number of modules per controller - const int nadchan = 4; //!< number of channels per ADC module - - class Interface { + // Archon hardware-based constants. + // These shouldn't change unless there is a significant hardware change. + // + const int nbufs = 3; //!< total number of frame buffers //TODO rename to maxnbufs? + const int nmods = 12; //!< number of modules per controller + const int nadchan = 4; //!< number of channels per ADC module + + class Interface { private: - unsigned long int start_timer, finish_timer; //!< Archon internal timer, start and end of exposure + unsigned long int start_timer, finish_timer; //!< Archon internal timer, start and end of exposure public: - Interface(); - ~Interface(); - - // Class Objects - // - Config config; - - std::string systemfile; - - 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 - - struct image_t { - uint32_t framen; - int activebufs; //!< number of active frame buffers - int taplines; //!< from "TAPLINES=" in ACF file - int linecount; //!< from "LINECOUNT=" in ACF file - int pixelcount; //!< from "PIXELCOUNT=" in ACF file - int readtime; //!< from "READOUT_TIME=" in configuration file - int exptime; //!< requested exposure time in msec from WCONFIG - int exposure_factor; //!< multiplier for exptime relative to 1 sec (=1 for sec, =1000 for msec, etc.) - } image; - - /** - * @var struct frame_data_t frame - * @details structure to contain Archon results from "FRAME" command - */ - struct frame_data_t { - int index; // index of newest buffer data - int frame; // index of newest buffer data - std::string timer; // current hex 64 bit internal timer - int rbuf; // current buffer locked for reading - int wbuf; // current buffer locked for writing - std::vector bufsample; // sample mode 0=16 bit, 1=32 bit - std::vector bufcomplete; // buffer complete, 1=ready to read - std::vector bufmode; // buffer mode: 0=top 1=bottom 2=split - std::vector bufbase; // buffer base address for fetching - std::vector bufframen; // buffer frame number - std::vector bufwidth; // buffer width - std::vector bufheight; // buffer height - std::vector bufpixels; // buffer pixel progress - std::vector buflines; // buffer line progress - std::vector bufrawblocks; // buffer raw blocks per line - std::vector bufrawlines; // buffer raw lines - std::vector bufrawoffset; // buffer raw offset - 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 - } frame; - - // Functions - // - long configure_controller(); //!< get configuration parameters from .cfg file - long system_report(std::string buf, std::string &retstring); - long status_report(std::string &retstring); - long timer_report(std::string &retstring); - unsigned long get_timer(); - long frame_report(std::string &retstring); - long fetch_data(std::string ref, std::string cmd, Network::TcpSocket &sock); //!< - long wconfig(std::string buf); //!< - long rconfig(std::string buf, std::string &retstring); - long write_parameter(std::string buf); - static void dothread_expose(frame_data_t &frame, image_t &image, int numexpose); - -// TODO ***** below here need to check what's needed ********************************************************** - - int msgref; //!< Archon message reference identifier, matches reply to command - bool abort; - std::vector gain; //!< digital CDS gain (from TAPLINE definition) - std::vector offset; //!< digital CDS offset (from TAPLINE definition) - bool modeselected; //!< true if a valid mode has been selected, false otherwise - bool firmwareloaded; //!< true if firmware is loaded, false otherwise - - float heater_target_min; //!< minimum heater target temperature - float heater_target_max; //!< maximum heater target temperature - - char *image_data; //!< image data buffer - uint32_t image_data_bytes; //!< requested number of bytes allocated for image_data rounded up to block size - uint32_t image_data_allocated; //!< allocated number of bytes for image_data - - std::atomic archon_busy; //!< indicates a thread is accessing Archon - std::mutex archon_mutex; //!< protects Archon from being accessed by multiple threads, - //!< use in conjunction with archon_busy flag - - /** - * @var struct geometry_t geometry[] - * @details structure of geometry which is unique to each observing mode - */ - struct geometry_t { - int amps[2]; // number of amplifiers per detector for each axis, set in set_camera_mode - int num_detect; // number of detectors, set in set_camera_mode - int linecount; // number of lines per tap - int pixelcount; // number of pixels per tap - }; - - /** - * @var struct tapinfo_t tapinfo[] - * @details structure of tapinfo which is unique to each observing mode - */ - struct tapinfo_t { - int num_taps; - int tap[16]; - float gain[16]; - float offset[16]; - std::string readoutdir[16]; - }; - - /** @var vector modtype - * @details stores the type of each module from the SYSTEM command - */ - std::vector modtype; - - /** @var vector modversion - * @details stores the version of each module from the SYSTEM command - */ - std::vector modversion; - - std::string backplaneversion; - - /** @var int lastframe - * @details the last (I.E. previous) frame number acquired - */ - int lastframe; - - /** - * rawinfo_t is a struct which contains variables specific to raw data functions - */ - struct rawinfo_t { - int adchan; // selected A/D channels - int rawsamples; // number of raw samples per line - int rawlines; // number of raw lines - int iteration; // iteration number - int iterations; // number of iterations - } rawinfo; - - /** - * config_line_t is a struct for the configfile key=value map, used to - * store the configuration line and its associated line number. - */ - typedef struct { - std::string line; // the line number, used for updating Archon - std::string key; // the part before the '=' - std::string value; // the part after the '=' - } config_line_t; - - /** - * param_line_t is a struct for the PARAMETER name key=value map, used to - * store parameters where the format is PARAMETERn=parametername=value - */ - typedef struct { - std::string key; // the PARAMETERn part - std::string name; // the parametername part - std::string value; // the value part - std::string line; // the line number - } param_line_t; - - typedef std::map cfg_map_t; - typedef std::map param_map_t; - - cfg_map_t configmap; - param_map_t parammap; - - /** - * generic key=value STL map for Archon commands - */ - typedef std::map map_t; - - /** - * \var map_t systemmap - * \details key=value map for Archon SYSTEM command - */ - map_t systemmap; - - /** - * \var map_t statusmap - * \details key=value map for Archon STATUS command - */ - map_t statusmap; - - }; - + Interface(); + + ~Interface(); + + // Class Objects + // + Config config; + + std::string systemfile; + + 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 + + struct image_t { + uint32_t framen; + int activebufs; //!< number of active frame buffers + int taplines; //!< from "TAPLINES=" in ACF file + int linecount; //!< from "LINECOUNT=" in ACF file + int pixelcount; //!< from "PIXELCOUNT=" in ACF file + int readtime; //!< from "READOUT_TIME=" in configuration file + int exptime; //!< requested exposure time in msec from WCONFIG + int exposure_factor; //!< multiplier for exptime relative to 1 sec (=1 for sec, =1000 for msec, etc.) + } image; + + /** + * @var struct frame_data_t frame + * @details structure to contain Archon results from "FRAME" command + */ + struct frame_data_t { + int index; // index of newest buffer data + int frame; // index of newest buffer data + std::string timer; // current hex 64 bit internal timer + int rbuf; // current buffer locked for reading + int wbuf; // current buffer locked for writing + std::vector bufsample; // sample mode 0=16 bit, 1=32 bit + std::vector bufcomplete; // buffer complete, 1=ready to read + std::vector bufmode; // buffer mode: 0=top 1=bottom 2=split + std::vector bufbase; // buffer base address for fetching + std::vector bufframen; // buffer frame number + std::vector bufwidth; // buffer width + std::vector bufheight; // buffer height + std::vector bufpixels; // buffer pixel progress + std::vector buflines; // buffer line progress + std::vector bufrawblocks; // buffer raw blocks per line + std::vector bufrawlines; // buffer raw lines + std::vector bufrawoffset; // buffer raw offset + 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 + } frame; + + // Functions + // + long configure_controller(); //!< get configuration parameters from .cfg file + long system_report(std::string buf, std::string &retstring); + + long status_report(std::string &retstring); + + long timer_report(std::string &retstring); + + unsigned long get_timer(); + + long frame_report(std::string &retstring); + + long fetch_data(std::string ref, std::string cmd, Network::TcpSocket &sock); //!< + long wconfig(std::string buf); //!< + long rconfig(std::string buf, std::string &retstring); + + long write_parameter(std::string buf); + + static void dothread_expose(frame_data_t &frame, image_t &image, int numexpose); + + // TODO ***** below here need to check what's needed ********************************************************** + + int msgref; //!< Archon message reference identifier, matches reply to command + bool abort; + std::vector gain; //!< digital CDS gain (from TAPLINE definition) + std::vector offset; //!< digital CDS offset (from TAPLINE definition) + bool modeselected; //!< true if a valid mode has been selected, false otherwise + bool firmwareloaded; //!< true if firmware is loaded, false otherwise + + float heater_target_min; //!< minimum heater target temperature + float heater_target_max; //!< maximum heater target temperature + + char *image_data; //!< image data buffer + uint32_t image_data_bytes; //!< requested number of bytes allocated for image_data rounded up to block size + uint32_t image_data_allocated; //!< allocated number of bytes for image_data + + std::atomic archon_busy; //!< indicates a thread is accessing Archon + std::mutex archon_mutex; + //!< protects Archon from being accessed by multiple threads, + //!< use in conjunction with archon_busy flag + + /** + * @var struct geometry_t geometry[] + * @details structure of geometry which is unique to each observing mode + */ + struct geometry_t { + int amps[2]; // number of amplifiers per detector for each axis, set in set_camera_mode + int num_detect; // number of detectors, set in set_camera_mode + int linecount; // number of lines per tap + int pixelcount; // number of pixels per tap + }; + + /** + * @var struct tapinfo_t tapinfo[] + * @details structure of tapinfo which is unique to each observing mode + */ + struct tapinfo_t { + int num_taps; + int tap[16]; + float gain[16]; + float offset[16]; + std::string readoutdir[16]; + }; + + /** @var vector modtype + * @details stores the type of each module from the SYSTEM command + */ + std::vector modtype; + + /** @var vector modversion + * @details stores the version of each module from the SYSTEM command + */ + std::vector modversion; + + std::string backplaneversion; + + /** @var int lastframe + * @details the last (I.E. previous) frame number acquired + */ + int lastframe; + + /** + * rawinfo_t is a struct which contains variables specific to raw data functions + */ + struct rawinfo_t { + int adchan; // selected A/D channels + int rawsamples; // number of raw samples per line + int rawlines; // number of raw lines + int iteration; // iteration number + int iterations; // number of iterations + } rawinfo; + + /** + * config_line_t is a struct for the configfile key=value map, used to + * store the configuration line and its associated line number. + */ + typedef struct { + std::string line; // the line number, used for updating Archon + std::string key; // the part before the '=' + std::string value; // the part after the '=' + } config_line_t; + + /** + * param_line_t is a struct for the PARAMETER name key=value map, used to + * store parameters where the format is PARAMETERn=parametername=value + */ + typedef struct { + std::string key; // the PARAMETERn part + std::string name; // the parametername part + std::string value; // the value part + std::string line; // the line number + } param_line_t; + + typedef std::map cfg_map_t; + typedef std::map param_map_t; + + cfg_map_t configmap; + param_map_t parammap; + + /** + * generic key=value STL map for Archon commands + */ + typedef std::map map_t; + + /** + * \var map_t systemmap + * \details key=value map for Archon SYSTEM command + */ + map_t systemmap; + + /** + * \var map_t statusmap + * \details key=value map for Archon STATUS command + */ + map_t statusmap; + }; } #endif diff --git a/emulator/emulator-server.cpp b/emulator/emulator-server.cpp index 374520fb..1259f76e 100644 --- a/emulator/emulator-server.cpp +++ b/emulator/emulator-server.cpp @@ -19,31 +19,32 @@ Emulator::Server server; * */ void signal_handler(int signo) { - std::string function = "(Emulator::signal_handler) "; - switch (signo) { - case SIGINT: - std::cerr << function << "received INT\n"; - server.exit_cleanly(); // shutdown the server - break; - case SIGHUP: - std::cerr << function << "caught SIGHUP\n"; - server.configure_controller(); - break; - case SIGPIPE: - std::cerr << function << "caught SIGPIPE\n"; - break; - default: - server.exit_cleanly(); // shutdown the server - break; - } - return; + std::string function = "(Emulator::signal_handler) "; + switch (signo) { + case SIGINT: + std::cerr << function << "received INT\n"; + server.exit_cleanly(); // shutdown the server + break; + case SIGHUP: + std::cerr << function << "caught SIGHUP\n"; + server.configure_controller(); + break; + case SIGPIPE: + std::cerr << function << "caught SIGPIPE\n"; + break; + default: + server.exit_cleanly(); // shutdown the server + break; + } + return; } + /** signal_handler ***********************************************************/ -int main(int argc, char **argv); // main thread (just gets things started) -void block_main(Network::TcpSocket sock); // this thread handles requests on blocking port -void doit(Network::TcpSocket sock); // the worker thread +int main(int argc, char **argv); // main thread (just gets things started) +void block_main(Network::TcpSocket sock); // this thread handles requests on blocking port +void doit(Network::TcpSocket sock); // the worker thread /** main *********************************************************************/ @@ -55,52 +56,54 @@ void doit(Network::TcpSocket sock); // the worker thread * */ int main(int argc, char **argv) { - std::string function = "(Emulator::main) "; - std::stringstream message; - - signal(SIGINT, signal_handler); - signal(SIGPIPE, signal_handler); - signal(SIGHUP, signal_handler); - - // get the configuration file from the command line - // - long ret; - if (argc>1) { - server.config.filename = std::string( argv[1] ); - ret = server.config.read_config(server.config); // read configuration file specified on command line - } - else { - std::cerr << function << "ERROR: no configuration file specified\n"; - server.exit_cleanly(); - } - - std::cerr << function << server.config.n_entries << " lines read from " << server.config.filename << "\n"; - - if (ret==NO_ERROR) ret=server.configure_server(); // get needed values out of read-in configuration file for the server - - if (ret==NO_ERROR) ret=server.configure_controller(); // get needed values out of read-in configuration file for the controller - - if (ret != NO_ERROR) { - std::cerr << function << "ERROR: unable to configure system\n"; - server.exit_cleanly(); - } - - if ( server.emulatorport == -1 ) { - std::cerr << function << "ERROR: emulator server port not configured\n"; - server.exit_cleanly(); - } - - // create a thread for a single listening port - // The TcpSocket object is instantiated with (PORT#, BLOCKING_STATE, POLL_TIMEOUT_MSEC, THREAD_ID#) - // - - Network::TcpSocket s(server.emulatorport, true, -1, 0); // instantiate TcpSocket object - s.Listen(); // create a listening socket - std::thread(block_main, s).detach(); // spawn a thread to handle requests on this socket - - for (;;) pause(); // main thread suspends - return 0; + std::string function = "(Emulator::main) "; + std::stringstream message; + + signal(SIGINT, signal_handler); + signal(SIGPIPE, signal_handler); + signal(SIGHUP, signal_handler); + + // get the configuration file from the command line + // + long ret; + if (argc > 1) { + server.config.filename = std::string(argv[1]); + ret = server.config.read_config(server.config); // read configuration file specified on command line + } else { + std::cerr << function << "ERROR: no configuration file specified\n"; + server.exit_cleanly(); + } + + std::cerr << function << server.config.n_entries << " lines read from " << server.config.filename << "\n"; + + if (ret == NO_ERROR) ret = server.configure_server(); + // get needed values out of read-in configuration file for the server + + if (ret == NO_ERROR) ret = server.configure_controller(); + // get needed values out of read-in configuration file for the controller + + if (ret != NO_ERROR) { + std::cerr << function << "ERROR: unable to configure system\n"; + server.exit_cleanly(); + } + + if (server.emulatorport == -1) { + std::cerr << function << "ERROR: emulator server port not configured\n"; + server.exit_cleanly(); + } + + // create a thread for a single listening port + // The TcpSocket object is instantiated with (PORT#, BLOCKING_STATE, POLL_TIMEOUT_MSEC, THREAD_ID#) + // + + Network::TcpSocket s(server.emulatorport, true, -1, 0); // instantiate TcpSocket object + s.Listen(); // create a listening socket + std::thread(block_main, s).detach(); // spawn a thread to handle requests on this socket + + for (;;) pause(); // main thread suspends + return 0; } + /** main *********************************************************************/ @@ -118,13 +121,14 @@ int main(int argc, char **argv) { * */ void block_main(Network::TcpSocket sock) { - while(1) { - sock.Accept(); - doit(sock); // call function to do the work - sock.Close(); - } - return; + while (1) { + sock.Accept(); + doit(sock); // call function to do the work + sock.Close(); + } + return; } + /** block_main ***************************************************************/ @@ -139,230 +143,178 @@ void block_main(Network::TcpSocket sock) { * */ void doit(Network::TcpSocket sock) { - std::string function = "(Emulator::doit) "; - char buf[BUFSIZE+1]; - long ret; - std::stringstream message; - std::stringstream retstream; - std::string cmd, ref; - std::vector tokens; - - bool connection_open=true; - - while (connection_open) { - memset(buf, '\0', BUFSIZE); // init buffers - - retstream.str(""); // empty the return message stream - - // Wait (poll) connected socket for incoming data... - // - int pollret; - if ( ( pollret=sock.Poll() ) <= 0 ) { - if (pollret==0) { - std::cerr << function << "Poll timeout on thread " << sock.id << "\n"; - } - if (pollret <0) { - std::cerr << function << "Poll error on thread " << sock.id << ": " << strerror(errno) << "\n"; - } - break; // this will close the connection - } - - // Data available, now read from connected socket... - // - if ( (ret=sock.Read(buf, (size_t)BUFSIZE)) <= 0 ) { - if (ret<0) { // could be an actual read error - std::cerr << function << "Read error: " << strerror(errno) << "\n"; - } - break; // Breaking out of the while loop will close the connection. - // This probably means that the client has terminated abruptly, - // having sent FIN but not stuck around long enough - // to accept CLOSE and give the LAST_ACK. - } - - // convert the input buffer into a string and remove any trailing linefeed - // and carriage return - // - std::string sbuf = buf; - sbuf.erase(std::remove(sbuf.begin(), sbuf.end(), '\r' ), sbuf.end()); - sbuf.erase(std::remove(sbuf.begin(), sbuf.end(), '\n' ), sbuf.end()); - - try { - // ignore empty commands or commands that don't begin with '>' - // - if ( ( sbuf.size() == 0 ) || ( (sbuf.size() > 0) && (sbuf.at(0) != '>') ) ) continue; - - // extract the command reference for the checksum - // - if ( sbuf.size() >= 3 ) { - ref = sbuf.substr(1,2); - cmd = sbuf.substr(3); - } - } - catch ( std::runtime_error &e ) { - std::stringstream errstream; errstream << e.what(); - std::cerr << function << "error parsing arguments: " << errstream.str() << "\n"; - } - catch ( ... ) { - std::cerr << function << "unknown error parsing buffer: " << sbuf << "\n"; - } - - /** - * process commands here - * - * Most of these don't have to do anything but they're all listed here for completeness, - * in the order that they appear in the Archon manual. - * - * Archon returns "<" plus the message reference to acknowledge the command, - * or "?" plus reference on error, or nothing when it doesn't undertand something. - * - */ + std::string function = "(Emulator::doit) "; + char buf[BUFSIZE + 1]; + long ret; + std::stringstream message; + std::stringstream retstream; + std::string cmd, ref; + std::vector tokens; + + bool connection_open = true; + + while (connection_open) { + memset(buf, '\0', BUFSIZE); // init buffers + + retstream.str(""); // empty the return message stream + + // Wait (poll) connected socket for incoming data... + // + int pollret; + if ((pollret = sock.Poll()) <= 0) { + if (pollret == 0) { + std::cerr << function << "Poll timeout on thread " << sock.id << "\n"; + } + if (pollret < 0) { + std::cerr << function << "Poll error on thread " << sock.id << ": " << strerror(errno) << "\n"; + } + break; // this will close the connection + } + + // Data available, now read from connected socket... + // + if ((ret = sock.Read(buf, (size_t) BUFSIZE)) <= 0) { + if (ret < 0) { + // could be an actual read error + std::cerr << function << "Read error: " << strerror(errno) << "\n"; + } + break; // Breaking out of the while loop will close the connection. + // This probably means that the client has terminated abruptly, + // having sent FIN but not stuck around long enough + // to accept CLOSE and give the LAST_ACK. + } + + // convert the input buffer into a string and remove any trailing linefeed + // and carriage return + // + std::string sbuf = buf; + sbuf.erase(std::remove(sbuf.begin(), sbuf.end(), '\r'), sbuf.end()); + sbuf.erase(std::remove(sbuf.begin(), sbuf.end(), '\n'), sbuf.end()); + + try { + // ignore empty commands or commands that don't begin with '>' + // + if ((sbuf.size() == 0) || ((sbuf.size() > 0) && (sbuf.at(0) != '>'))) continue; + + // extract the command reference for the checksum + // + if (sbuf.size() >= 3) { + ref = sbuf.substr(1, 2); + cmd = sbuf.substr(3); + } + } catch (std::runtime_error &e) { + std::stringstream errstream; + errstream << e.what(); + std::cerr << function << "error parsing arguments: " << errstream.str() << "\n"; + } + catch (...) { + std::cerr << function << "unknown error parsing buffer: " << sbuf << "\n"; + } + + /** + * process commands here + * + * Most of these don't have to do anything but they're all listed here for completeness, + * in the order that they appear in the Archon manual. + * + * Archon returns "<" plus the message reference to acknowledge the command, + * or "?" plus reference on error, or nothing when it doesn't undertand something. + * + */ #ifdef STA_ARCHON - if (cmd.compare("SYSTEM")==0) { - std::string retstring; - ret = server.system_report( cmd, retstring ); - retstream << ( ret==ERROR ? "?" : "<" ) << ref << retstring; - } - else - if (cmd.compare("STATUS")==0) { - std::string retstring; - ret = server.status_report( retstring ); - retstream << ( ret==ERROR ? "?" : "<" ) << ref << retstring; - } - else - if (cmd.compare("TIMER")==0) { - std::string retstring; - ret = server.timer_report( retstring ); - retstream << ( ret==ERROR ? "?" : "<" ) << ref << "TIMER=" << retstring; - } - else - if (cmd.compare("FRAME")==0) { - std::string retstring; - ret = server.frame_report( retstring ); - retstream << ( ret==ERROR ? "?" : "<" ) << ref << retstring; - } - else - if (cmd.compare("FETCHLOG")==0) { - retstream << "<" << ref << "(null)"; - } - else - if (cmd.compare(0,4,"LOCK")==0) { - retstream << "<" << ref; - } - else - if (cmd.compare(0,5,"FETCH")==0) { - ret = server.fetch_data( ref, cmd, sock ); - retstream.str(""); // FETCH returns no message - } - else - if (cmd.compare(0,7,"WCONFIG")==0) { - ret = server.wconfig( cmd ); - retstream << ( ret==ERROR ? "?" : "<" ) << ref; - } - else - if (cmd.compare(0,7,"RCONFIG")==0) { - std::string retstring; - ret = server.rconfig( cmd, retstring ); - retstream << ( ret==ERROR ? "?" : "<" ) << ref << retstring; - } - else - if (cmd.compare("CLEARCONFIG")==0) { - retstream << "<" << ref; - } - else - if (cmd.compare("APPLYALL")==0) { - retstream << "<" << ref; - } - else - if (cmd.compare("APPLYSYSTEM")==0) { - retstream << "<" << ref; - } - else - if (cmd.compare("POWERON")==0) { - server.poweron = true; - retstream << "<" << ref; - } - else - if (cmd.compare("POWEROFF")==0) { - server.poweron = false; - retstream << "<" << ref; - } - else - if (cmd.compare("LOADTIMING")==0) { - retstream << "<" << ref; - } - else - if (cmd.compare("LOADPARAMS")==0) { - retstream << "<" << ref; - } - else - if (cmd.compare(0,9,"LOADPARAM")==0) { - ret = server.write_parameter( cmd.substr(14) ); - retstream << ( ret==ERROR ? "?" : "<" ) << ref; - } - else - if (cmd.compare(0,9,"PREPPARAM")==0) { -// server.write_parameter( cmd.substr(14) ); - retstream << "<" << ref; - } - else - if (cmd.compare(0,13,"FASTLOADPARAM")==0) { - ret = server.write_parameter( cmd.substr(14) ); - retstream << ( ret==ERROR ? "?" : "<" ) << ref; - } - else - if (cmd.compare(0,13,"FASTPREPPARAM")==0) { -// server.write_parameter( cmd.substr(14) ); - retstream << "<" << ref; - } - else - if (cmd.compare("RESETTIMING")==0) { - retstream << "<" << ref; - } - else - if (cmd.compare("HOLDTIMING")==0) { - retstream << "<" << ref; - } - else - if (cmd.compare("RELEASETIMING")==0) { - retstream << "<" << ref; - } - else - if (cmd.compare(0,8,"APPLYMOD")==0) { - retstream << "<" << ref; - } - else - if (cmd.compare(0,8,"APPLYDIO")==0) { - retstream << "<" << ref; - } - else - if (cmd.compare("APPLYCDS")==0) { - retstream << "<" << ref; - } - else - if (cmd.compare("POLLOFF")==0) { - retstream << "<" << ref; - } - else - if (cmd.compare("POLLON")==0) { - retstream << "<" << ref; - } - else { // Archon simply ignores things it doesn't understand - } + if (cmd.compare("SYSTEM") == 0) { + std::string retstring; + ret = server.system_report(cmd, retstring); + retstream << (ret == ERROR ? "?" : "<") << ref << retstring; + } else if (cmd.compare("STATUS") == 0) { + std::string retstring; + ret = server.status_report(retstring); + retstream << (ret == ERROR ? "?" : "<") << ref << retstring; + } else if (cmd.compare("TIMER") == 0) { + std::string retstring; + ret = server.timer_report(retstring); + retstream << (ret == ERROR ? "?" : "<") << ref << "TIMER=" << retstring; + } else if (cmd.compare("FRAME") == 0) { + std::string retstring; + ret = server.frame_report(retstring); + retstream << (ret == ERROR ? "?" : "<") << ref << retstring; + } else if (cmd.compare("FETCHLOG") == 0) { + retstream << "<" << ref << "(null)"; + } else if (cmd.compare(0, 4, "LOCK") == 0) { + retstream << "<" << ref; + } else if (cmd.compare(0, 5, "FETCH") == 0) { + ret = server.fetch_data(ref, cmd, sock); + retstream.str(""); // FETCH returns no message + } else if (cmd.compare(0, 7, "WCONFIG") == 0) { + ret = server.wconfig(cmd); + retstream << (ret == ERROR ? "?" : "<") << ref; + } else if (cmd.compare(0, 7, "RCONFIG") == 0) { + std::string retstring; + ret = server.rconfig(cmd, retstring); + retstream << (ret == ERROR ? "?" : "<") << ref << retstring; + } else if (cmd.compare("CLEARCONFIG") == 0) { + retstream << "<" << ref; + } else if (cmd.compare("APPLYALL") == 0) { + retstream << "<" << ref; + } else if (cmd.compare("APPLYSYSTEM") == 0) { + retstream << "<" << ref; + } else if (cmd.compare("POWERON") == 0) { + server.poweron = true; + retstream << "<" << ref; + } else if (cmd.compare("POWEROFF") == 0) { + server.poweron = false; + retstream << "<" << ref; + } else if (cmd.compare("LOADTIMING") == 0) { + retstream << "<" << ref; + } else if (cmd.compare("LOADPARAMS") == 0) { + retstream << "<" << ref; + } else if (cmd.compare(0, 9, "LOADPARAM") == 0) { + ret = server.write_parameter(cmd.substr(14)); + retstream << (ret == ERROR ? "?" : "<") << ref; + } else if (cmd.compare(0, 9, "PREPPARAM") == 0) { + // server.write_parameter( cmd.substr(14) ); + retstream << "<" << ref; + } else if (cmd.compare(0, 13, "FASTLOADPARAM") == 0) { + ret = server.write_parameter(cmd.substr(14)); + retstream << (ret == ERROR ? "?" : "<") << ref; + } else if (cmd.compare(0, 13, "FASTPREPPARAM") == 0) { + // server.write_parameter( cmd.substr(14) ); + retstream << "<" << ref; + } else if (cmd.compare("RESETTIMING") == 0) { + retstream << "<" << ref; + } else if (cmd.compare("HOLDTIMING") == 0) { + retstream << "<" << ref; + } else if (cmd.compare("RELEASETIMING") == 0) { + retstream << "<" << ref; + } else if (cmd.compare(0, 8, "APPLYMOD") == 0) { + retstream << "<" << ref; + } else if (cmd.compare(0, 8, "APPLYDIO") == 0) { + retstream << "<" << ref; + } else if (cmd.compare("APPLYCDS") == 0) { + retstream << "<" << ref; + } else if (cmd.compare("POLLOFF") == 0) { + retstream << "<" << ref; + } else if (cmd.compare("POLLON") == 0) { + retstream << "<" << ref; + } else { + // Archon simply ignores things it doesn't understand + } #endif - if ( ! retstream.str().empty() ) { - retstream << "\n"; - if ( (ret=sock.Write(retstream.str())) < 0 ) { - std::cerr << function << "ret=" << ret << " err=" << std::strerror(errno) << "\n"; connection_open=false; - } + if (!retstream.str().empty()) { + retstream << "\n"; + if ((ret = sock.Write(retstream.str())) < 0) { + std::cerr << function << "ret=" << ret << " err=" << std::strerror(errno) << "\n"; + connection_open = false; + } + } } - } - std::cerr << function << "socket connection closed\n"; + std::cerr << function << "socket connection closed\n"; - sock.Close(); - return; + sock.Close(); + return; } -/** doit *********************************************************************/ +/** doit *********************************************************************/ diff --git a/emulator/emulator-server.h b/emulator/emulator-server.h index 445c0a04..ccd93c32 100644 --- a/emulator/emulator-server.h +++ b/emulator/emulator-server.h @@ -35,172 +35,166 @@ #define CONN_TIMEOUT 3000 //emulatorport=-1; - this->nbport=-1; - this->blkport=-1; - this->asyncport=-1; - } - - /** Emulator::~Server ********************************************************/ - /** - * @fn ~Server - * @brief class deconstructor cleans up on exit - */ - ~Server() { - close(this->blocking_socket); - } - /** Emulator::~Server ********************************************************/ - - int emulatorport; //!< emulator port - int nbport; //!< non-blocking port - int blkport; //!< blocking port - int asyncport; //!< asynchronous message port - std::string asyncgroup; //!< asynchronous multicast group - - int nonblocking_socket; - int blocking_socket; - - Network::TcpSocket nonblocking; - - std::mutex conn_mutex; //!< mutex to protect against simultaneous access to Accept() - - /** Emulator::Server::exit_cleanly *******************************************/ - /** - * @fn signal_handler - * @brief handles ctrl-C and exits - * @param int signo - * @return nothing - * - */ - void exit_cleanly(void) { - std::cerr << "server exiting\n"; - exit(EXIT_SUCCESS); - } - /** Emulator::Server::exit_cleanly *******************************************/ - - - /** Emulator::Server::configure_server ***************************************/ - /** - * @fn configure_server - * @brief - * @param none - * @return ERROR or NO_ERROR - * - */ - long configure_server() { - std::string function = "(Emulator::Server::configure_server) "; - std::stringstream message; - int applied=0; - long error; - - // loop through the entries in the configuration file, stored in config class - // - for (int entry=0; entry < this->config.n_entries; entry++) { - - // EMULATOR_PORT - if (config.param[entry].compare(0, 13, "EMULATOR_PORT")==0) { - int port; - try { - port = std::stoi( config.arg[entry] ); - } - catch (std::invalid_argument &) { - std::cerr << function << "ERROR: bad EMULATOR_PORT: unable to convert to integer\n"; - return(ERROR); - } - catch (std::out_of_range &) { - std::cerr << function << "EMULATOR_PORT number out of integer range\n"; - return(ERROR); - } - this->emulatorport = port; - applied++; - } - - // NBPORT - if (config.param[entry].compare(0, 6, "NBPORT")==0) { - int port; - try { - port = std::stoi( config.arg[entry] ); - } - catch (std::invalid_argument &) { - std::cerr << function << "ERROR: bad NBPORT: unable to convert to integer\n"; - return(ERROR); - } - catch (std::out_of_range &) { - std::cerr << function << "NBPORT number out of integer range\n"; - return(ERROR); - } - this->nbport = port; - applied++; - } - - // BLKPORT - if (config.param[entry].compare(0, 7, "BLKPORT")==0) { - int port; - try { - port = std::stoi( config.arg[entry] ); - } - catch (std::invalid_argument &) { - std::cerr << function << "ERROR: bad BLKPORT: unable to convert to integer\n"; - return(ERROR); - } - catch (std::out_of_range &) { - std::cerr << function << "BLKPORT number out of integer range\n"; - return(ERROR); - } - this->blkport = port; - applied++; - } - - // ASYNCPORT - if (config.param[entry].compare(0, 9, "ASYNCPORT")==0) { - int port; - try { - port = std::stoi( config.arg[entry] ); - } - catch (std::invalid_argument &) { - std::cerr << function << "ERROR: bad ASYNCPORT: unable to convert to integer\n"; - return(ERROR); - } - catch (std::out_of_range &) { - std::cerr << function << "ASYNCPORT number out of integer range\n"; - return(ERROR); + Server() { + this->emulatorport = -1; + this->nbport = -1; + this->blkport = -1; + this->asyncport = -1; + } + + /** Emulator::~Server ********************************************************/ + /** + * @fn ~Server + * @brief class deconstructor cleans up on exit + */ + ~Server() { + close(this->blocking_socket); + } + + /** Emulator::~Server ********************************************************/ + + int emulatorport; //!< emulator port + int nbport; //!< non-blocking port + int blkport; //!< blocking port + int asyncport; //!< asynchronous message port + std::string asyncgroup; //!< asynchronous multicast group + + int nonblocking_socket; + int blocking_socket; + + Network::TcpSocket nonblocking; + + std::mutex conn_mutex; //!< mutex to protect against simultaneous access to Accept() + + /** Emulator::Server::exit_cleanly *******************************************/ + /** + * @fn signal_handler + * @brief handles ctrl-C and exits + * @param int signo + * @return nothing + * + */ + void exit_cleanly(void) { + std::cerr << "server exiting\n"; + exit(EXIT_SUCCESS); + } + + /** Emulator::Server::exit_cleanly *******************************************/ + + + /** Emulator::Server::configure_server ***************************************/ + /** + * @fn configure_server + * @brief + * @param none + * @return ERROR or NO_ERROR + * + */ + long configure_server() { + std::string function = "(Emulator::Server::configure_server) "; + std::stringstream message; + int applied = 0; + long error; + + // loop through the entries in the configuration file, stored in config class + // + for (int entry = 0; entry < this->config.n_entries; entry++) { + // EMULATOR_PORT + if (config.param[entry].compare(0, 13, "EMULATOR_PORT") == 0) { + int port; + try { + port = std::stoi(config.arg[entry]); + } catch (std::invalid_argument &) { + std::cerr << function << "ERROR: bad EMULATOR_PORT: unable to convert to integer\n"; + return (ERROR); + } + catch (std::out_of_range &) { + std::cerr << function << "EMULATOR_PORT number out of integer range\n"; + return (ERROR); + } + this->emulatorport = port; + applied++; + } + + // NBPORT + if (config.param[entry].compare(0, 6, "NBPORT") == 0) { + int port; + try { + port = std::stoi(config.arg[entry]); + } catch (std::invalid_argument &) { + std::cerr << function << "ERROR: bad NBPORT: unable to convert to integer\n"; + return (ERROR); + } + catch (std::out_of_range &) { + std::cerr << function << "NBPORT number out of integer range\n"; + return (ERROR); + } + this->nbport = port; + applied++; + } + + // BLKPORT + if (config.param[entry].compare(0, 7, "BLKPORT") == 0) { + int port; + try { + port = std::stoi(config.arg[entry]); + } catch (std::invalid_argument &) { + std::cerr << function << "ERROR: bad BLKPORT: unable to convert to integer\n"; + return (ERROR); + } + catch (std::out_of_range &) { + std::cerr << function << "BLKPORT number out of integer range\n"; + return (ERROR); + } + this->blkport = port; + applied++; + } + + // ASYNCPORT + if (config.param[entry].compare(0, 9, "ASYNCPORT") == 0) { + int port; + try { + port = std::stoi(config.arg[entry]); + } catch (std::invalid_argument &) { + std::cerr << function << "ERROR: bad ASYNCPORT: unable to convert to integer\n"; + return (ERROR); + } + catch (std::out_of_range &) { + std::cerr << function << "ASYNCPORT number out of integer range\n"; + return (ERROR); + } + this->asyncport = port; + applied++; + } + + // ASYNCGROUP + if (config.param[entry].compare(0, 10, "ASYNCGROUP") == 0) { + this->asyncgroup = config.arg[entry]; + applied++; + } + } // end loop through the entries in the configuration file + + message.str(""); + if (applied == 0) { + message << "ERROR: "; + error = ERROR; + } else { + error = NO_ERROR; } - this->asyncport = port; - applied++; - } - - // ASYNCGROUP - if (config.param[entry].compare(0, 10, "ASYNCGROUP")==0) { - this->asyncgroup = config.arg[entry]; - applied++; - } - - } // end loop through the entries in the configuration file - - message.str(""); - if (applied==0) { - message << "ERROR: "; - error = ERROR; - } - else { - error = NO_ERROR; - } - message << "applied " << applied << " configuration lines to server"; - std::cerr << function << message.str() << "\n"; - return error; - } - /** Emulator::Server::configure_server ***************************************/ - - }; // end class Server + message << "applied " << applied << " configuration lines to server"; + std::cerr << function << message.str() << "\n"; + return error; + } + /** Emulator::Server::configure_server ***************************************/ + }; // end class Server } // end namespace Emulator #endif diff --git a/tests/utility_tests.cpp b/tests/utility_tests.cpp index 874a9e58..cb72cf51 100644 --- a/tests/utility_tests.cpp +++ b/tests/utility_tests.cpp @@ -9,4 +9,4 @@ TEST(UtilitiesTest, TimeStampNotLocalTest) { int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); -} \ No newline at end of file +} diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index 9cf81fa2..12ecd10e 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -5,32 +5,32 @@ cmake_minimum_required(VERSION 3.12) set(PROJECT_UTILS_DIR ${PROJECT_BASE_DIR}/utils) -set( CMAKE_CXX_STANDARD 17 ) +set(CMAKE_CXX_STANDARD 17) -add_definitions( -Wall -ansi -O1 -Wno-variadic-macros -std=c++17 -ggdb ) +add_definitions(-Wall -ansi -O1 -Wno-variadic-macros -std=c++17 -ggdb) add_library(utilities STATIC ${PROJECT_UTILS_DIR}/utilities.cpp md5 - ) +) add_library(logentry STATIC ${PROJECT_UTILS_DIR}/logentry.cpp - ) -target_link_libraries( logentry $,,stdc++fs> ) +) +target_link_libraries(logentry $,,stdc++fs>) add_library(network STATIC ${PROJECT_UTILS_DIR}/network.cpp - ) +) -add_library( md5 STATIC +add_library(md5 STATIC ${PROJECT_UTILS_DIR}/md5.c - ) +) add_executable(listener ${PROJECT_UTILS_DIR}/listener.c - ) +) add_executable(socksend ${PROJECT_UTILS_DIR}/sendcmd.c - ) +) diff --git a/utils/config.h b/utils/config.h index 224b00d2..71e90c16 100644 --- a/utils/config.h +++ b/utils/config.h @@ -11,56 +11,58 @@ #include #include "logentry.h" - /***** Config ***************************************************************/ - /** - * @class Config - * @brief class provides functions for accessing configuration files - */ - class Config { - private: - public: - Config(std::string filename_in): n_entries(), param(), arg() { +/***** Config ***************************************************************/ +/** + * @class Config + * @brief class provides functions for accessing configuration files + */ +class Config { +private: +public: + Config(std::string filename_in): n_entries(), param(), arg() { this->filename = filename_in; - }; - Config(): n_entries(), param(), arg() { }; - - std::string filename; - int n_entries; - std::vector param; - std::vector arg; - - - /***** Config::read_config **********************************************/ - /** - * @brief read the configuration file - * @param[in] config reference to Config class object - * @return 0 (no error) or 1 (error) - * - */ - long read_config(Config &config) { + }; + + Config(): n_entries(), param(), arg() { + }; + + std::string filename; + int n_entries; + std::vector param; + std::vector arg; + + + /***** Config::read_config **********************************************/ + /** + * @brief read the configuration file + * @param[in] config reference to Config class object + * @return 0 (no error) or 1 (error) + * + */ + long read_config(Config &config) { std::string function = "Config::read_config"; - std::fstream filestream; // I/O stream class - std::string line; // Temporary string to read file lines into - size_t index1, index2; // String indexing variables - int linesread = 0; // Counts the number of parameter lines read from file + std::fstream filestream; // I/O stream class + std::string line; // Temporary string to read file lines into + size_t index1, index2; // String indexing variables + int linesread = 0; // Counts the number of parameter lines read from file // Try to open the config file // - if ( !this->filename.empty() ) { - try { - filestream.exceptions ( std::ios_base::failbit ); // set the fail bit to catch exceptions opening config file - filestream.open( this->filename, std::ios::in ); - } - catch ( std::ios_base::failure &e ) { - std::stringstream message; - message.str(""); message << "ERROR: opening configuration file " << this->filename; - logwrite( function, message.str() ); - return( 1 ); - } - } - else { - logwrite(function, "no config file specified"); - return(1); + if (!this->filename.empty()) { + try { + filestream.exceptions(std::ios_base::failbit); + // set the fail bit to catch exceptions opening config file + filestream.open(this->filename, std::ios::in); + } catch (std::ios_base::failure &e) { + std::stringstream message; + message.str(""); + message << "ERROR: opening configuration file " << this->filename; + logwrite(function, message.str()); + return (1); + } + } else { + logwrite(function, "no config file specified"); + return (1); } // Clear the vectors to load the new information from the config file @@ -70,88 +72,92 @@ // Read the config file // - try { // setting the fail bit above requires catching exceptions here - while (getline(filestream, line)) { // Get a line from the file as long as they are available - - if (line.length() > 2) { // valid line is at least 3 characters, ie. X=Y - - if (line.at(0) == '#') continue; // If the first character is a #, ignore it. - - - // At this point, all that is left to check are PARAM=ARG pairs, plus - // possibly comments tagged on the end of the line. - // - else { - line = line.substr( 0, line.find_first_of("#") ); // If # appears elsewhere then take only the line up until that - rtrim( line ); // remove trailing whitespace - index1 = line.find_first_of("="); // Find the = delimiter in the line - this->param.push_back(line.substr(0, index1)); // Put the variable name into the vector holding the names - - // Look for configuration parameters in a vector format (i.e. surrounded by parentheses). - // - if ((index2 = line.find_last_of("(")) == std::string::npos){ - - // Look for quotes around the variable value. Quotes are required for - // variables that have spaces in them (name strings for example). - // - if ((index2 = line.find_last_of("\"")) == std::string::npos){ - // No quote strings, check for a comment at the end of the line - // - if ((index2 = line.find_first_of("#")) == std::string::npos){ - // No comment, so get the index of the end of the comment and set the value into the vector. + try { + // setting the fail bit above requires catching exceptions here + while (getline(filestream, line)) { + // Get a line from the file as long as they are available + + if (line.length() > 2) { + // valid line is at least 3 characters, ie. X=Y + + if (line.at(0) == '#') continue; // If the first character is a #, ignore it. + + + // At this point, all that is left to check are PARAM=ARG pairs, plus + // possibly comments tagged on the end of the line. // - index2 = line.find_first_of("\t\0"); - this->arg.push_back(line.substr(index1+1, index2-index1)); - } - - // There is a comment, get the index and put the value (note the - // different index value for the line substring). - // - else { - index2 = line.find_first_of(" \t#"); - this->arg.push_back(line.substr(index1+1, index2-index1-1)); - } - } - - // Quotes change the substring index again, so find the position of the - // two quote characters in the string and get the value. - // - else { - index1 = line.find_first_of("\"") + 1; - index2 = line.find_last_of("\""); - this->arg.push_back(line.substr(index1, index2-index1)); - } - } - - // A vector was found, so strip the part between the parentheses out and save it - // - else { - index1 = line.find_first_of("(") + 1; - index2 = line.find_last_of(")"); - this->arg.push_back(line.substr(index1, index2-index1)); - } + else { + line = line.substr(0, line.find_first_of("#")); + // If # appears elsewhere then take only the line up until that + rtrim(line); // remove trailing whitespace + index1 = line.find_first_of("="); // Find the = delimiter in the line + this->param.push_back(line.substr(0, index1)); + // Put the variable name into the vector holding the names + + // Look for configuration parameters in a vector format (i.e. surrounded by parentheses). + // + if ((index2 = line.find_last_of("(")) == std::string::npos) { + // Look for quotes around the variable value. Quotes are required for + // variables that have spaces in them (name strings for example). + // + if ((index2 = line.find_last_of("\"")) == std::string::npos) { + // No quote strings, check for a comment at the end of the line + // + if ((index2 = line.find_first_of("#")) == std::string::npos) { + // No comment, so get the index of the end of the comment and set the value into the vector. + // + index2 = line.find_first_of("\t\0"); + this->arg.push_back(line.substr(index1 + 1, index2 - index1)); + } + + // There is a comment, get the index and put the value (note the + // different index value for the line substring). + // + else { + index2 = line.find_first_of(" \t#"); + this->arg.push_back(line.substr(index1 + 1, index2 - index1 - 1)); + } + } + + // Quotes change the substring index again, so find the position of the + // two quote characters in the string and get the value. + // + else { + index1 = line.find_first_of("\"") + 1; + index2 = line.find_last_of("\""); + this->arg.push_back(line.substr(index1, index2 - index1)); + } + } + + // A vector was found, so strip the part between the parentheses out and save it + // + else { + index1 = line.find_first_of("(") + 1; + index2 = line.find_last_of(")"); + this->arg.push_back(line.substr(index1, index2 - index1)); + } + } + } else continue; // For lines of less than 2 characters, we just loop to the next line + + linesread++; // Increment the number of values read successfully } - } - else continue; // For lines of less than 2 characters, we just loop to the next line - - linesread++; // Increment the number of values read successfully - } - } - catch ( std::ios_base::failure &e ) { // even an EOF will set the fail bit, which we don't care about - ; // so just ignore it here + } catch (std::ios_base::failure &e) { + // even an EOF will set the fail bit, which we don't care about + ; // so just ignore it here } - this->n_entries = linesread; // Set the number of elements to the number of lines read + this->n_entries = linesread; // Set the number of elements to the number of lines read - if (filestream.is_open() == true) { // Close the file stream - filestream.flush(); - filestream.close(); + if (filestream.is_open() == true) { + // Close the file stream + filestream.flush(); + filestream.close(); } - return(0); + return (0); + } - } - /***** Config::read_config **********************************************/ + /***** Config::read_config **********************************************/ +}; - }; - /***** Config ***************************************************************/ +/***** Config ***************************************************************/ diff --git a/utils/daemonize.h b/utils/daemonize.h index e3b5ade7..bdc49783 100644 --- a/utils/daemonize.h +++ b/utils/daemonize.h @@ -29,130 +29,138 @@ * */ namespace Daemon { - - /***** Daemon::daemonize ****************************************************/ - /** - * @brief this function will daemonize a process - * @param[in] name the name for this daemon - * @param[in] path directory to chdir to when running daemon - * @param[in] outfile where to direct stdout, /dev/null by default - * @param[in] errfile where to direct stderr, /dev/null by default - * @param[in] infile stdin, /dev/null by default - * @param[in] closefd set true to close all file descriptors - * @return 0 - * - * This function is overloaded - * - * This version accepts a boolean to indicate if all file descriptors should - * be closed (true) or not (false). Some applications (E.G. Andor camera) - * can't handle having all file descriptors closed, so setting this to false - * will cause only stdin, stdout, and stderr to be closed. - * - */ - int daemonize( std::string name, std::string path, std::string outfile, std::string errfile, std::string infile, bool closefd ) { - if ( path.empty() ) { path = "/tmp"; } - if ( name.empty() ) { name = "mydaemon"; } - if ( infile.empty() ) { infile = "/dev/null"; } - if ( outfile.empty() ) { outfile = "/dev/null"; } - if ( errfile.empty() ) { errfile = "/dev/null"; } - - // fork, detach from process group leader - // - pid_t child = fork(); - - if ( child < 0 ) { // failed fork - fprintf( stderr, "ERROR: failed fork\n" ); - exit( EXIT_FAILURE ); - } - if ( child > 0 ) { // parent - exit( EXIT_SUCCESS ); + /***** Daemon::daemonize ****************************************************/ + /** + * @brief this function will daemonize a process + * @param[in] name the name for this daemon + * @param[in] path directory to chdir to when running daemon + * @param[in] outfile where to direct stdout, /dev/null by default + * @param[in] errfile where to direct stderr, /dev/null by default + * @param[in] infile stdin, /dev/null by default + * @param[in] closefd set true to close all file descriptors + * @return 0 + * + * This function is overloaded + * + * This version accepts a boolean to indicate if all file descriptors should + * be closed (true) or not (false). Some applications (E.G. Andor camera) + * can't handle having all file descriptors closed, so setting this to false + * will cause only stdin, stdout, and stderr to be closed. + * + */ + int daemonize(std::string name, std::string path, std::string outfile, std::string errfile, std::string infile, + bool closefd) { + if (path.empty()) { path = "/tmp"; } + if (name.empty()) { name = "mydaemon"; } + if (infile.empty()) { infile = "/dev/null"; } + if (outfile.empty()) { outfile = "/dev/null"; } + if (errfile.empty()) { errfile = "/dev/null"; } + + // fork, detach from process group leader + // + pid_t child = fork(); + + if (child < 0) { + // failed fork + fprintf(stderr, "ERROR: failed fork\n"); + exit(EXIT_FAILURE); + } + if (child > 0) { + // parent + exit(EXIT_SUCCESS); + } + if (setsid() < 0) { + // failed to become session leader + fprintf(stderr, "error: failed setsid\n"); + exit(EXIT_FAILURE); + } + + // catch/ignore signals + // + signal(SIGCHLD, SIG_IGN); + + // fork second time + // + if ((child = fork()) < 0) { + //failed fork + fprintf(stderr, "error: failed fork\n"); + exit(EXIT_FAILURE); + } + if (child > 0) { + //parent + exit(EXIT_SUCCESS); + } + + umask(0); // new file permissions + + if (chdir(path.c_str()) < 0) { + // change to path directory + fprintf(stderr, "error: failed chdir: %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + + // Close file descriptors, either {0:MAX} (closefd=true) + // or only {0:2} (closefd=false). + // + int fdmax = (closefd ? sysconf(_SC_OPEN_MAX) : 2); + for (int fd = fdmax; fd >= 0; fd--) close(fd); + + // reopen stdin, stdout, stderr + // + stdin = fopen(infile.c_str(), "r"); // fd=0 + stdout = fopen(outfile.c_str(), "w+"); // fd=1 + stderr = fopen(errfile.c_str(), "w+"); // fd=2 + + // open syslog + // + openlog(name.c_str(), LOG_PID, LOG_DAEMON); + return (0); } - if( setsid() < 0 ) { // failed to become session leader - fprintf( stderr, "error: failed setsid\n" ); - exit( EXIT_FAILURE ); - } - - // catch/ignore signals - // - signal( SIGCHLD, SIG_IGN ); - // fork second time - // - if ( ( child = fork() ) < 0 ) { //failed fork - fprintf( stderr, "error: failed fork\n" ); - exit( EXIT_FAILURE ); + /***** Daemon::daemonize ****************************************************/ + + + /***** Daemon::daemonize ****************************************************/ + /** + * @brief this function will daemonize a process + * @param[in] name the name for this daemon + * @param[in] path directory to chdir to when running daemon + * @param[in] outfile where to direct stdout, /dev/null by default + * @param[in] errfile where to direct stderr, /dev/null by default + * @param[in] infile stdin, /dev/null by default + * @return 0 + * + * This function is overloaded + * + * This version is for backwards compatibility with functions that were + * written prior to adding the closefd boolean argument. If that arg is + * missing then this will call the new function with closefd=true. + * I.E., the default is to close all file descriptors. + * + */ + int daemonize(std::string name, std::string path, std::string outfile, std::string errfile, std::string infile) { + return daemonize(name, path, outfile, errfile, infile, true); } - if( child > 0 ) { //parent - exit( EXIT_SUCCESS ); - } - - umask( 0 ); // new file permissions - if ( chdir( path.c_str() ) < 0 ) { // change to path directory - fprintf( stderr, "error: failed chdir: %s\n", strerror(errno) ); - exit( EXIT_FAILURE ); + /***** Daemon::daemonize ****************************************************/ + + + /***** Daemon::daemonize ****************************************************/ + /** + * @brief this function will daemonize a process + * @param[in] name the name for this daemon + * @param[in] path directory to chdir to when running daemon + * @return 0 + * + * This function is overloaded + * + * This version accepts only a name and path, using defaults + * for all other arguments. + * + */ + int daemonize(std::string name, std::string path) { + return daemonize(name, path, "", "", ""); } - // Close file descriptors, either {0:MAX} (closefd=true) - // or only {0:2} (closefd=false). - // - int fdmax = ( closefd ? sysconf( _SC_OPEN_MAX ) : 2 ); - for( int fd = fdmax; fd >= 0; fd-- ) close( fd ); - - // reopen stdin, stdout, stderr - // - stdin = fopen( infile.c_str(), "r" ); // fd=0 - stdout = fopen( outfile.c_str(), "w+" ); // fd=1 - stderr = fopen( errfile.c_str(), "w+" ); // fd=2 - - // open syslog - // - openlog( name.c_str(), LOG_PID, LOG_DAEMON ); - return( 0 ); - } - /***** Daemon::daemonize ****************************************************/ - - - /***** Daemon::daemonize ****************************************************/ - /** - * @brief this function will daemonize a process - * @param[in] name the name for this daemon - * @param[in] path directory to chdir to when running daemon - * @param[in] outfile where to direct stdout, /dev/null by default - * @param[in] errfile where to direct stderr, /dev/null by default - * @param[in] infile stdin, /dev/null by default - * @return 0 - * - * This function is overloaded - * - * This version is for backwards compatibility with functions that were - * written prior to adding the closefd boolean argument. If that arg is - * missing then this will call the new function with closefd=true. - * I.E., the default is to close all file descriptors. - * - */ - int daemonize( std::string name, std::string path, std::string outfile, std::string errfile, std::string infile ) { - return daemonize( name, path, outfile, errfile, infile, true ); - } - /***** Daemon::daemonize ****************************************************/ - - - /***** Daemon::daemonize ****************************************************/ - /** - * @brief this function will daemonize a process - * @param[in] name the name for this daemon - * @param[in] path directory to chdir to when running daemon - * @return 0 - * - * This function is overloaded - * - * This version accepts only a name and path, using defaults - * for all other arguments. - * - */ - int daemonize( std::string name, std::string path ) { - return daemonize( name, path, "", "", "" ); - } - /***** Daemon::daemonize ****************************************************/ - + /***** Daemon::daemonize ****************************************************/ } diff --git a/utils/listener.c b/utils/listener.c index df9e4ac7..1849a311 100644 --- a/utils/listener.c +++ b/utils/listener.c @@ -21,20 +21,19 @@ #define MSGBUFSIZE 256 -int main(int argc, char *argv[]) -{ +int main(int argc, char *argv[]) { if (argc < 3) { - printf("Command line args should be multicast group and port\n"); - printf("(e.g. for SSDP, `listener 239.255.255.250 1900`)\n"); - return 1; + printf("Command line args should be multicast group and port\n"); + printf("(e.g. for SSDP, `listener 239.255.255.250 1900`)\n"); + return 1; } - char* group = argv[1]; // e.g. 239.255.255.250 for SSDP + char *group = argv[1]; // e.g. 239.255.255.250 for SSDP int port = atoi(argv[2]); // 0 if error, which is an invalid port - char *filterstr=NULL; + char *filterstr = NULL; - if ( argc==4 ) filterstr = argv[3]; + if (argc == 4) filterstr = argv[3]; // create what looks like an ordinary UDP socket // @@ -49,11 +48,11 @@ int main(int argc, char *argv[]) u_int yes = 1; if ( setsockopt( - fd, SOL_SOCKET, SO_REUSEADDR, (char*) &yes, sizeof(yes) + fd, SOL_SOCKET, SO_REUSEADDR, (char *) &yes, sizeof(yes) ) < 0 - ){ - perror("Reusing ADDR failed"); - return 1; + ) { + perror("Reusing ADDR failed"); + return 1; } // set up destination address @@ -66,7 +65,7 @@ int main(int argc, char *argv[]) // bind to receive address // - if (bind(fd, (struct sockaddr*) &addr, sizeof(addr)) < 0) { + if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { perror("bind"); return 1; } @@ -78,9 +77,9 @@ int main(int argc, char *argv[]) mreq.imr_interface.s_addr = htonl(INADDR_ANY); if ( setsockopt( - fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*) &mreq, sizeof(mreq) + fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *) &mreq, sizeof(mreq) ) < 0 - ){ + ) { perror("setsockopt"); return 1; } @@ -103,11 +102,10 @@ int main(int argc, char *argv[]) return 1; } msgbuf[nbytes] = '\0'; - if ( filterstr != NULL ) { - if ( strstr(msgbuf, filterstr) != NULL ) puts(msgbuf); - } - else puts(msgbuf); - } + if (filterstr != NULL) { + if (strstr(msgbuf, filterstr) != NULL) puts(msgbuf); + } else puts(msgbuf); + } return 0; } diff --git a/utils/logentry.cpp b/utils/logentry.cpp index 598b2576..59f5c121 100644 --- a/utils/logentry.cpp +++ b/utils/logentry.cpp @@ -11,11 +11,11 @@ #include "logentry.h" -std::mutex loglock; /// mutex to protect from multiple access -std::ofstream filestream; /// IO stream class -unsigned int nextday = 86410; /// number of seconds until a new day -bool to_stderr = true; /// write to stderr by default -std::string tmzone_log; /// optional time zone for logging +std::mutex loglock; /// mutex to protect from multiple access +std::ofstream filestream; /// IO stream class +unsigned int nextday = 86410; /// number of seconds until a new day +bool to_stderr = true; /// write to stderr by default +std::string tmzone_log; /// optional time zone for logging /***** init_log ***************************************************************/ @@ -31,93 +31,97 @@ std::string tmzone_log; /// optional time zone for logging * where logpath and name are passed in as parameters. * */ -long init_log( std::string name, std::string logpath, std::string logstderr, std::string logtmzone ) { - const std::string function = "init_log"; - std::stringstream filename; - std::stringstream message; - int year, mon, mday, hour, min, sec, usec; - long error = 0; - - to_stderr = ( logstderr=="false" ? false : true ); // should logwrite also print to stderr? - - tmzone_log = logtmzone; - - if ( ( error = get_time( tmzone_log, year, mon, mday, hour, min, sec, usec ) ) ) return error; - - // assemble log file name from the passed-in arguments and current date - // - filename << logpath << "/" << name << "_" << std::setfill('0') - << std::setw(4) << year - << std::setw(2) << mon - << std::setw(2) << mday << ".log"; - - // number of seconds until a new day - // - nextday = (unsigned int)(86410 - hour*3600 - min*60 - sec); - - // open the log file stream for append - // - try { - filestream.open(filename.str(), std::ios_base::app); - } - catch ( const std::filesystem::filesystem_error& e ) { - message.str(""); message << "ERROR " << filename.str() << ": " << e.what() << ": " << e.code().value(); - logwrite(function, message.str()); - return 1; - } - catch(...) { - message.str(""); message << "ERROR: opening log file " << filename.str() << ": " << std::strerror(errno); - logwrite(function, message.str()); - return 1; - } - - // If I am the owner then make sure the permissions are set correctly. - // Only the owner can change permissions. Even if I have write access, - // if I'm not the owner then I can't change the permissions. - // - if ( is_owner( filename.str() ) ) { +long init_log(std::string name, std::string logpath, std::string logstderr, std::string logtmzone) { + const std::string function = "init_log"; + std::stringstream filename; + std::stringstream message; + int year, mon, mday, hour, min, sec, usec; + long error = 0; + + to_stderr = (logstderr == "false" ? false : true); // should logwrite also print to stderr? + + tmzone_log = logtmzone; + + if ((error = get_time(tmzone_log, year, mon, mday, hour, min, sec, usec))) return error; + + // assemble log file name from the passed-in arguments and current date + // + filename << logpath << "/" << name << "_" << std::setfill('0') + << std::setw(4) << year + << std::setw(2) << mon + << std::setw(2) << mday << ".log"; + + // number of seconds until a new day + // + nextday = (unsigned int) (86410 - hour * 3600 - min * 60 - sec); + + // open the log file stream for append + // try { - // remove all permissions - // - std::filesystem::permissions( filename.str(), - std::filesystem::perms::all, - std::filesystem::perm_options::remove - ); - - // add back permissions rw-rw-r-- (664) - // - std::filesystem::permissions( filename.str(), - std::filesystem::perms::owner_read | - std::filesystem::perms::owner_write | - std::filesystem::perms::group_read | - std::filesystem::perms::group_write | - std::filesystem::perms::others_read, - std::filesystem::perm_options::add - ); + filestream.open(filename.str(), std::ios_base::app); + } catch (const std::filesystem::filesystem_error &e) { + message.str(""); + message << "ERROR " << filename.str() << ": " << e.what() << ": " << e.code().value(); + logwrite(function, message.str()); + return 1; + } + catch (...) { + message.str(""); + message << "ERROR: opening log file " << filename.str() << ": " << std::strerror(errno); + logwrite(function, message.str()); + return 1; } - catch ( const std::filesystem::filesystem_error& e ) { - message.str(""); message << "ERROR " << filename.str() << ": " << e.what() << ": " << e.code().value(); - logwrite(function, message.str()); - return 1; + + // If I am the owner then make sure the permissions are set correctly. + // Only the owner can change permissions. Even if I have write access, + // if I'm not the owner then I can't change the permissions. + // + if (is_owner(filename.str())) { + try { + // remove all permissions + // + std::filesystem::permissions(filename.str(), + std::filesystem::perms::all, + std::filesystem::perm_options::remove + ); + + // add back permissions rw-rw-r-- (664) + // + std::filesystem::permissions(filename.str(), + std::filesystem::perms::owner_read | + std::filesystem::perms::owner_write | + std::filesystem::perms::group_read | + std::filesystem::perms::group_write | + std::filesystem::perms::others_read, + std::filesystem::perm_options::add + ); + } catch (const std::filesystem::filesystem_error &e) { + message.str(""); + message << "ERROR " << filename.str() << ": " << e.what() << ": " << e.code().value(); + logwrite(function, message.str()); + return 1; + } + } + + // If I do not have write permission then that is a fatal error + // + if (!has_write_permission(filename.str())) { + message.str(""); + message << "ERROR: no write permission for log file " << filename.str(); + logwrite(function, message.str()); + return 1; } - } - - // If I do not have write permission then that is a fatal error - // - if ( ! has_write_permission( filename.str() ) ) { - message.str(""); message << "ERROR: no write permission for log file " << filename.str(); - logwrite(function, message.str()); - return 1; - } - - if ( ! filestream.is_open() ) { - message.str(""); message << "ERROR: log file " << filename.str() << " not open"; - logwrite(function, message.str()); - return 1; - } - - return 0; + + if (!filestream.is_open()) { + message.str(""); + message << "ERROR: log file " << filename.str() << " not open"; + logwrite(function, message.str()); + return 1; + } + + return 0; } + /***** init_log ***************************************************************/ @@ -128,11 +132,12 @@ long init_log( std::string name, std::string logpath, std::string logstderr, std * */ void close_log() { - if ( filestream.is_open() ) { - filestream.flush(); - filestream.close(); - } + if (filestream.is_open()) { + filestream.flush(); + filestream.close(); + } } + /***** close_log **************************************************************/ @@ -149,20 +154,20 @@ void close_log() { * log filestream isn't open. * */ -void logwrite( const std::string &function, const std::string &message ) { - std::stringstream logmsg; +void logwrite(const std::string &function, const std::string &message) { + std::stringstream logmsg; - std::lock_guard lock(loglock); // lock mutex to protect from multiple access + std::lock_guard lock(loglock); // lock mutex to protect from multiple access - std::string timestamp = get_timestamp(tmzone_log); // get the current time (defined in utilities.h) + std::string timestamp = get_timestamp(tmzone_log); // get the current time (defined in utilities.h) - logmsg << timestamp << " (" << function << ") " << message << "\n"; + logmsg << timestamp << " (" << function << ") " << message << "\n"; - if (filestream.is_open()) { - filestream << logmsg.str(); // send to the file stream (if open) - filestream.flush(); - } - if ( to_stderr ) std::cerr << logmsg.str(); // send to standard error if requested + if (filestream.is_open()) { + filestream << logmsg.str(); // send to the file stream (if open) + filestream.flush(); + } + if (to_stderr) std::cerr << logmsg.str(); // send to standard error if requested } -/***** logwrite ***************************************************************/ +/***** logwrite ***************************************************************/ diff --git a/utils/logentry.h b/utils/logentry.h index 100c3236..25c9dad3 100644 --- a/utils/logentry.h +++ b/utils/logentry.h @@ -16,8 +16,12 @@ #include #include "utilities.h" -extern unsigned int nextday; /// number of seconds until the next day is a global +extern unsigned int nextday; /// number of seconds until the next day is a global -long init_log( std::string name, std::string logpath, std::string logstderr, std::string logtmzone ); /// initialize the logging system -void close_log(); /// close the log file stream -void logwrite( const std::string &function, const std::string &message ); /// create a time-stamped log entry "message" from "function" +long init_log(std::string name, std::string logpath, std::string logstderr, std::string logtmzone); + +/// initialize the logging system +void close_log(); /// close the log file stream +void logwrite(const std::string &function, const std::string &message); + +/// create a time-stamped log entry "message" from "function" diff --git a/utils/md5.c b/utils/md5.c index 565a2e6b..822bdf35 100644 --- a/utils/md5.c +++ b/utils/md5.c @@ -34,158 +34,153 @@ a = b + ROTLEFT(a,s); } /*********************** FUNCTION DEFINITIONS ***********************/ -void md5_transform(MD5_CTX *ctx, const BYTE data[]) -{ - WORD a, b, c, d, m[16], i, j; - - // MD5 specifies big endian byte order, but this implementation assumes a little - // endian byte order CPU. Reverse all the bytes upon input, and re-reverse them - // on output (in md5_final()). - for (i = 0, j = 0; i < 16; ++i, j += 4) - m[i] = (data[j]) + (data[j + 1] << 8) + (data[j + 2] << 16) + (data[j + 3] << 24); - - a = ctx->state[0]; - b = ctx->state[1]; - c = ctx->state[2]; - d = ctx->state[3]; - - FF(a,b,c,d,m[0], 7,0xd76aa478); - FF(d,a,b,c,m[1], 12,0xe8c7b756); - FF(c,d,a,b,m[2], 17,0x242070db); - FF(b,c,d,a,m[3], 22,0xc1bdceee); - FF(a,b,c,d,m[4], 7,0xf57c0faf); - FF(d,a,b,c,m[5], 12,0x4787c62a); - FF(c,d,a,b,m[6], 17,0xa8304613); - FF(b,c,d,a,m[7], 22,0xfd469501); - FF(a,b,c,d,m[8], 7,0x698098d8); - FF(d,a,b,c,m[9], 12,0x8b44f7af); - FF(c,d,a,b,m[10],17,0xffff5bb1); - FF(b,c,d,a,m[11],22,0x895cd7be); - FF(a,b,c,d,m[12], 7,0x6b901122); - FF(d,a,b,c,m[13],12,0xfd987193); - FF(c,d,a,b,m[14],17,0xa679438e); - FF(b,c,d,a,m[15],22,0x49b40821); - - GG(a,b,c,d,m[1], 5,0xf61e2562); - GG(d,a,b,c,m[6], 9,0xc040b340); - GG(c,d,a,b,m[11],14,0x265e5a51); - GG(b,c,d,a,m[0], 20,0xe9b6c7aa); - GG(a,b,c,d,m[5], 5,0xd62f105d); - GG(d,a,b,c,m[10], 9,0x02441453); - GG(c,d,a,b,m[15],14,0xd8a1e681); - GG(b,c,d,a,m[4], 20,0xe7d3fbc8); - GG(a,b,c,d,m[9], 5,0x21e1cde6); - GG(d,a,b,c,m[14], 9,0xc33707d6); - GG(c,d,a,b,m[3], 14,0xf4d50d87); - GG(b,c,d,a,m[8], 20,0x455a14ed); - GG(a,b,c,d,m[13], 5,0xa9e3e905); - GG(d,a,b,c,m[2], 9,0xfcefa3f8); - GG(c,d,a,b,m[7], 14,0x676f02d9); - GG(b,c,d,a,m[12],20,0x8d2a4c8a); - - HH(a,b,c,d,m[5], 4,0xfffa3942); - HH(d,a,b,c,m[8], 11,0x8771f681); - HH(c,d,a,b,m[11],16,0x6d9d6122); - HH(b,c,d,a,m[14],23,0xfde5380c); - HH(a,b,c,d,m[1], 4,0xa4beea44); - HH(d,a,b,c,m[4], 11,0x4bdecfa9); - HH(c,d,a,b,m[7], 16,0xf6bb4b60); - HH(b,c,d,a,m[10],23,0xbebfbc70); - HH(a,b,c,d,m[13], 4,0x289b7ec6); - HH(d,a,b,c,m[0], 11,0xeaa127fa); - HH(c,d,a,b,m[3], 16,0xd4ef3085); - HH(b,c,d,a,m[6], 23,0x04881d05); - HH(a,b,c,d,m[9], 4,0xd9d4d039); - HH(d,a,b,c,m[12],11,0xe6db99e5); - HH(c,d,a,b,m[15],16,0x1fa27cf8); - HH(b,c,d,a,m[2], 23,0xc4ac5665); - - II(a,b,c,d,m[0], 6,0xf4292244); - II(d,a,b,c,m[7], 10,0x432aff97); - II(c,d,a,b,m[14],15,0xab9423a7); - II(b,c,d,a,m[5], 21,0xfc93a039); - II(a,b,c,d,m[12], 6,0x655b59c3); - II(d,a,b,c,m[3], 10,0x8f0ccc92); - II(c,d,a,b,m[10],15,0xffeff47d); - II(b,c,d,a,m[1], 21,0x85845dd1); - II(a,b,c,d,m[8], 6,0x6fa87e4f); - II(d,a,b,c,m[15],10,0xfe2ce6e0); - II(c,d,a,b,m[6], 15,0xa3014314); - II(b,c,d,a,m[13],21,0x4e0811a1); - II(a,b,c,d,m[4], 6,0xf7537e82); - II(d,a,b,c,m[11],10,0xbd3af235); - II(c,d,a,b,m[2], 15,0x2ad7d2bb); - II(b,c,d,a,m[9], 21,0xeb86d391); - - ctx->state[0] += a; - ctx->state[1] += b; - ctx->state[2] += c; - ctx->state[3] += d; +void md5_transform(MD5_CTX *ctx, const BYTE data[]) { + WORD a, b, c, d, m[16], i, j; + + // MD5 specifies big endian byte order, but this implementation assumes a little + // endian byte order CPU. Reverse all the bytes upon input, and re-reverse them + // on output (in md5_final()). + for (i = 0, j = 0; i < 16; ++i, j += 4) + m[i] = (data[j]) + (data[j + 1] << 8) + (data[j + 2] << 16) + (data[j + 3] << 24); + + a = ctx->state[0]; + b = ctx->state[1]; + c = ctx->state[2]; + d = ctx->state[3]; + + FF(a, b, c, d, m[0], 7, 0xd76aa478); + FF(d, a, b, c, m[1], 12, 0xe8c7b756); + FF(c, d, a, b, m[2], 17, 0x242070db); + FF(b, c, d, a, m[3], 22, 0xc1bdceee); + FF(a, b, c, d, m[4], 7, 0xf57c0faf); + FF(d, a, b, c, m[5], 12, 0x4787c62a); + FF(c, d, a, b, m[6], 17, 0xa8304613); + FF(b, c, d, a, m[7], 22, 0xfd469501); + FF(a, b, c, d, m[8], 7, 0x698098d8); + FF(d, a, b, c, m[9], 12, 0x8b44f7af); + FF(c, d, a, b, m[10], 17, 0xffff5bb1); + FF(b, c, d, a, m[11], 22, 0x895cd7be); + FF(a, b, c, d, m[12], 7, 0x6b901122); + FF(d, a, b, c, m[13], 12, 0xfd987193); + FF(c, d, a, b, m[14], 17, 0xa679438e); + FF(b, c, d, a, m[15], 22, 0x49b40821); + + GG(a, b, c, d, m[1], 5, 0xf61e2562); + GG(d, a, b, c, m[6], 9, 0xc040b340); + GG(c, d, a, b, m[11], 14, 0x265e5a51); + GG(b, c, d, a, m[0], 20, 0xe9b6c7aa); + GG(a, b, c, d, m[5], 5, 0xd62f105d); + GG(d, a, b, c, m[10], 9, 0x02441453); + GG(c, d, a, b, m[15], 14, 0xd8a1e681); + GG(b, c, d, a, m[4], 20, 0xe7d3fbc8); + GG(a, b, c, d, m[9], 5, 0x21e1cde6); + GG(d, a, b, c, m[14], 9, 0xc33707d6); + GG(c, d, a, b, m[3], 14, 0xf4d50d87); + GG(b, c, d, a, m[8], 20, 0x455a14ed); + GG(a, b, c, d, m[13], 5, 0xa9e3e905); + GG(d, a, b, c, m[2], 9, 0xfcefa3f8); + GG(c, d, a, b, m[7], 14, 0x676f02d9); + GG(b, c, d, a, m[12], 20, 0x8d2a4c8a); + + HH(a, b, c, d, m[5], 4, 0xfffa3942); + HH(d, a, b, c, m[8], 11, 0x8771f681); + HH(c, d, a, b, m[11], 16, 0x6d9d6122); + HH(b, c, d, a, m[14], 23, 0xfde5380c); + HH(a, b, c, d, m[1], 4, 0xa4beea44); + HH(d, a, b, c, m[4], 11, 0x4bdecfa9); + HH(c, d, a, b, m[7], 16, 0xf6bb4b60); + HH(b, c, d, a, m[10], 23, 0xbebfbc70); + HH(a, b, c, d, m[13], 4, 0x289b7ec6); + HH(d, a, b, c, m[0], 11, 0xeaa127fa); + HH(c, d, a, b, m[3], 16, 0xd4ef3085); + HH(b, c, d, a, m[6], 23, 0x04881d05); + HH(a, b, c, d, m[9], 4, 0xd9d4d039); + HH(d, a, b, c, m[12], 11, 0xe6db99e5); + HH(c, d, a, b, m[15], 16, 0x1fa27cf8); + HH(b, c, d, a, m[2], 23, 0xc4ac5665); + + II(a, b, c, d, m[0], 6, 0xf4292244); + II(d, a, b, c, m[7], 10, 0x432aff97); + II(c, d, a, b, m[14], 15, 0xab9423a7); + II(b, c, d, a, m[5], 21, 0xfc93a039); + II(a, b, c, d, m[12], 6, 0x655b59c3); + II(d, a, b, c, m[3], 10, 0x8f0ccc92); + II(c, d, a, b, m[10], 15, 0xffeff47d); + II(b, c, d, a, m[1], 21, 0x85845dd1); + II(a, b, c, d, m[8], 6, 0x6fa87e4f); + II(d, a, b, c, m[15], 10, 0xfe2ce6e0); + II(c, d, a, b, m[6], 15, 0xa3014314); + II(b, c, d, a, m[13], 21, 0x4e0811a1); + II(a, b, c, d, m[4], 6, 0xf7537e82); + II(d, a, b, c, m[11], 10, 0xbd3af235); + II(c, d, a, b, m[2], 15, 0x2ad7d2bb); + II(b, c, d, a, m[9], 21, 0xeb86d391); + + ctx->state[0] += a; + ctx->state[1] += b; + ctx->state[2] += c; + ctx->state[3] += d; } -void md5_init(MD5_CTX *ctx) -{ - ctx->datalen = 0; - ctx->bitlen = 0; - ctx->state[0] = 0x67452301; - ctx->state[1] = 0xEFCDAB89; - ctx->state[2] = 0x98BADCFE; - ctx->state[3] = 0x10325476; +void md5_init(MD5_CTX *ctx) { + ctx->datalen = 0; + ctx->bitlen = 0; + ctx->state[0] = 0x67452301; + ctx->state[1] = 0xEFCDAB89; + ctx->state[2] = 0x98BADCFE; + ctx->state[3] = 0x10325476; } -void md5_update(MD5_CTX *ctx, const BYTE data[], size_t len) -{ - size_t i; - - for (i = 0; i < len; ++i) { - ctx->data[ctx->datalen] = data[i]; - ctx->datalen++; - if (ctx->datalen == 64) { - md5_transform(ctx, ctx->data); - ctx->bitlen += 512; - ctx->datalen = 0; - } - } +void md5_update(MD5_CTX *ctx, const BYTE data[], size_t len) { + size_t i; + + for (i = 0; i < len; ++i) { + ctx->data[ctx->datalen] = data[i]; + ctx->datalen++; + if (ctx->datalen == 64) { + md5_transform(ctx, ctx->data); + ctx->bitlen += 512; + ctx->datalen = 0; + } + } } -void md5_final(MD5_CTX *ctx, BYTE hash[]) -{ - size_t i; - - i = ctx->datalen; - - // Pad whatever data is left in the buffer. - if (ctx->datalen < 56) { - ctx->data[i++] = 0x80; - while (i < 56) - ctx->data[i++] = 0x00; - } - else if (ctx->datalen >= 56) { - ctx->data[i++] = 0x80; - while (i < 64) - ctx->data[i++] = 0x00; - md5_transform(ctx, ctx->data); - memset(ctx->data, 0, 56); - } - - // Append to the padding the total message's length in bits and transform. - ctx->bitlen += ctx->datalen * 8; - ctx->data[56] = ctx->bitlen; - ctx->data[57] = ctx->bitlen >> 8; - ctx->data[58] = ctx->bitlen >> 16; - ctx->data[59] = ctx->bitlen >> 24; - ctx->data[60] = ctx->bitlen >> 32; - ctx->data[61] = ctx->bitlen >> 40; - ctx->data[62] = ctx->bitlen >> 48; - ctx->data[63] = ctx->bitlen >> 56; - md5_transform(ctx, ctx->data); - - // Since this implementation uses little endian byte ordering and MD uses big endian, - // reverse all the bytes when copying the final state to the output hash. - for (i = 0; i < 4; ++i) { - hash[i] = (ctx->state[0] >> (i * 8)) & 0x000000ff; - hash[i + 4] = (ctx->state[1] >> (i * 8)) & 0x000000ff; - hash[i + 8] = (ctx->state[2] >> (i * 8)) & 0x000000ff; - hash[i + 12] = (ctx->state[3] >> (i * 8)) & 0x000000ff; - } +void md5_final(MD5_CTX *ctx, BYTE hash[]) { + size_t i; + + i = ctx->datalen; + + // Pad whatever data is left in the buffer. + if (ctx->datalen < 56) { + ctx->data[i++] = 0x80; + while (i < 56) + ctx->data[i++] = 0x00; + } else if (ctx->datalen >= 56) { + ctx->data[i++] = 0x80; + while (i < 64) + ctx->data[i++] = 0x00; + md5_transform(ctx, ctx->data); + memset(ctx->data, 0, 56); + } + + // Append to the padding the total message's length in bits and transform. + ctx->bitlen += ctx->datalen * 8; + ctx->data[56] = ctx->bitlen; + ctx->data[57] = ctx->bitlen >> 8; + ctx->data[58] = ctx->bitlen >> 16; + ctx->data[59] = ctx->bitlen >> 24; + ctx->data[60] = ctx->bitlen >> 32; + ctx->data[61] = ctx->bitlen >> 40; + ctx->data[62] = ctx->bitlen >> 48; + ctx->data[63] = ctx->bitlen >> 56; + md5_transform(ctx, ctx->data); + + // Since this implementation uses little endian byte ordering and MD uses big endian, + // reverse all the bytes when copying the final state to the output hash. + for (i = 0; i < 4; ++i) { + hash[i] = (ctx->state[0] >> (i * 8)) & 0x000000ff; + hash[i + 4] = (ctx->state[1] >> (i * 8)) & 0x000000ff; + hash[i + 8] = (ctx->state[2] >> (i * 8)) & 0x000000ff; + hash[i + 12] = (ctx->state[3] >> (i * 8)) & 0x000000ff; + } } diff --git a/utils/utilities.cpp b/utils/utilities.cpp index 82845e80..042cdcba 100644 --- a/utils/utilities.cpp +++ b/utils/utilities.cpp @@ -7,618 +7,638 @@ #include "utilities.h" -std::string tmzone_cfg; ///< time zone if set in cfg file +std::string tmzone_cfg; ///< time zone if set in cfg file std::mutex generate_tmpfile_mtx; - /***** cmdOptionExists ******************************************************/ - /** - * @brief returns true if option is found in argv - * @param[in] begin this is the beginning, should be argv - * @param[in] end this is the end, should be argv+argc - * @param[out] option string to search for - * @return true|false if string was found - * - * Intended to be called as cmdOptionExists( argv, argv+argc, "-X" ) - * to search for "-X" option in argv. - * - * Pair with getCmdOption() function. - * - */ - bool cmdOptionExists( char** begin, char** end, const std::string &option ) { - return std::find( begin, end, option ) != end; - } - /***** cmdOptionExists ******************************************************/ - - - /***** getCmdOption *********************************************************/ - /** - * @brief returns pointer to command line option specified with "-X option" - * @param[in] begin this is the beginning, should be argv - * @param[in] end this is the end, should be argv+argc - * @param[out] option string to search for - * @return char* pointer to the option found - * - * Intended to be called as char* option = getCmdOption( argv, argv+argc, "-X" ); - * to get option associated with "-X option" in argv. - * - * Pair with cmdOptionExists() - * - */ - char* getCmdOption( char** begin, char** end, const std::string &option ) { - char** itr = std::find(begin, end, option); - if ( itr != end && ++itr != end ) { - return *itr; +/***** cmdOptionExists ******************************************************/ +/** + * @brief returns true if option is found in argv + * @param[in] begin this is the beginning, should be argv + * @param[in] end this is the end, should be argv+argc + * @param[out] option string to search for + * @return true|false if string was found + * + * Intended to be called as cmdOptionExists( argv, argv+argc, "-X" ) + * to search for "-X" option in argv. + * + * Pair with getCmdOption() function. + * + */ +bool cmdOptionExists(char **begin, char **end, const std::string &option) { + return std::find(begin, end, option) != end; +} + +/***** cmdOptionExists ******************************************************/ + + +/***** getCmdOption *********************************************************/ +/** + * @brief returns pointer to command line option specified with "-X option" + * @param[in] begin this is the beginning, should be argv + * @param[in] end this is the end, should be argv+argc + * @param[out] option string to search for + * @return char* pointer to the option found + * + * Intended to be called as char* option = getCmdOption( argv, argv+argc, "-X" ); + * to get option associated with "-X option" in argv. + * + * Pair with cmdOptionExists() + * + */ +char *getCmdOption(char **begin, char **end, const std::string &option) { + char **itr = std::find(begin, end, option); + if (itr != end && ++itr != end) { + return *itr; } return nullptr; - } - /***** getCmdOption *********************************************************/ - - - /***** my_hardware_concurrency **********************************************/ - /** - * @brief return number of concurrent threads supported by the implementation - * @return integer number of processors listed in /proc/cpuinfo - * - * Counts the number of processors listed in /proc/cpuinfo - * - */ - int my_hardware_concurrency() { - std::ifstream cpuinfo( "/proc/cpuinfo" ); - return static_cast(std::count( std::istream_iterator(cpuinfo), - std::istream_iterator(), - std::string("processor") )); - } - /***** my_hardware_concurrency **********************************************/ - - - /***** cores_available ******************************************************/ - /** - * @brief return number of concurrent threads supported by the implementation - * @return integer number of cores - * - * If the value is not known then check /proc/cpuinfo - * - */ - int cores_available() { +} + +/***** getCmdOption *********************************************************/ + + +/***** my_hardware_concurrency **********************************************/ +/** + * @brief return number of concurrent threads supported by the implementation + * @return integer number of processors listed in /proc/cpuinfo + * + * Counts the number of processors listed in /proc/cpuinfo + * + */ +int my_hardware_concurrency() { + std::ifstream cpuinfo("/proc/cpuinfo"); + return static_cast(std::count(std::istream_iterator(cpuinfo), + std::istream_iterator(), + std::string("processor"))); +} + +/***** my_hardware_concurrency **********************************************/ + + +/***** cores_available ******************************************************/ +/** + * @brief return number of concurrent threads supported by the implementation + * @return integer number of cores + * + * If the value is not known then check /proc/cpuinfo + * + */ +int cores_available() { int cores = static_cast(std::thread::hardware_concurrency()); return cores ? cores : my_hardware_concurrency(); - } - /***** cores_available ******************************************************/ - - - /***** parse_val ************************************************************/ - /** - * @brief returns an unsigned int from a string - * @param[in] str string to convert to uint - * @return unsigned int form of string - * - */ - unsigned int parse_val(const std::string& str) { +} + +/***** cores_available ******************************************************/ + + +/***** parse_val ************************************************************/ +/** + * @brief returns an unsigned int from a string + * @param[in] str string to convert to uint + * @return unsigned int form of string + * + */ +unsigned int parse_val(const std::string &str) { std::stringstream v; - unsigned int u=0; - if ( (str.find("0x")!=std::string::npos) || (str.find("0X")!=std::string::npos)) - v << std::hex << str; + unsigned int u = 0; + if ((str.find("0x") != std::string::npos) || (str.find("0X") != std::string::npos)) + v << std::hex << str; else - v << std::dec << str; + v << std::dec << str; v >> u; return u; - } - /***** parse_val ************************************************************/ - - - /***** Tokenize *************************************************************/ - /** - * @brief break a string into a vector - * @param[in] str input string - * @param[out] tokens vector of tokens - * @param[in] delimiters delimiters - * @return number of tokens - * - */ - int Tokenize(const std::string& str, std::vector& tokens, const std::string& delimiters) { +} + +/***** parse_val ************************************************************/ + + +/***** Tokenize *************************************************************/ +/** + * @brief break a string into a vector + * @param[in] str input string + * @param[out] tokens vector of tokens + * @param[in] delimiters delimiters + * @return number of tokens + * + */ +int Tokenize(const std::string &str, std::vector &tokens, const std::string &delimiters) { // Clear the tokens vector, we only want to putput new tokens tokens.clear(); // If the string is zero length, return now with no tokens - if (str.empty()) { return(0); } + if (str.empty()) { return (0); } // Skip delimiters at beginning. std::string::size_type lastPos = str.find_first_not_of(delimiters, 0); // Find first "non-delimiter". - std::string::size_type pos = str.find_first_of(delimiters, lastPos); + std::string::size_type pos = str.find_first_of(delimiters, lastPos); std::string quote("\""); unsigned int quote_start = str.find(quote); //finds first quote mark bool quotes_found = false; if (quote_start <= str.length()) { - } - else { - quote_start = -1; + } else { + quote_start = -1; } while (std::string::npos != pos || std::string::npos != lastPos) { - if (quotes_found) { - tokens.push_back(str.substr(lastPos + 1, pos - lastPos - 2)); - pos++; - lastPos = str.find_first_not_of(delimiters, pos); - quotes_found = false; - } - else { - // Found a token, add it to the vector. - tokens.push_back(str.substr(lastPos, pos - lastPos)); - - // Skip delimiters. Note the "not_of" - lastPos = str.find_first_not_of(delimiters, pos); - } - - // If the next character is a quote, grab between the quotes - if (std::string::npos != lastPos && lastPos == quote_start){ - pos = str.find_first_of('\"', lastPos + 1) + 1; - quotes_found = true; - } - // Otherwise, find next "non-delimiter" - else { - pos = str.find_first_of(delimiters, lastPos); - } + if (quotes_found) { + tokens.push_back(str.substr(lastPos + 1, pos - lastPos - 2)); + pos++; + lastPos = str.find_first_not_of(delimiters, pos); + quotes_found = false; + } else { + // Found a token, add it to the vector. + tokens.push_back(str.substr(lastPos, pos - lastPos)); + + // Skip delimiters. Note the "not_of" + lastPos = str.find_first_not_of(delimiters, pos); + } + + // If the next character is a quote, grab between the quotes + if (std::string::npos != lastPos && lastPos == quote_start) { + pos = str.find_first_of('\"', lastPos + 1) + 1; + quotes_found = true; + } + // Otherwise, find next "non-delimiter" + else { + pos = str.find_first_of(delimiters, lastPos); + } } return static_cast(tokens.size()); - } - /***** Tokenize *************************************************************/ - - - /***** Tokenize *************************************************************/ - /** - * @brief break a string into device list and arg list vectors - * @param[in] str reference to input string - * @param[in] devlist reference to vector for device list - * @param[in] ndev reference to number of devices - * @param[out] arglist reference to vector for arg list - * @param[in] narg reference to number of args - * - * This is a special version of Tokenize. - * - * The expected format is a comma-delimited device list, followed by a colon, - * followed by a space-delimited argument list. - * - * On error, ndev is set to -1 - * - */ - void Tokenize( const std::string &str, std::vector &devlist, int &ndev, std::vector &arglist, int &narg ) { - - devlist.clear(); ndev=0; // empty the dev and arg list vectors - arglist.clear(); narg=0; - - std::size_t devdelim = str.find( ':' ); // position of device delimiter +} + +/***** Tokenize *************************************************************/ + + +/***** Tokenize *************************************************************/ +/** + * @brief break a string into device list and arg list vectors + * @param[in] str reference to input string + * @param[in] devlist reference to vector for device list + * @param[in] ndev reference to number of devices + * @param[out] arglist reference to vector for arg list + * @param[in] narg reference to number of args + * + * This is a special version of Tokenize. + * + * The expected format is a comma-delimited device list, followed by a colon, + * followed by a space-delimited argument list. + * + * On error, ndev is set to -1 + * + */ +void Tokenize(const std::string &str, std::vector &devlist, int &ndev, std::vector &arglist, + int &narg) { + devlist.clear(); + ndev = 0; // empty the dev and arg list vectors + arglist.clear(); + narg = 0; + + std::size_t devdelim = str.find(':'); // position of device delimiter // If there is a device delimiter then build a vector of the device numbers // - if ( devdelim != std::string::npos ) { - std::string dev_str = str.substr( 0, str.find( ':' ) ); - std::vector tokens; - Tokenize( dev_str, tokens, "," ); // Tokenize the dev string on the comma "," - for ( const auto &tok : tokens ) { - try { - devlist.push_back( std::stoi( tok ) ); + if (devdelim != std::string::npos) { + std::string dev_str = str.substr(0, str.find(':')); + std::vector tokens; + Tokenize(dev_str, tokens, ","); // Tokenize the dev string on the comma "," + for (const auto &tok: tokens) { + try { + devlist.push_back(std::stoi(tok)); + } catch (std::invalid_argument &) { + ndev = -1; + return; + } + catch (std::out_of_range &) { + ndev = -1; + return; + } } - catch (std::invalid_argument &) { - ndev=-1; - return; - } - catch ( std::out_of_range & ) { - ndev=-1; - return; - } - } - ndev = static_cast(devlist.size()); + ndev = static_cast(devlist.size()); } // Anything left, look for space-delimited tokens for the arg list // - std::string arg_str = str.substr( devdelim+1 ); + std::string arg_str = str.substr(devdelim + 1); std::vector tokens; - Tokenize( arg_str, tokens, " " ); // Tokenize the arg string on the space " " - for ( const auto &tok : tokens ) { - arglist.push_back( tok ); + Tokenize(arg_str, tokens, " "); // Tokenize the arg string on the space " " + for (const auto &tok: tokens) { + arglist.push_back(tok); } narg = static_cast(arglist.size()); - } - /***** Tokenize *************************************************************/ - - - /***** chrrep ***************************************************************/ - /** - * @brief replace one character within a string with a new character - * @param[out] str pointer to string - * @param[in] oldchr the old character to replace in the string - * @param[in] newchr the replacement value - * - * This function modifies the original string pointed to by *str. - * - */ - void chrrep(char *str, char oldchr, char newchr) { +} + +/***** Tokenize *************************************************************/ + + +/***** chrrep ***************************************************************/ +/** + * @brief replace one character within a string with a new character + * @param[out] str pointer to string + * @param[in] oldchr the old character to replace in the string + * @param[in] newchr the replacement value + * + * This function modifies the original string pointed to by *str. + * + */ +void chrrep(char *str, char oldchr, char newchr) { char *p = str; - int i=0; - while ( *p ) { - if (*p == oldchr) { - // special case for DEL character. move string over by one char - // - if (newchr == 127) { // if newchr==DEL copy memory after that chr - memmove(&str[i], &str[i+1], strlen(str)-i); - } - else { // otherwise just replace chr - *p = newchr; + int i = 0; + while (*p) { + if (*p == oldchr) { + // special case for DEL character. move string over by one char + // + if (newchr == 127) { + // if newchr==DEL copy memory after that chr + memmove(&str[i], &str[i + 1], strlen(str) - i); + } else { + // otherwise just replace chr + *p = newchr; + } } - } - ++p; i++; // increment pointer and byte counter + ++p; + i++; // increment pointer and byte counter } - } - /***** chrrep ***************************************************************/ - - - /***** string_replace_char **************************************************/ - /** - * @brief replace one character within a std::string with a new character - * @param[out] str reference to string to modify - * @param[in] oldchar the char to replace - * @param[in] newchar the replacement value - * - */ - void string_replace_char(std::string &str, const char *oldchar, const char *newchar) { +} + +/***** chrrep ***************************************************************/ + + +/***** string_replace_char **************************************************/ +/** + * @brief replace one character within a std::string with a new character + * @param[out] str reference to string to modify + * @param[in] oldchar the char to replace + * @param[in] newchar the replacement value + * + */ +void string_replace_char(std::string &str, const char *oldchar, const char *newchar) { while (str.find(oldchar) != std::string::npos) { - if ( str.find(oldchar) != std::string::npos ) str.replace(str.find(oldchar), 1, newchar); + if (str.find(oldchar) != std::string::npos) str.replace(str.find(oldchar), 1, newchar); } - } - /***** string_replace_char **************************************************/ - - - /***** get_time *************************************************************/ - /** - * @brief gets the current time and returns values by reference - * @details call get_time with tmzone_cfg, which is the optional time zone - * set by config file. If not set then UTC is used. - * @param[out] year int reference for year - * @param[out] mon int reference for month - * @param[out] mday int reference for day - * @param[out] hour int reference for hour - * @param[out] min int reference for minute - * @param[out] sec int reference for second - * @param[out] usec int reference for microsecond - * @return 1=error, 0=okay - * - * This function is overloaded - * - */ - long get_time( int &year, int &mon, int &mday, int &hour, int &min, int &sec, int &usec ) { - return get_time( tmzone_cfg, year, mon, mday, hour, min, sec, usec ); - } - /***** get_time *************************************************************/ - - - /***** get_time *************************************************************/ - /** - * @brief gets the current time and returns values by reference - * @param[in] tmzone_in optional time zone {local|UTC|} - * @param[out] year reference to year - * @param[out] mon reference to month - * @param[out] mday reference to day - * @param[out] hour reference to hour - * @param[out] min reference to minute - * @param[out] sec reference to second - * @param[out] usec reference to microsecond - * @return 1=error, 0=okay - * - * This function is overloaded - * - */ - long get_time( const std::string &tmzone_in, int &year, int &mon, int &mday, int &hour, int &min, int &sec, int &usec ) { - std::stringstream current_time; // String to contain the time - std::time_t t = std::time(nullptr); // Container for system time - timespec timenow{}; // Time of day container - tm mytime{}; // GMT time container +} + +/***** string_replace_char **************************************************/ + + +/***** get_time *************************************************************/ +/** + * @brief gets the current time and returns values by reference + * @details call get_time with tmzone_cfg, which is the optional time zone + * set by config file. If not set then UTC is used. + * @param[out] year int reference for year + * @param[out] mon int reference for month + * @param[out] mday int reference for day + * @param[out] hour int reference for hour + * @param[out] min int reference for minute + * @param[out] sec int reference for second + * @param[out] usec int reference for microsecond + * @return 1=error, 0=okay + * + * This function is overloaded + * + */ +long get_time(int &year, int &mon, int &mday, int &hour, int &min, int &sec, int &usec) { + return get_time(tmzone_cfg, year, mon, mday, hour, min, sec, usec); +} + +/***** get_time *************************************************************/ + + +/***** get_time *************************************************************/ +/** + * @brief gets the current time and returns values by reference + * @param[in] tmzone_in optional time zone {local|UTC|} + * @param[out] year reference to year + * @param[out] mon reference to month + * @param[out] mday reference to day + * @param[out] hour reference to hour + * @param[out] min reference to minute + * @param[out] sec reference to second + * @param[out] usec reference to microsecond + * @return 1=error, 0=okay + * + * This function is overloaded + * + */ +long get_time(const std::string &tmzone_in, int &year, int &mon, int &mday, int &hour, int &min, int &sec, int &usec) { + std::stringstream current_time; // String to contain the time + std::time_t t = std::time(nullptr); // Container for system time + timespec timenow{}; // Time of day container + tm mytime{}; // GMT time container long error = 0; // Get the system time, return a bad timestamp on error // - if ( clock_gettime( CLOCK_REALTIME, &timenow ) != 0 ) error = 1; + if (clock_gettime(CLOCK_REALTIME, &timenow) != 0) error = 1; // Convert the time of day to local or GMT // - if ( !error ) { - t = timenow.tv_sec; - if ( tmzone_in == "local" ) { if ( localtime_r( &t, &mytime ) == nullptr ) error = 1; } - else { - if ( gmtime_r( &t, &mytime ) == nullptr ) error = 1; - } + if (!error) { + t = timenow.tv_sec; + if (tmzone_in == "local") { if (localtime_r(&t, &mytime) == nullptr) error = 1; } else { + if (gmtime_r(&t, &mytime) == nullptr) error = 1; + } } // prepare the return values // - if ( error == 0 ) { + if (error == 0) { year = mytime.tm_year + 1900; - mon = mytime.tm_mon + 1; + mon = mytime.tm_mon + 1; mday = mytime.tm_mday; hour = mytime.tm_hour; - min = mytime.tm_min; - sec = mytime.tm_sec; - usec = (int)(timenow.tv_nsec/1000); - } - else { - year = 9999; - mon = 99; - mday = 99; - hour = 99; - min = 99; - sec = 99; - usec = 999999; + min = mytime.tm_min; + sec = mytime.tm_sec; + usec = (int) (timenow.tv_nsec / 1000); + } else { + year = 9999; + mon = 99; + mday = 99; + hour = 99; + min = 99; + sec = 99; + usec = 999999; } - return( error ); - } - /***** get_time *************************************************************/ - - - /***** timestamp_from *******************************************************/ - /** - * @brief get a human-readable timestamp from a timespec struct - * @details call timestamp_from with tmzone_cfg, which is the optional time zone - * set by config file. If not set then UTC is used. - * The timespec struct must have been filled before calling - * this function. This function only gets the time from it. This - * is used when multiple functions might need to do things all - * with the same time and you don't want the time to differ by - * even a fraction of a second between those operations. - * @param[in] time_in reference to a filled timespec struct - * @return string YYYY-MM-DDTHH:MM:SS.sss - * - * This function is overloaded - * - */ - std::string timestamp_from( struct timespec &time_in ) { - return timestamp_from( tmzone_cfg, time_in ); - } - /***** timestamp_from *******************************************************/ - - - /***** timestamp_from *******************************************************/ - /** - * @brief get a human-readable timestamp from a timespec struct - * @details The timespec struct must have been filled before calling - * this function. This function only gets the time from it. This - * is used when multiple functions might need to do things all - * with the same time and you don't want the time to differ by - * even a fraction of a second between those operations. - * @param[in] tmzone_in optional time zone {local|UTC|} - * @param[in] time_in reference to a filled timespec struct - * @return string YYYY-MM-DDTHH:MM:SS.sss - * - * This function is overloaded - * - */ - std::string timestamp_from( const std::string &tmzone_in, struct timespec &time_in ) { - std::stringstream current_time; // String to contain the time - std::time_t t=std::time(nullptr); // Container for system time - tm time{}; // time container + return (error); +} + +/***** get_time *************************************************************/ + + +/***** timestamp_from *******************************************************/ +/** + * @brief get a human-readable timestamp from a timespec struct + * @details call timestamp_from with tmzone_cfg, which is the optional time zone + * set by config file. If not set then UTC is used. + * The timespec struct must have been filled before calling + * this function. This function only gets the time from it. This + * is used when multiple functions might need to do things all + * with the same time and you don't want the time to differ by + * even a fraction of a second between those operations. + * @param[in] time_in reference to a filled timespec struct + * @return string YYYY-MM-DDTHH:MM:SS.sss + * + * This function is overloaded + * + */ +std::string timestamp_from(struct timespec &time_in) { + return timestamp_from(tmzone_cfg, time_in); +} + +/***** timestamp_from *******************************************************/ + + +/***** timestamp_from *******************************************************/ +/** + * @brief get a human-readable timestamp from a timespec struct + * @details The timespec struct must have been filled before calling + * this function. This function only gets the time from it. This + * is used when multiple functions might need to do things all + * with the same time and you don't want the time to differ by + * even a fraction of a second between those operations. + * @param[in] tmzone_in optional time zone {local|UTC|} + * @param[in] time_in reference to a filled timespec struct + * @return string YYYY-MM-DDTHH:MM:SS.sss + * + * This function is overloaded + * + */ +std::string timestamp_from(const std::string &tmzone_in, struct timespec &time_in) { + std::stringstream current_time; // String to contain the time + std::time_t t = std::time(nullptr); // Container for system time + tm time{}; // time container // Convert the input time to local or GMT // t = time_in.tv_sec; - if ( tmzone_in == "local" ) { if ( localtime_r( &t, &time ) == nullptr ) return( "9999-99-99T99:99:99.999999" ); } - else { if ( gmtime_r( &t, &time ) == nullptr ) return( "9999-99-99T99:99:99.999999" ); } + if (tmzone_in == "local") { if (localtime_r(&t, &time) == nullptr) return ("9999-99-99T99:99:99.999999"); } else { + if (gmtime_r(&t, &time) == nullptr) return ("9999-99-99T99:99:99.999999"); + } current_time.setf(std::ios_base::right); current_time << std::setfill('0') << std::setprecision(0) - << std::setw(4) << time.tm_year + 1900 << "-" - << std::setw(2) << time.tm_mon + 1 << "-" - << std::setw(2) << time.tm_mday << "T" - << std::setw(2) << time.tm_hour << ":" - << std::setw(2) << time.tm_min << ":" - << std::setw(2) << time.tm_sec << "." - << std::setw(6) << time_in.tv_nsec/1000; - - return(current_time.str()); - } - /***** timestamp_from *******************************************************/ - - - /***** get_system_date ******************************************************/ - /** - * @brief return current date in formatted string "YYYYMMDD" - * @details call get_system_date with tmzone_cfg, which is the optional time zone - * set by config file. If not set then UTC is used. - * @return string YYYYMMDD - * - * This function is overloaded - * - */ - std::string get_system_date() { - return get_system_date( tmzone_cfg ); - } - /***** get_system_date ******************************************************/ - - - /***** get_system_date ******************************************************/ - /** - * @brief return current date in formatted string "YYYYMMDD" - * @param[in] tmzone_in optional time zone {local|UTC|} - * @return string YYYYMMDD - * - * This function is overloaded - * - */ - std::string get_system_date( const std::string &tmzone_in ) { - std::stringstream current_date; // String to contain the return value - std::time_t t=std::time(nullptr); // Container for system time - timespec timenow{}; // Time of day container - tm mytime{}; // time container + << std::setw(4) << time.tm_year + 1900 << "-" + << std::setw(2) << time.tm_mon + 1 << "-" + << std::setw(2) << time.tm_mday << "T" + << std::setw(2) << time.tm_hour << ":" + << std::setw(2) << time.tm_min << ":" + << std::setw(2) << time.tm_sec << "." + << std::setw(6) << time_in.tv_nsec / 1000; + + return (current_time.str()); +} + +/***** timestamp_from *******************************************************/ + + +/***** get_system_date ******************************************************/ +/** + * @brief return current date in formatted string "YYYYMMDD" + * @details call get_system_date with tmzone_cfg, which is the optional time zone + * set by config file. If not set then UTC is used. + * @return string YYYYMMDD + * + * This function is overloaded + * + */ +std::string get_system_date() { + return get_system_date(tmzone_cfg); +} + +/***** get_system_date ******************************************************/ + + +/***** get_system_date ******************************************************/ +/** + * @brief return current date in formatted string "YYYYMMDD" + * @param[in] tmzone_in optional time zone {local|UTC|} + * @return string YYYYMMDD + * + * This function is overloaded + * + */ +std::string get_system_date(const std::string &tmzone_in) { + std::stringstream current_date; // String to contain the return value + std::time_t t = std::time(nullptr); // Container for system time + timespec timenow{}; // Time of day container + tm mytime{}; // time container // Get the system time, return a bad datestamp on error // - if ( clock_gettime( CLOCK_REALTIME, &timenow ) != 0 ) return( "99999999" ); + if (clock_gettime(CLOCK_REALTIME, &timenow) != 0) return ("99999999"); // Convert the time of day to local or GMT // t = timenow.tv_sec; - if ( tmzone_in == "local" ) { if ( localtime_r( &t, &mytime ) == nullptr ) return( "9999-99-99T99:99:99.999999" ); } - else { if ( gmtime_r( &t, &mytime ) == nullptr ) return( "9999-99-99T99:99:99.999999" ); } + if (tmzone_in == "local") { if (localtime_r(&t, &mytime) == nullptr) return ("9999-99-99T99:99:99.999999"); } else { + if (gmtime_r(&t, &mytime) == nullptr) return ("9999-99-99T99:99:99.999999"); + } current_date.setf(std::ios_base::right); current_date << std::setfill('0') << std::setprecision(0) - << std::setw(4) << mytime.tm_year + 1900 - << std::setw(2) << mytime.tm_mon + 1 - << std::setw(2) << mytime.tm_mday; - - return( current_date.str() ); - } - /***** get_system_date ******************************************************/ - - - /***** get_file_time ********************************************************/ - /** - * @brief return current time in formatted string "YYYYMMDDHHMMSS" - * @return string YYYYMMDDHHMMSS - * - * Used for filenames - * - */ - std::string get_file_time() { - return get_file_time( tmzone_cfg ); - } - /***** get_file_time ********************************************************/ - - - /***** get_file_time ********************************************************/ - /** - * @brief return current time in formatted string "YYYYMMDDHHMMSS" - * @param[in] tmzone_in optional time zone {local|UTC|} - * @return string YYYYMMDDHHMMSS - * - * Used for filenames - * - */ - std::string get_file_time( const std::string &tmzone_in ) { - std::stringstream current_time; // String to contain the time - std::time_t t=std::time(nullptr); // Container for system time - timespec timenow{}; // Time of day container - tm mytime{}; // time container + << std::setw(4) << mytime.tm_year + 1900 + << std::setw(2) << mytime.tm_mon + 1 + << std::setw(2) << mytime.tm_mday; + + return (current_date.str()); +} + +/***** get_system_date ******************************************************/ + + +/***** get_file_time ********************************************************/ +/** + * @brief return current time in formatted string "YYYYMMDDHHMMSS" + * @return string YYYYMMDDHHMMSS + * + * Used for filenames + * + */ +std::string get_file_time() { + return get_file_time(tmzone_cfg); +} + +/***** get_file_time ********************************************************/ + + +/***** get_file_time ********************************************************/ +/** + * @brief return current time in formatted string "YYYYMMDDHHMMSS" + * @param[in] tmzone_in optional time zone {local|UTC|} + * @return string YYYYMMDDHHMMSS + * + * Used for filenames + * + */ +std::string get_file_time(const std::string &tmzone_in) { + std::stringstream current_time; // String to contain the time + std::time_t t = std::time(nullptr); // Container for system time + timespec timenow{}; // Time of day container + tm mytime{}; // time container // Get the system time, return a bad timestamp on error // - if ( clock_gettime( CLOCK_REALTIME, &timenow ) != 0 ) return( "99999999999999" ); + if (clock_gettime(CLOCK_REALTIME, &timenow) != 0) return ("99999999999999"); // Convert the time of day to local or GMT // t = timenow.tv_sec; - if ( tmzone_in == "local" ) { if ( localtime_r( &t, &mytime ) == nullptr ) return( "99999999999999" ); } - else { if ( gmtime_r( &t, &mytime ) == nullptr ) return( "99999999999999" ); } + if (tmzone_in == "local") { if (localtime_r(&t, &mytime) == nullptr) return ("99999999999999"); } else { + if (gmtime_r(&t, &mytime) == nullptr) return ("99999999999999"); + } current_time.setf(std::ios_base::right); current_time << std::setfill('0') << std::setprecision(0) - << std::setw(4) << mytime.tm_year + 1900 - << std::setw(2) << mytime.tm_mon + 1 - << std::setw(2) << mytime.tm_mday - << std::setw(2) << mytime.tm_hour - << std::setw(2) << mytime.tm_min - << std::setw(2) << mytime.tm_sec; - - return(current_time.str()); - } - /***** get_file_time ********************************************************/ - - - /***** get_clock_time *******************************************************/ - /** - * @brief get the current clock time using REALTIME flag from the processor - * @return time in seconds - * - */ - double get_clock_time() { - timespec data{}; // Container for the current time + << std::setw(4) << mytime.tm_year + 1900 + << std::setw(2) << mytime.tm_mon + 1 + << std::setw(2) << mytime.tm_mday + << std::setw(2) << mytime.tm_hour + << std::setw(2) << mytime.tm_min + << std::setw(2) << mytime.tm_sec; + + return (current_time.str()); +} + +/***** get_file_time ********************************************************/ + + +/***** get_clock_time *******************************************************/ +/** + * @brief get the current clock time using REALTIME flag from the processor + * @return time in seconds + * + */ +double get_clock_time() { + timespec data{}; // Container for the current time if (clock_gettime(CLOCK_REALTIME, &data) != 0) return 0; - return ( static_cast(data.tv_sec) + (static_cast(data.tv_nsec) / 1000000000.0) ); - } - /***** get_clock_time *******************************************************/ - - - /***** timeout **************************************************************/ - /** - * @brief sleeps integral number of minutes or seconds - * @param[in] wholesec an optional number of integral seconds to sleep first - * @param[in] next string "sec" or "min" to return on integral sec or min - * @return 0 on success, 1 on error - * - */ - long timeout( int wholesec, const std::string &next ) { - - std::time_t t=std::time(nullptr); // Container for system time - timespec timenow{}; // Time of day container - tm mytime{}; // GMT time container - long error=0; - int nsec=0; - int sec=0; + return (static_cast(data.tv_sec) + (static_cast(data.tv_nsec) / 1000000000.0)); +} + +/***** get_clock_time *******************************************************/ + + +/***** timeout **************************************************************/ +/** + * @brief sleeps integral number of minutes or seconds + * @param[in] wholesec an optional number of integral seconds to sleep first + * @param[in] next string "sec" or "min" to return on integral sec or min + * @return 0 on success, 1 on error + * + */ +long timeout(int wholesec, const std::string &next) { + std::time_t t = std::time(nullptr); // Container for system time + timespec timenow{}; // Time of day container + tm mytime{}; // GMT time container + long error = 0; + int nsec = 0; + int sec = 0; // sleep for any requested whole number of seconds // - if ( wholesec > 0 ) std::this_thread::sleep_for( std::chrono::seconds( wholesec ) ); + if (wholesec > 0) std::this_thread::sleep_for(std::chrono::seconds(wholesec)); // Get the system time, return a bad timestamp on error // - if ( clock_gettime( CLOCK_REALTIME, &timenow ) != 0 ) error = 1; + if (clock_gettime(CLOCK_REALTIME, &timenow) != 0) error = 1; // Convert the time of day to local or GMT // - if ( !error ) { - t = timenow.tv_sec; - if ( gmtime_r( &t, &mytime ) == nullptr ) error = 1; - sec = mytime.tm_sec; // current second - nsec = static_cast(timenow.tv_nsec); // current nanosecond + if (!error) { + t = timenow.tv_sec; + if (gmtime_r(&t, &mytime) == nullptr) error = 1; + sec = mytime.tm_sec; // current second + nsec = static_cast(timenow.tv_nsec); // current nanosecond } // sleep for the required fraction to get to the next whole number // of sec or min, as requested // - if ( !error && next == "sec" ) { - if (nsec < 999999999) { - std::this_thread::sleep_for( std::chrono::nanoseconds( 999999999-nsec ) ); - } - } else if ( !error && next == "min" ) { - if (sec < 59) { - std::this_thread::sleep_for( std::chrono::seconds( 59-sec ) ); - } - if (nsec < 999999999) { - std::this_thread::sleep_for( std::chrono::nanoseconds( 999999999-nsec ) ); - } + if (!error && next == "sec") { + if (nsec < 999999999) { + std::this_thread::sleep_for(std::chrono::nanoseconds(999999999 - nsec)); + } + } else if (!error && next == "min") { + if (sec < 59) { + std::this_thread::sleep_for(std::chrono::seconds(59 - sec)); + } + if (nsec < 999999999) { + std::this_thread::sleep_for(std::chrono::nanoseconds(999999999 - nsec)); + } } return error; - } - /***** timeout **************************************************************/ - - - /***** mjd_from *************************************************************/ - /** - * @brief return Modified Julian Date for time in a timespec struct - * @details The input timespec struct must have been filled before calling - * this function. This function only calculates the MJD from it. - * - * I got this from ZTF's astronomy.h which says it came from - * Meeus' Astronomical Formulae for Calculators. The two JD - * conversion routines routines were replaced 1998 November 29 - * to avoid inclusion of copyrighted "Numerical Recipes" code. - * A test of 1 million random JDs between 1585 and 3200 AD gave - * the same conversions as the NR routines. - * @param[in] time_in reference to timespec struct filled by clock_gettime() - * @return double, 0 on error - * - */ - double mjd_from( timespec &time_in ) { - std::time_t t=std::time(nullptr); // Container for system time - tm time{}; // GMT time container +} + +/***** timeout **************************************************************/ + + +/***** mjd_from *************************************************************/ +/** + * @brief return Modified Julian Date for time in a timespec struct + * @details The input timespec struct must have been filled before calling + * this function. This function only calculates the MJD from it. + * + * I got this from ZTF's astronomy.h which says it came from + * Meeus' Astronomical Formulae for Calculators. The two JD + * conversion routines routines were replaced 1998 November 29 + * to avoid inclusion of copyrighted "Numerical Recipes" code. + * A test of 1 million random JDs between 1585 and 3200 AD gave + * the same conversions as the NR routines. + * @param[in] time_in reference to timespec struct filled by clock_gettime() + * @return double, 0 on error + * + */ +double mjd_from(timespec &time_in) { + std::time_t t = std::time(nullptr); // Container for system time + tm time{}; // GMT time container // Convert the input time to GMT // t = time_in.tv_sec; - if ( gmtime_r( &t, &time ) == nullptr ) return 0.; + if (gmtime_r(&t, &time) == nullptr) return 0.; double a = std::floor((14 - (time.tm_mon + 1)) / 12); double y = (time.tm_year + 1900) + 4800 - a; @@ -636,285 +656,295 @@ std::mutex generate_tmpfile_mtx; + time.tm_sec / 86400. + (static_cast(time_in.tv_nsec) / 1000000000.) / 86400.; - return( jd - 2400000.5 ); - } - /***** mjd_from *************************************************************/ - - - /***** compare_versions *****************************************************/ - /** - * @brief compares two version numbers represented as strings - * @param[in] v1 the first version number string to be compared - * @param[in] v2 the second version number string to be compared - * @return 0,1,-1,-999 - * - * This function compares version or revision numbers which are represented - * as strings which contain decimals. Each portion of a version number is - * compared. - * - * Returns 1 if v2 is smaller, -1 if v1 is smaller, 0 if equal - * Returns -999 on error - * - * Revision numbers can be like n.n.n etc with any number of numbers n, - * but must be numbers. - * - */ - int compare_versions(const std::string &v1, const std::string &v2) { + return (jd - 2400000.5); +} + +/***** mjd_from *************************************************************/ + + +/***** compare_versions *****************************************************/ +/** + * @brief compares two version numbers represented as strings + * @param[in] v1 the first version number string to be compared + * @param[in] v2 the second version number string to be compared + * @return 0,1,-1,-999 + * + * This function compares version or revision numbers which are represented + * as strings which contain decimals. Each portion of a version number is + * compared. + * + * Returns 1 if v2 is smaller, -1 if v1 is smaller, 0 if equal + * Returns -999 on error + * + * Revision numbers can be like n.n.n etc with any number of numbers n, + * but must be numbers. + * + */ +int compare_versions(const std::string &v1, const std::string &v2) { std::vector tokens1; std::vector tokens2; // Tokenize the version numbers, using the decimal point "." as the separator // - Tokenize( v1, tokens1, "." ); - Tokenize( v2, tokens2, "." ); + Tokenize(v1, tokens1, "."); + Tokenize(v2, tokens2, "."); // Compare each token. // As soon as one is greater than the other, then return. // - for (size_t i=0,j=0; ( i < tokens1.size() && j < tokens2.size() ); i++,j++) { - try { - if ( std::stoi( tokens1.at(i) ) > std::stoi( tokens2.at(j) ) ) return 1; - if ( std::stoi( tokens2.at(j) ) > std::stoi( tokens1.at(i) ) ) return -1; - } - catch (std::invalid_argument &) { - return( -999 ); - } - catch ( std::out_of_range & ) { - return( -999 ); - } + for (size_t i = 0, j = 0; (i < tokens1.size() && j < tokens2.size()); i++, j++) { + try { + if (std::stoi(tokens1.at(i)) > std::stoi(tokens2.at(j))) return 1; + if (std::stoi(tokens2.at(j)) > std::stoi(tokens1.at(i))) return -1; + } catch (std::invalid_argument &) { + return (-999); + } + catch (std::out_of_range &) { + return (-999); + } } // If we finished the loop then either they were all equal or // one version had more tokens than the other (E.G., 1.1.123 vs 1.1). // The one with more tokens has to be greater. // - if ( tokens1.size() > tokens2.size() ) return 1; - if ( tokens2.size() > tokens1.size() ) return -1; - - return 0; // or they are equal - } - /***** compare_versions *****************************************************/ - - - /***** md5_file *************************************************************/ - /** - * @brief compute the md5sum of a file - * @details This makes use of an external source, md5.h and md5.c - * @param[in] filename const reference to filename to process - * @param[out] hash reference to a string to contain result - * - */ - long md5_file( const std::string &filename, std::string &hash ) { + if (tokens1.size() > tokens2.size()) return 1; + if (tokens2.size() > tokens1.size()) return -1; + + return 0; // or they are equal +} + +/***** compare_versions *****************************************************/ + + +/***** md5_file *************************************************************/ +/** + * @brief compute the md5sum of a file + * @details This makes use of an external source, md5.h and md5.c + * @param[in] filename const reference to filename to process + * @param[out] hash reference to a string to contain result + * + */ +long md5_file(const std::string &filename, std::string &hash) { MD5_CTX ctx; - md5_init( &ctx ); + md5_init(&ctx); try { - // open the input file stream - // - std::ifstream instream( filename, std::ios::binary ); - if ( ! instream.is_open() ) { - std::cerr << "ERROR opening file: " << filename << "\n"; - return 1; - } + // open the input file stream + // + std::ifstream instream(filename, std::ios::binary); + if (!instream.is_open()) { + std::cerr << "ERROR opening file: " << filename << "\n"; + return 1; + } - // process a byte at a time so the entire file never lives in memory - // use a vector for better cleanup - // - std::vector buffer(1, 0); - while ( instream.read(reinterpret_cast(buffer.data()), 1) ) { - md5_update( &ctx, buffer.data(), instream.gcount() ); - } + // process a byte at a time so the entire file never lives in memory + // use a vector for better cleanup + // + std::vector buffer(1, 0); + while (instream.read(reinterpret_cast(buffer.data()), 1)) { + md5_update(&ctx, buffer.data(), instream.gcount()); + } - instream.close(); - } - catch ( std::ifstream::failure &e ) { - std::cerr << "md5_file( " << filename << " ): " << e.what() << "\n"; - hash = "ERROR"; - return 1; + instream.close(); + } catch (std::ifstream::failure &e) { + std::cerr << "md5_file( " << filename << " ): " << e.what() << "\n"; + hash = "ERROR"; + return 1; } - catch ( std::exception &e ) { - std::cerr << "md5_file( " << filename << " ): " << e.what() << "\n"; - hash = "ERROR"; - return 1; + catch (std::exception &e) { + std::cerr << "md5_file( " << filename << " ): " << e.what() << "\n"; + hash = "ERROR"; + return 1; } BYTE result[MD5_BLOCK_SIZE]; - md5_final( &ctx, result ); + md5_final(&ctx, result); // convert result to a string // std::stringstream str; - for (unsigned char i : result) { - str << std::hex << std::setw(2) << std::setfill('0') << static_cast(i); + for (unsigned char i: result) { + str << std::hex << std::setw(2) << std::setfill('0') << static_cast(i); } hash = str.str(); return 0; - } - /***** md5_file *************************************************************/ - - - /***** is_owner *************************************************************/ - /** - * @brief does the user own the specified file? - * @details Check if the effective user ID matches the file's owner ID - * @param[in] filename const reference to filename to check - * @return true or false - * - */ - bool is_owner( const std::filesystem::path &filename ) { +} + +/***** md5_file *************************************************************/ + + +/***** is_owner *************************************************************/ +/** + * @brief does the user own the specified file? + * @details Check if the effective user ID matches the file's owner ID + * @param[in] filename const reference to filename to check + * @return true or false + * + */ +bool is_owner(const std::filesystem::path &filename) { struct stat fstat{}; - if ( stat( filename.c_str(), &fstat ) == 0 ) { - // Check if the effective user ID matches the file's owner ID - return geteuid() == fstat.st_uid; + if (stat(filename.c_str(), &fstat) == 0) { + // Check if the effective user ID matches the file's owner ID + return geteuid() == fstat.st_uid; } - return false; // Error handling: Unable to get file information - } - /***** is_owner *************************************************************/ - - - /***** has_write_permission *************************************************/ - /** - * @brief does the user have write permission on the specified file? - * @details Check if the effective user ID has write permission - * @param[in] filename const reference to filename to check - * @return true or false - * - */ - bool has_write_permission( const std::filesystem::path &filename ) { + return false; // Error handling: Unable to get file information +} + +/***** is_owner *************************************************************/ + + +/***** has_write_permission *************************************************/ +/** + * @brief does the user have write permission on the specified file? + * @details Check if the effective user ID has write permission + * @param[in] filename const reference to filename to check + * @return true or false + * + */ +bool has_write_permission(const std::filesystem::path &filename) { struct stat fstat{}; - if ( stat( filename.c_str(), &fstat ) == 0 ) { - // Check if the effective user ID has write permission - return fstat.st_mode & S_IWUSR; + if (stat(filename.c_str(), &fstat) == 0) { + // Check if the effective user ID has write permission + return fstat.st_mode & S_IWUSR; } - return false; // Error handling: Unable to get file information - } - /***** has_write_permission *************************************************/ - - - /***** tchar ****************************************************************/ - /** - * @brief return a printable string of a non-printable terminating char - * @param[in] str reference to input string - * @return string (static references are valid for the lifetime of the program) - * - */ - const std::string &tchar( const std::string &str ) { - static const std::string unknown = "??"; - static const std::string newline = "\\n"; + return false; // Error handling: Unable to get file information +} + +/***** has_write_permission *************************************************/ + + +/***** tchar ****************************************************************/ +/** + * @brief return a printable string of a non-printable terminating char + * @param[in] str reference to input string + * @return string (static references are valid for the lifetime of the program) + * + */ +const std::string &tchar(const std::string &str) { + static const std::string unknown = "??"; + static const std::string newline = "\\n"; static const std::string carreturn = "\\r"; - static const std::string nullchar = "\\0"; + static const std::string nullchar = "\\0"; - if ( str.empty() ) return unknown; + if (str.empty()) return unknown; - switch ( str.back() ) { - case '\n': return newline; - case '\r': return carreturn; - case '\0': return nullchar; - default : return unknown; + switch (str.back()) { + case '\n': return newline; + case '\r': return carreturn; + case '\0': return nullchar; + default: return unknown; } - } - /***** tchar ****************************************************************/ - - - /***** strip_control_characters *********************************************/ - /** - * @brief strip all leading and trailing control chars from a string - * @param[in] str input string - * @return input string without control chars - * - * str is cast to uchar for safety because iscntrl() requires uchar - * - */ - std::string strip_control_characters( const std::string &str ) { +} + +/***** tchar ****************************************************************/ + + +/***** strip_control_characters *********************************************/ +/** + * @brief strip all leading and trailing control chars from a string + * @param[in] str input string + * @return input string without control chars + * + * str is cast to uchar for safety because iscntrl() requires uchar + * + */ +std::string strip_control_characters(const std::string &str) { // locate leading control characters // size_t start = 0; - while ( start < str.length() && std::iscntrl( static_cast( str[start] ) ) ) { - ++start; + while (start < str.length() && std::iscntrl(static_cast(str[start]))) { + ++start; } // locate trailing control characters // size_t end = str.length(); - while ( end > start && std::iscntrl( static_cast( str[end - 1] ) ) ) { - --end; - } + while (end > start && std::iscntrl(static_cast(str[end - 1]))) { + --end; + } // return the substring without leading and trailing control characters // - return str.substr( start, end - start ); - } - /***** strip_control_characters *********************************************/ - - - /***** starts_with **********************************************************/ - /** - * @brief check if a string starts with a string literal - * @details this is here in case c++20 is not available - * @param[in] str reference to input string - * @param[in] prefix a read-only prefix string - * @return true or false - * - */ - bool starts_with( const std::string &str, std::string_view prefix ) { - return ( str.compare( 0, prefix.length(), prefix ) == 0 ); - } - /***** starts_with **********************************************************/ - - - /***** ends_with ************************************************************/ - /** - * @brief check if a string end with a string literal - * @details this is here in case c++20 is not available - * @param[in] str reference to input string - * @param[in] suffix a read-only suffix string - * @return true or false - * - */ - bool ends_with( const std::string &str, std::string_view suffix ) { - if ( str.length() < suffix.length() ) return false; - return str.substr( str.length() - suffix.length() ) == suffix; - } - /***** ends_with ************************************************************/ - - - /***** generate_temp_filename ***********************************************/ - /** - * @brief generates a temporary filename only, not a file - * @details mimmics tmpnam to more safely create a temporary filename - * @param[in] prefix prefix for the filename, /tmp/prefix.XXXXXX - * @return generated temporary filename - * - */ - std::string generate_temp_filename( const std::string &prefix ) { + return str.substr(start, end - start); +} + +/***** strip_control_characters *********************************************/ + + +/***** starts_with **********************************************************/ +/** + * @brief check if a string starts with a string literal + * @details this is here in case c++20 is not available + * @param[in] str reference to input string + * @param[in] prefix a read-only prefix string + * @return true or false + * + */ +bool starts_with(const std::string &str, std::string_view prefix) { + return (str.compare(0, prefix.length(), prefix) == 0); +} + +/***** starts_with **********************************************************/ + + +/***** ends_with ************************************************************/ +/** + * @brief check if a string end with a string literal + * @details this is here in case c++20 is not available + * @param[in] str reference to input string + * @param[in] suffix a read-only suffix string + * @return true or false + * + */ +bool ends_with(const std::string &str, std::string_view suffix) { + if (str.length() < suffix.length()) return false; + return str.substr(str.length() - suffix.length()) == suffix; +} + +/***** ends_with ************************************************************/ + + +/***** generate_temp_filename ***********************************************/ +/** + * @brief generates a temporary filename only, not a file + * @details mimmics tmpnam to more safely create a temporary filename + * @param[in] prefix prefix for the filename, /tmp/prefix.XXXXXX + * @return generated temporary filename + * + */ +std::string generate_temp_filename(const std::string &prefix) { std::string pattern = "/tmp/" + prefix + "XXXXXX"; - char* filename = strdup( pattern.c_str() ); // mkstemp requires a non-const pointer + char *filename = strdup(pattern.c_str()); // mkstemp requires a non-const pointer generate_tmpfile_mtx.lock(); - int fd = mkstemp( filename ); // create and open the file + int fd = mkstemp(filename); // create and open the file generate_tmpfile_mtx.unlock(); - if ( fd != -1 ) { - close(fd); // close the file immediately - std::string temp_filename = filename; // create a std::string - free( filename ); // cleanup and - unlink( temp_filename.c_str() ); // delete the file. - return temp_filename; + if (fd != -1) { + close(fd); // close the file immediately + std::string temp_filename = filename; // create a std::string + free(filename); // cleanup and + unlink(temp_filename.c_str()); // delete the file. + return temp_filename; } else { - free( filename ); + free(filename); return ""; } - } - /***** generate_temp_filename ***********************************************/ +} + +/***** generate_temp_filename ***********************************************/ /***** rtrim ***********************************************/ /** * @s string from which to trim trailing whitespaces * */ -void rtrim(std::string &s) { /// trim off trailing whitespace from a string - s.erase( std::find_if( s.rbegin(), s.rend(), [](unsigned char ch) { return !std::isspace(ch); } ).base(), s.end() ); - } -/***** rtrim ***********************************************/ \ No newline at end of file +void rtrim(std::string &s) { + /// trim off trailing whitespace from a string + s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) { return !std::isspace(ch); }).base(), s.end()); +} + +/***** rtrim ***********************************************/ diff --git a/utils/utilities.h b/utils/utilities.h index 892ced01..96a4bf10 100644 --- a/utils/utilities.h +++ b/utils/utilities.h @@ -30,85 +30,102 @@ #include #include -extern std::string tmzone_cfg; /// time zone if set in cfg file +extern std::string tmzone_cfg; /// time zone if set in cfg file extern std::mutex generate_tmpfile_mtx; -bool cmdOptionExists( char** begin, char** end, const std::string &option ); -char* getCmdOption( char** begin, char** end, const std::string &option ); +bool cmdOptionExists(char **begin, char **end, const std::string &option); + +char *getCmdOption(char **begin, char **end, const std::string &option); + int my_hardware_concurrency(); + int cores_available(); -inline int mod( int k, int n ) { return ( (k %= n) < 0 ) ? k+n : k; } +inline int mod(int k, int n) { return ((k %= n) < 0) ? k + n : k; } -unsigned int parse_val(const std::string& str); /// returns an unsigned int from a string +unsigned int parse_val(const std::string &str); /// returns an unsigned int from a string -int Tokenize(const std::string& str, - std::vector& tokens, - const std::string& delimiters); /// break a string into a vector +int Tokenize(const std::string &str, + std::vector &tokens, + const std::string &delimiters); /// break a string into a vector -void Tokenize(const std::string &str, - std::vector &devlist, - int &ndev, - std::vector &arglist, - int &narg ); +void Tokenize(const std::string &str, + std::vector &devlist, + int &ndev, + std::vector &arglist, + int &narg); -void chrrep(char *str, char oldchr, char newchr); /// replace one character within a string with a new character +void chrrep(char *str, char oldchr, char newchr); /// replace one character within a string with a new character void string_replace_char(std::string &str, const char *oldchar, const char *newchar); -long get_time( int &year, int &mon, int &mday, int &hour, int &min, int &sec, int &usec ); -long get_time( const std::string &tmzone_in, int &year, int &mon, int &mday, int &hour, int &min, int &sec, int &usec ); +long get_time(int &year, int &mon, int &mday, int &hour, int &min, int &sec, int &usec); + +long get_time(const std::string &tmzone_in, int &year, int &mon, int &mday, int &hour, int &min, int &sec, int &usec); -std::string timestamp_from( struct timespec &time_n ); /// return time from input timespec struct in formatted string "YYYY-MM-DDTHH:MM:SS.sss" -std::string timestamp_from( const std::string &tmzone_in, struct timespec &time_in ); +std::string timestamp_from(struct timespec &time_n); +/// return time from input timespec struct in formatted string "YYYY-MM-DDTHH:MM:SS.sss" +std::string timestamp_from(const std::string &tmzone_in, struct timespec &time_in); -inline std::string get_timestamp(const std::string &tz) { /// return current time in formatted string "YYYY-MM-DDTHH:MM:SS.sss" + +inline std::string get_timestamp(const std::string &tz) { + /// return current time in formatted string "YYYY-MM-DDTHH:MM:SS.sss" struct timespec timenow{}; - clock_gettime( CLOCK_REALTIME, &timenow ); - return timestamp_from( tz, timenow ); + clock_gettime(CLOCK_REALTIME, &timenow); + return timestamp_from(tz, timenow); } -inline std::string get_timestamp() { /// return current time in formatted string "YYYY-MM-DDTHH:MM:SS.sss" + +inline std::string get_timestamp() { + /// return current time in formatted string "YYYY-MM-DDTHH:MM:SS.sss" return get_timestamp(tmzone_cfg); } -std::string get_system_date(); /// return current date in formatted string "YYYYMMDD" -std::string get_system_date( const std::string &tmzone_in ); +std::string get_system_date(); /// return current date in formatted string "YYYYMMDD" +std::string get_system_date(const std::string &tmzone_in); -std::string get_file_time(); /// return current time in formatted string "YYYYMMDDHHMMSS" used for filenames -std::string get_file_time( const std::string &tmzone_in ); +std::string get_file_time(); /// return current time in formatted string "YYYYMMDDHHMMSS" used for filenames +std::string get_file_time(const std::string &tmzone_in); double get_clock_time(); -long timeout( int wholesec=0, const std::string &next="" ); /// wait until next integral second or minute +long timeout(int wholesec = 0, const std::string &next = ""); /// wait until next integral second or minute -double mjd_from( struct timespec &time_n ); /// modified Julian date from input timespec struct +double mjd_from(struct timespec &time_n); /// modified Julian date from input timespec struct -inline double mjd_now() { /// modified Julian date now +inline double mjd_now() { + /// modified Julian date now timespec timenow{}; - clock_gettime( CLOCK_REALTIME, &timenow ); - return( mjd_from( timenow ) ); + clock_gettime(CLOCK_REALTIME, &timenow); + return (mjd_from(timenow)); } int compare_versions(const std::string &v1, const std::string &v2); -long md5_file( const std::string &filename, std::string &hash ); /// compute md5 checksum of file +long md5_file(const std::string &filename, std::string &hash); /// compute md5 checksum of file + +bool is_owner(const std::filesystem::path &filename); -bool is_owner( const std::filesystem::path &filename ); -bool has_write_permission( const std::filesystem::path &filename ); -const std::string &tchar( const std::string &str ); -std::string strip_control_characters( const std::string &str ); -bool starts_with( const std::string &str, std::string_view prefix ); -bool ends_with( const std::string &str, std::string_view suffix ); -std::string generate_temp_filename( const std::string &prefix ); +bool has_write_permission(const std::filesystem::path &filename); + +const std::string &tchar(const std::string &str); + +std::string strip_control_characters(const std::string &str); + +bool starts_with(const std::string &str, std::string_view prefix); + +bool ends_with(const std::string &str, std::string_view suffix); + +std::string generate_temp_filename(const std::string &prefix); void rtrim(std::string &s); -inline bool caseCompareChar( char a, char b ) { return ( std::toupper(a) == std::toupper(b) ); } +inline bool caseCompareChar(char a, char b) { return (std::toupper(a) == std::toupper(b)); } -inline bool caseCompareString( const std::string &s1, const std::string &s2 ) { - return( (s1.size()==s2.size() ) && std::equal( s1.begin(), s1.end(), s2.begin(), caseCompareChar) ); } +inline bool caseCompareString(const std::string &s1, const std::string &s2) { + return ((s1.size() == s2.size()) && std::equal(s1.begin(), s1.end(), s2.begin(), caseCompareChar)); +} /***** to_string_prec *******************************************************/ @@ -122,13 +139,14 @@ inline bool caseCompareString( const std::string &s1, const std::string &s2 ) { * @return string * */ -template -std::string to_string_prec( const T value_in, const int prec = 6 ) { +template +std::string to_string_prec(const T value_in, const int prec = 6) { std::ostringstream out; out.precision(prec); out << std::fixed << value_in; return std::move(out).str(); } + /***** to_string_prec *******************************************************/ @@ -141,62 +159,70 @@ std::string to_string_prec( const T value_in, const int prec = 6 ) { * */ class InterruptableSleepTimer { - private: - std::timed_mutex _mut; - std::atomic _locked{}; // track whether the mutex is locked - std::atomic _run; - - void _lock() { _mut.lock(); _locked = true; } // lock mutex - - void _unlock() { _locked = false; _mut.unlock(); } // unlock mutex - - public: - // lock on creation - // - InterruptableSleepTimer() : _run(true) { - _lock(); +private: + std::timed_mutex _mut; + std::atomic _locked{}; // track whether the mutex is locked + std::atomic _run; + + void _lock() { + _mut.lock(); + _locked = true; + } // lock mutex + + void _unlock() { + _locked = false; + _mut.unlock(); + } // unlock mutex + +public: + // lock on creation + // + InterruptableSleepTimer() : _run(true) { + _lock(); + } + + // unlock on destruction, if wake was never called + // + ~InterruptableSleepTimer() { + if (_locked) { + _unlock(); + _run = false; } - - // unlock on destruction, if wake was never called - // - ~InterruptableSleepTimer() { - if ( _locked ) { - _unlock(); - _run = false; - } + } + + bool running() { return _run; } + + // called by any thread except the creator + // waits until wake is called or the specified time passes + // + template + void sleepFor(const std::chrono::duration &timeout_duration) { + if (_run && _mut.try_lock_for(timeout_duration)) { + // if successfully locked, remove the lock + // + _mut.unlock(); } - - bool running() { return _run; } - - // called by any thread except the creator - // waits until wake is called or the specified time passes - // - template< class Rep, class Period > - void sleepFor( const std::chrono::duration &timeout_duration ) { - if ( _run && _mut.try_lock_for( timeout_duration ) ) { - // if successfully locked, remove the lock - // - _mut.unlock(); - } + } + + // unblock any waiting threads, handling a situation + // where wake has already been called. + // should only be called by the creating thread + // + void stop() { + if (_locked) { + _run = false; + _unlock(); } + } - // unblock any waiting threads, handling a situation - // where wake has already been called. - // should only be called by the creating thread - // - void stop() { - if ( _locked ) { - _run = false; - _unlock(); - } - } - void start() { - if ( ! _locked ) { - _lock(); - _run = true; - } + void start() { + if (!_locked) { + _lock(); + _run = true; } + } }; + /***** InterruptableSleepTimer **********************************************/ @@ -209,11 +235,12 @@ class InterruptableSleepTimer { * */ class Time { - public: - static timespec getTimeNow() { - timespec timenow{}; - clock_gettime( CLOCK_REALTIME, &timenow ); - return timenow; - } +public: + static timespec getTimeNow() { + timespec timenow{}; + clock_gettime(CLOCK_REALTIME, &timenow); + return timenow; + } }; + /***** Time *****************************************************************/ From 970db062f96c95a725ac02fc0338655db1b5684c Mon Sep 17 00:00:00 2001 From: Michael Langmayr Date: Tue, 6 Aug 2024 11:14:25 -0700 Subject: [PATCH 2/6] update workflow branch --- .github/workflows/cmake-workflow.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cmake-workflow.yml b/.github/workflows/cmake-workflow.yml index 691e1d9c..d827414d 100644 --- a/.github/workflows/cmake-workflow.yml +++ b/.github/workflows/cmake-workflow.yml @@ -2,9 +2,9 @@ name: CMake workflow on: push: - branches: [ "master" ] + branches: [ "main" ] pull_request: - branches: [ "master" ] + branches: [ "main" ] env: # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) From f90285b0887b89e232d65cd333532c1b8164d03d Mon Sep 17 00:00:00 2001 From: Michael Langmayr Date: Tue, 6 Aug 2024 12:33:23 -0700 Subject: [PATCH 3/6] undo changes to archon.cpp because it's too large (8K lines) --- camerad/archon.cpp | 13579 +++++++++++++++++++++---------------------- 1 file changed, 6556 insertions(+), 7023 deletions(-) diff --git a/camerad/archon.cpp b/camerad/archon.cpp index 1aa8cb64..8e6cef22 100644 --- a/camerad/archon.cpp +++ b/camerad/archon.cpp @@ -10,7 +10,7 @@ #include // for std::stringstream #include // for setfil, setw, etc. #include // for hex, uppercase, etc. -#include +#include #include #include #include @@ -18,1812 +18,1731 @@ #include namespace Archon { - // Archon::Interface constructor - // - Interface::Interface() { - this->archon_busy = false; - this->modeselected = false; - this->firmwareloaded = false; - this->msgref = 0; - this->lastframe = 0; - this->frame.index = 0; - this->frame.next_index = 0; - this->abort = false; - this->taplines = 0; - this->image_data = nullptr; - this->image_data_bytes = 0; - this->image_data_allocated = 0; - this->is_longexposure = false; - this->is_window = false; - this->is_autofetch = false; - this->win_hstart = 0; - this->win_hstop = 2047; - this->win_vstart = 0; - this->win_vstop = 2047; - this->tapline0_store = ""; - this->taplines_store = 0; - - this->n_hdrshift = 16; - this->backplaneversion = ""; - - this->trigin_state = "disabled"; - this->trigin_expose = 0; - this->trigin_untimed = 0; - this->trigin_readout = 0; - - this->lastcubeamps = this->camera.cubeamps(); - - this->trigin_expose_enable = DEF_TRIGIN_EXPOSE_ENABLE; - this->trigin_expose_disable = DEF_TRIGIN_EXPOSE_DISABLE; - this->trigin_untimed_enable = DEF_TRIGIN_UNTIMED_ENABLE; - this->trigin_untimed_disable = DEF_TRIGIN_UNTIMED_DISABLE; - this->trigin_readout_enable = DEF_TRIGIN_READOUT_ENABLE; - this->trigin_readout_disable = DEF_TRIGIN_READOUT_DISABLE; - - this->shutenable_enable = DEF_SHUTENABLE_ENABLE; - this->shutenable_disable = DEF_SHUTENABLE_DISABLE; - - // pre-size the modtype and modversion vectors to hold the max number of modules - // - this->modtype.resize(nmods); - this->modversion.resize(nmods); - // TODO I should change these to STL maps instead - // - this->frame.bufsample.resize(Archon::nbufs); - this->frame.bufcomplete.resize(Archon::nbufs); - this->frame.bufmode.resize(Archon::nbufs); - this->frame.bufbase.resize(Archon::nbufs); - this->frame.bufframen.resize(Archon::nbufs); - this->frame.bufwidth.resize(Archon::nbufs); - this->frame.bufheight.resize(Archon::nbufs); - this->frame.bufpixels.resize(Archon::nbufs); - this->frame.buflines.resize(Archon::nbufs); - this->frame.bufrawblocks.resize(Archon::nbufs); - this->frame.bufrawlines.resize(Archon::nbufs); - this->frame.bufrawoffset.resize(Archon::nbufs); - this->frame.buftimestamp.resize(Archon::nbufs); - this->frame.bufretimestamp.resize(Archon::nbufs); - this->frame.buffetimestamp.resize(Archon::nbufs); - } + // Archon::Interface constructor + // + Interface::Interface() { + this->archon_busy = false; + this->modeselected = false; + this->firmwareloaded = false; + this->msgref = 0; + this->lastframe = 0; + this->frame.index = 0; + this->frame.next_index = 0; + this->abort = false; + this->taplines = 0; + this->image_data = nullptr; + this->image_data_bytes = 0; + this->image_data_allocated = 0; + this->is_longexposure = false; + this->is_window = false; + this->is_autofetch = false; + this->win_hstart = 0; + this->win_hstop = 2047; + this->win_vstart = 0; + this->win_vstop = 2047; + this->tapline0_store = ""; + this->taplines_store = 0; + + this->n_hdrshift = 16; + this->backplaneversion=""; + + this->trigin_state="disabled"; + this->trigin_expose = 0; + this->trigin_untimed = 0; + this->trigin_readout = 0; + + this->lastcubeamps = this->camera.cubeamps(); + + this->trigin_expose_enable = DEF_TRIGIN_EXPOSE_ENABLE; + this->trigin_expose_disable = DEF_TRIGIN_EXPOSE_DISABLE; + this->trigin_untimed_enable = DEF_TRIGIN_UNTIMED_ENABLE; + this->trigin_untimed_disable = DEF_TRIGIN_UNTIMED_DISABLE; + this->trigin_readout_enable = DEF_TRIGIN_READOUT_ENABLE; + this->trigin_readout_disable = DEF_TRIGIN_READOUT_DISABLE; + + this->shutenable_enable = DEF_SHUTENABLE_ENABLE; + this->shutenable_disable = DEF_SHUTENABLE_DISABLE; + + // pre-size the modtype and modversion vectors to hold the max number of modules + // + this->modtype.resize( nmods ); + this->modversion.resize( nmods ); - // Archon::Interface deconstructor + // TODO I should change these to STL maps instead // - Interface::~Interface() = default; + this->frame.bufsample.resize( Archon::nbufs ); + this->frame.bufcomplete.resize( Archon::nbufs ); + this->frame.bufmode.resize( Archon::nbufs ); + this->frame.bufbase.resize( Archon::nbufs ); + this->frame.bufframen.resize( Archon::nbufs ); + this->frame.bufwidth.resize( Archon::nbufs ); + this->frame.bufheight.resize( Archon::nbufs ); + this->frame.bufpixels.resize( Archon::nbufs ); + this->frame.buflines.resize( Archon::nbufs ); + this->frame.bufrawblocks.resize( Archon::nbufs ); + this->frame.bufrawlines.resize( Archon::nbufs ); + this->frame.bufrawoffset.resize( Archon::nbufs ); + this->frame.buftimestamp.resize( Archon::nbufs ); + this->frame.bufretimestamp.resize( Archon::nbufs ); + this->frame.buffetimestamp.resize( Archon::nbufs ); + } + + // Archon::Interface deconstructor + // + Interface::~Interface() = default; + + + /**************** Archon::Interface::interface ******************************/ + long Interface::interface(std::string &iface) { + std::string function = "Archon::Interface::interface"; + iface = "STA-Archon"; + logwrite(function, iface); + return 0; + } + /**************** Archon::Interface::interface ******************************/ + + + /***** Archon::Interface::configure_controller ******************************/ + /** + * @brief parse controller-related keys from the configuration file + * @details the config file was read by server.config.read_config() in main() + * @return ERROR or NO_ERROR + * + */ + long Interface::configure_controller() { + std::string function = "Archon::Interface::configure_controller"; + std::stringstream message; + int applied=0; + long error; + + // Must re-init all values to start-up defaults in case this function is + // called again to re-load the config file (such as if a HUP is received) + // and the new config file may not have everything defined. + // This ensures nothing is carried over from any previous config. + // + this->camera_info.hostname = ""; + this->archon.sethost( "" ); + this->camera_info.port = -1; + this->archon.setport( -1 ); + this->is_longexposure = false; + this->n_hdrshift = 16; - /**************** Archon::Interface::interface ******************************/ - long Interface::interface(std::string &iface) { - std::string function = "Archon::Interface::interface"; - iface = "STA-Archon"; - logwrite(function, iface); - return 0; - } + this->camera.firmware[0] = ""; - /**************** Archon::Interface::interface ******************************/ + this->exposeparam = ""; + this->trigin_exposeparam = ""; + this->trigin_untimedparam = ""; + this->trigin_readoutparam = ""; + this->trigin_expose_enable = DEF_TRIGIN_EXPOSE_ENABLE; + this->trigin_expose_disable = DEF_TRIGIN_EXPOSE_DISABLE; + this->trigin_untimed_enable = DEF_TRIGIN_UNTIMED_ENABLE; + this->trigin_untimed_disable = DEF_TRIGIN_UNTIMED_DISABLE; + this->trigin_readout_enable = DEF_TRIGIN_READOUT_ENABLE; + this->trigin_readout_disable = DEF_TRIGIN_READOUT_DISABLE; - /***** Archon::Interface::configure_controller ******************************/ - /** - * @brief parse controller-related keys from the configuration file - * @details the config file was read by server.config.read_config() in main() - * @return ERROR or NO_ERROR - * - */ - long Interface::configure_controller() { - std::string function = "Archon::Interface::configure_controller"; - std::stringstream message; - int applied = 0; - long error; + this->shutenable_enable = DEF_SHUTENABLE_ENABLE; + this->shutenable_disable = DEF_SHUTENABLE_DISABLE; - // Must re-init all values to start-up defaults in case this function is - // called again to re-load the config file (such as if a HUP is received) - // and the new config file may not have everything defined. - // This ensures nothing is carried over from any previous config. - // - this->camera_info.hostname = ""; - this->archon.sethost(""); - this->camera_info.port = -1; - this->archon.setport(-1); + // loop through the entries in the configuration file, stored in config class + // + for (int entry=0; entry < this->config.n_entries; entry++) { - this->is_longexposure = false; - this->n_hdrshift = 16; + if (config.param[entry].compare(0, 9, "ARCHON_IP")==0) { + this->camera_info.hostname = config.arg[entry]; + this->archon.sethost( config.arg[entry] ); + message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite( function, message.str() ); + this->camera.async.enqueue( message.str() ); + applied++; + } - this->camera.firmware[0] = ""; + if (config.param[entry].compare(0, 11, "ARCHON_PORT")==0) { // ARCHON_PORT + int port; + try { + port = std::stoi( config.arg[entry] ); - this->exposeparam = ""; - this->trigin_exposeparam = ""; - this->trigin_untimedparam = ""; - this->trigin_readoutparam = ""; + } catch (std::invalid_argument &) { + this->camera.log_error( function, "unable to convert port number to integer" ); + return ERROR; - this->trigin_expose_enable = DEF_TRIGIN_EXPOSE_ENABLE; - this->trigin_expose_disable = DEF_TRIGIN_EXPOSE_DISABLE; - this->trigin_untimed_enable = DEF_TRIGIN_UNTIMED_ENABLE; - this->trigin_untimed_disable = DEF_TRIGIN_UNTIMED_DISABLE; - this->trigin_readout_enable = DEF_TRIGIN_READOUT_ENABLE; - this->trigin_readout_disable = DEF_TRIGIN_READOUT_DISABLE; + } catch (std::out_of_range &) { + this->camera.log_error( function, "port number out of integer range" ); + return ERROR; + } + this->camera_info.port = port; + this->archon.setport(port); + message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite( function, message.str() ); + this->camera.async.enqueue( message.str() ); + applied++; + } - this->shutenable_enable = DEF_SHUTENABLE_ENABLE; - this->shutenable_disable = DEF_SHUTENABLE_DISABLE; + if (config.param[entry].compare(0, 12, "AMPS_AS_CUBE")==0) { + std::string dontcare; + if ( this->camera.cubeamps( config.arg[entry], dontcare ) == ERROR ) { + this->camera.log_error( function, "setting cubeamps" ); + return ERROR; + } + } - // loop through the entries in the configuration file, stored in config class - // - for (int entry = 0; entry < this->config.n_entries; entry++) { - if (config.param[entry].compare(0, 9, "ARCHON_IP") == 0) { - this->camera_info.hostname = config.arg[entry]; - this->archon.sethost(config.arg[entry]); - message.str(""); - message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite(function, message.str()); - this->camera.async.enqueue(message.str()); - applied++; - } + if (config.param[entry].compare(0, 12, "EXPOSE_PARAM")==0) { // EXPOSE_PARAM + this->exposeparam = config.arg[entry]; + message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite( function, message.str() ); + this->camera.async.enqueue( message.str() ); + applied++; + } - if (config.param[entry].compare(0, 11, "ARCHON_PORT") == 0) { - // ARCHON_PORT - int port; - try { - port = std::stoi(config.arg[entry]); - } catch (std::invalid_argument &) { - this->camera.log_error(function, "unable to convert port number to integer"); - return ERROR; - } catch (std::out_of_range &) { - this->camera.log_error(function, "port number out of integer range"); - return ERROR; - } - this->camera_info.port = port; - this->archon.setport(port); - message.str(""); - message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite(function, message.str()); - this->camera.async.enqueue(message.str()); - applied++; - } + if (config.param[entry].compare(0, 19, "TRIGIN_EXPOSE_PARAM")==0) { // TRIGIN_EXPOSE_PARAM + this->trigin_exposeparam = config.arg[entry]; + message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite( function, message.str() ); + this->camera.async.enqueue( message.str() ); + applied++; + } - if (config.param[entry].compare(0, 12, "AMPS_AS_CUBE") == 0) { - std::string dontcare; - if (this->camera.cubeamps(config.arg[entry], dontcare) == ERROR) { - this->camera.log_error(function, "setting cubeamps"); - return ERROR; - } - } + if (config.param[entry].compare(0, 20, "TRIGIN_UNTIMED_PARAM")==0) { // TRIGIN_UNTIMED_PARAM + this->trigin_untimedparam = config.arg[entry]; + message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite( function, message.str() ); + this->camera.async.enqueue( message.str() ); + applied++; + } - if (config.param[entry].compare(0, 12, "EXPOSE_PARAM") == 0) { - // EXPOSE_PARAM - this->exposeparam = config.arg[entry]; - message.str(""); - message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite(function, message.str()); - this->camera.async.enqueue(message.str()); - applied++; - } + if (config.param[entry].compare(0, 20, "TRIGIN_READOUT_PARAM")==0) { // TRIGIN_READOUT_PARAM + this->trigin_readoutparam = config.arg[entry]; + message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite( function, message.str() ); + this->camera.async.enqueue( message.str() ); + applied++; + } - if (config.param[entry].compare(0, 19, "TRIGIN_EXPOSE_PARAM") == 0) { - // TRIGIN_EXPOSE_PARAM - this->trigin_exposeparam = config.arg[entry]; - message.str(""); - message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite(function, message.str()); - this->camera.async.enqueue(message.str()); - applied++; - } + if (config.param[entry].compare(0, 20, "TRIGIN_EXPOSE_ENABLE")==0) { // TRIGIN_EXPOSE_ENABLE + int enable; + try { + enable = std::stoi( config.arg[entry] ); - if (config.param[entry].compare(0, 20, "TRIGIN_UNTIMED_PARAM") == 0) { - // TRIGIN_UNTIMED_PARAM - this->trigin_untimedparam = config.arg[entry]; - message.str(""); - message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite(function, message.str()); - this->camera.async.enqueue(message.str()); - applied++; - } + } catch (std::invalid_argument &) { + this->camera.log_error( function, "unable to convert TRIGIN_EXPOSE_ENABLE to integer" ); + return ERROR; - if (config.param[entry].compare(0, 20, "TRIGIN_READOUT_PARAM") == 0) { - // TRIGIN_READOUT_PARAM - this->trigin_readoutparam = config.arg[entry]; - message.str(""); - message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite(function, message.str()); - this->camera.async.enqueue(message.str()); - applied++; - } + } catch (std::out_of_range &) { + this->camera.log_error( function, "TRIGIN_EXPOSE_ENABLE out of integer range" ); + return ERROR; + } + this->trigin_expose_enable = enable; + message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite( function, message.str() ); + this->camera.async.enqueue( message.str() ); + applied++; + } - if (config.param[entry].compare(0, 20, "TRIGIN_EXPOSE_ENABLE") == 0) { - // TRIGIN_EXPOSE_ENABLE - int enable; - try { - enable = std::stoi(config.arg[entry]); - } catch (std::invalid_argument &) { - this->camera.log_error(function, "unable to convert TRIGIN_EXPOSE_ENABLE to integer"); - return ERROR; - } catch (std::out_of_range &) { - this->camera.log_error(function, "TRIGIN_EXPOSE_ENABLE out of integer range"); - return ERROR; - } - this->trigin_expose_enable = enable; - message.str(""); - message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite(function, message.str()); - this->camera.async.enqueue(message.str()); - applied++; - } + if (config.param[entry].compare(0, 21, "TRIGIN_EXPOSE_DISABLE")==0) { // TRIGIN_EXPOSE_DISABLE + int disable; + try { + disable = std::stoi( config.arg[entry] ); - if (config.param[entry].compare(0, 21, "TRIGIN_EXPOSE_DISABLE") == 0) { - // TRIGIN_EXPOSE_DISABLE - int disable; - try { - disable = std::stoi(config.arg[entry]); - } catch (std::invalid_argument &) { - this->camera.log_error(function, "unable to convert TRIGIN_EXPOSE_DISABLE to integer"); - return ERROR; - } catch (std::out_of_range &) { - this->camera.log_error(function, "TRIGIN_EXPOSE_DISABLE out of integer range"); - return ERROR; - } - this->trigin_expose_disable = disable; - message.str(""); - message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite(function, message.str()); - this->camera.async.enqueue(message.str()); - applied++; - } + } catch (std::invalid_argument &) { + this->camera.log_error( function, "unable to convert TRIGIN_EXPOSE_DISABLE to integer" ); + return ERROR; - if (config.param[entry].compare(0, 21, "TRIGIN_UNTIMED_ENABLE") == 0) { - // TRIGIN_UNTIMED_ENABLE - int enable; - try { - enable = std::stoi(config.arg[entry]); - } catch (std::invalid_argument &) { - this->camera.log_error(function, "unable to convert TRIGIN_UNTIMED_ENABLE to integer"); - return ERROR; - } catch (std::out_of_range &) { - this->camera.log_error(function, "TRIGIN_UNTIMED_ENABLE out of integer range"); - return ERROR; - } - this->trigin_untimed_enable = enable; - message.str(""); - message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite(function, message.str()); - this->camera.async.enqueue(message.str()); - applied++; - } + } catch (std::out_of_range &) { + this->camera.log_error( function, "TRIGIN_EXPOSE_DISABLE out of integer range" ); + return ERROR; + } + this->trigin_expose_disable = disable; + message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite( function, message.str() ); + this->camera.async.enqueue( message.str() ); + applied++; + } - if (config.param[entry].compare(0, 22, "TRIGIN_UNTIMED_DISABLE") == 0) { - // TRIGIN_UNTIMED_DISABLE - int disable; - try { - disable = std::stoi(config.arg[entry]); - } catch (std::invalid_argument &) { - this->camera.log_error(function, "unable to convert TRIGIN_UNTIMED_DISABLE to integer"); - return ERROR; - } catch (std::out_of_range &) { - this->camera.log_error(function, "TRIGIN_UNTIMED_DISABLE out of integer range"); - return ERROR; - } - this->trigin_untimed_disable = disable; - message.str(""); - message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite(function, message.str()); - this->camera.async.enqueue(message.str()); - applied++; - } + if (config.param[entry].compare(0, 21, "TRIGIN_UNTIMED_ENABLE")==0) { // TRIGIN_UNTIMED_ENABLE + int enable; + try { + enable = std::stoi( config.arg[entry] ); - if (config.param[entry].compare(0, 21, "TRIGIN_READOUT_ENABLE") == 0) { - // TRIGIN_READOUT_ENABLE - int enable; - try { - enable = std::stoi(config.arg[entry]); - } catch (std::invalid_argument &) { - this->camera.log_error(function, "unable to convert TRIGIN_READOUT_ENABLE to integer"); - return ERROR; - } catch (std::out_of_range &) { - this->camera.log_error(function, "TRIGIN_READOUT_ENABLE out of integer range"); - return ERROR; - } - this->trigin_readout_enable = enable; - message.str(""); - message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite(function, message.str()); - this->camera.async.enqueue(message.str()); - applied++; - } + } catch (std::invalid_argument &) { + this->camera.log_error( function, "unable to convert TRIGIN_UNTIMED_ENABLE to integer" ); + return ERROR; - if (config.param[entry].compare(0, 22, "TRIGIN_READOUT_DISABLE") == 0) { - // TRIGIN_READOUT_DISABLE - int disable; - try { - disable = std::stoi(config.arg[entry]); - } catch (std::invalid_argument &) { - this->camera.log_error(function, "unable to convert TRIGIN_READOUT_DISABLE to integer"); - return ERROR; - } catch (std::out_of_range &) { - this->camera.log_error(function, "TRIGIN_READOUT_DISABLE out of integer range"); - return ERROR; - } - this->trigin_readout_disable = disable; - message.str(""); - message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite(function, message.str()); - this->camera.async.enqueue(message.str()); - applied++; - } + } catch (std::out_of_range &) { + this->camera.log_error( function, "TRIGIN_UNTIMED_ENABLE out of integer range" ); + return ERROR; + } + this->trigin_untimed_enable = enable; + message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite( function, message.str() ); + this->camera.async.enqueue( message.str() ); + applied++; + } - if (config.param[entry].compare(0, 16, "SHUTENABLE_PARAM") == 0) { - // SHUTENABLE_PARAM - this->shutenableparam = config.arg[entry]; - message.str(""); - message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite(function, message.str()); - this->camera.async.enqueue(message.str()); - applied++; - } + if (config.param[entry].compare(0, 22, "TRIGIN_UNTIMED_DISABLE")==0) { // TRIGIN_UNTIMED_DISABLE + int disable; + try { + disable = std::stoi( config.arg[entry] ); - if (config.param[entry].compare(0, 17, "SHUTENABLE_ENABLE") == 0) { - // SHUTENABLE_ENABLE - int enable; - try { - enable = std::stoi(config.arg[entry]); - } catch (std::invalid_argument &) { - this->camera.log_error(function, "unable to convert SHUTENABLE_ENABLE to integer"); - return ERROR; - } catch (std::out_of_range &) { - this->camera.log_error(function, "SHUTENABLE_ENABLE out of integer range"); - return ERROR; - } - this->shutenable_enable = enable; - message.str(""); - message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite(function, message.str()); - this->camera.async.enqueue(message.str()); - applied++; - } + } catch (std::invalid_argument &) { + this->camera.log_error( function, "unable to convert TRIGIN_UNTIMED_DISABLE to integer" ); + return ERROR; - if (config.param[entry].compare(0, 18, "SHUTENABLE_DISABLE") == 0) { - // SHUTENABLE_DISABLE - int disable; - try { - disable = std::stoi(config.arg[entry]); - } catch (std::invalid_argument &) { - this->camera.log_error(function, "unable to convert SHUTENABLE_DISABLE to integer"); - return ERROR; - } catch (std::out_of_range &) { - this->camera.log_error(function, "SHUTENABLE_DISABLE out of integer range"); - return ERROR; - } - this->shutenable_disable = disable; - message.str(""); - message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite(function, message.str()); - this->camera.async.enqueue(message.str()); - applied++; - } + } catch (std::out_of_range &) { + this->camera.log_error( function, "TRIGIN_UNTIMED_DISABLE out of integer range" ); + return ERROR; + } + this->trigin_untimed_disable = disable; + message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite( function, message.str() ); + this->camera.async.enqueue( message.str() ); + applied++; + } - // .firmware and .readout_time are STL maps but (for now) only one Archon per computer - // so map always to 0 - // - if (config.param[entry].compare(0, 16, "DEFAULT_FIRMWARE") == 0) { - this->camera.firmware[0] = config.arg[entry]; - message.str(""); - message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite(function, message.str()); - this->camera.async.enqueue(message.str()); - applied++; - } + if (config.param[entry].compare(0, 21, "TRIGIN_READOUT_ENABLE")==0) { // TRIGIN_READOUT_ENABLE + int enable; + try { + enable = std::stoi( config.arg[entry] ); - if (config.param[entry].compare(0, 16, "HDR_SHIFT") == 0) { - std::string dontcare; - this->hdrshift(config.arg[entry], dontcare); - message.str(""); - message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite(function, message.str()); - this->camera.async.enqueue(message.str()); - applied++; - } + } catch (std::invalid_argument &) { + this->camera.log_error( function, "unable to convert TRIGIN_READOUT_ENABLE to integer" ); + return ERROR; - if (config.param[entry].compare(0, 12, "READOUT_TIME") == 0) { - int readtime; - try { - readtime = std::stoi(config.arg[entry]); - } catch (std::invalid_argument &) { - this->camera.log_error(function, "unable to convert readout time to integer"); - return ERROR; - } catch (std::out_of_range &) { - this->camera.log_error(function, "readout time out of integer range"); - return ERROR; - } - this->camera.readout_time[0] = readtime; - message.str(""); - message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite(function, message.str()); - this->camera.async.enqueue(message.str()); - applied++; - } + } catch (std::out_of_range &) { + this->camera.log_error( function, "TRIGIN_READOUT_ENABLE out of integer range" ); + return ERROR; + } + this->trigin_readout_enable = enable; + message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite( function, message.str() ); + this->camera.async.enqueue( message.str() ); + applied++; + } - if (config.param[entry].compare(0, 5, "IMDIR") == 0) { - this->camera.imdir(config.arg[entry]); - message.str(""); - message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite(function, message.str()); - this->camera.async.enqueue(message.str()); - applied++; - } + if (config.param[entry].compare(0, 22, "TRIGIN_READOUT_DISABLE")==0) { // TRIGIN_READOUT_DISABLE + int disable; + try { + disable = std::stoi( config.arg[entry] ); - if (config.param[entry].compare(0, 7, "DIRMODE") == 0) { - std::string s(config.arg[entry]); - std::stringstream mode_bit; - mode_t mode = 0; - for (size_t i = 0; i < s.length(); i++) { - try { - mode = (mode << 3); - mode_bit.str(""); - mode_bit << s.at(i); - mode |= std::stoi(mode_bit.str()); - } catch (std::invalid_argument &) { - this->camera.log_error(function, "unable to convert mode bit to integer"); - return ERROR; - } catch (std::out_of_range &) { - this->camera.log_error(function, "out of range converting dirmode bit"); - return ERROR; - } - } - this->camera.set_dirmode(mode); - message.str(""); - message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite(function, message.str()); - this->camera.async.enqueue(message.str()); - applied++; - } + } catch (std::invalid_argument &) { + this->camera.log_error( function, "unable to convert TRIGIN_READOUT_DISABLE to integer" ); + return ERROR; - if (config.param[entry].compare(0, 8, "BASENAME") == 0) { - this->camera.basename(config.arg[entry]); - message.str(""); - message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; - logwrite(function, message.str()); - this->camera.async.enqueue(message.str()); - applied++; - } + } catch (std::out_of_range &) { + this->camera.log_error( function, "TRIGIN_READOUT_DISABLE out of integer range" ); + return ERROR; } + this->trigin_readout_disable = disable; + message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite( function, message.str() ); + this->camera.async.enqueue( message.str() ); + applied++; + } - message.str(""); - if (applied == 0) { - message << "ERROR: "; - error = ERROR; - } else { - error = NO_ERROR; - } - message << "applied " << applied << " configuration lines to controller"; - error == NO_ERROR ? logwrite(function, message.str()) : this->camera.log_error(function, message.str()); - return error; - } + if (config.param[entry].compare(0, 16, "SHUTENABLE_PARAM")==0) { // SHUTENABLE_PARAM + this->shutenableparam = config.arg[entry]; + message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite( function, message.str() ); + this->camera.async.enqueue( message.str() ); + applied++; + } - /***** Archon::Interface::configure_controller ******************************/ + if (config.param[entry].compare(0, 17, "SHUTENABLE_ENABLE")==0) { // SHUTENABLE_ENABLE + int enable; + try { + enable = std::stoi( config.arg[entry] ); + } catch (std::invalid_argument &) { + this->camera.log_error( function, "unable to convert SHUTENABLE_ENABLE to integer" ); + return ERROR; - /**************** Archon::Interface::prepare_image_buffer *******************/ - /** - * @fn prepare_image_buffer - * @brief prepare image_data buffer, allocating memory as needed - * @param none - * @return NO_ERROR if successful or ERROR on error - * - */ - long Interface::prepare_image_buffer() { - std::string function = "Archon::Interface::prepare_image_buffer"; - std::stringstream message; + } catch (std::out_of_range &) { + this->camera.log_error( function, "SHUTENABLE_ENABLE out of integer range" ); + return ERROR; + } + this->shutenable_enable = enable; + message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite( function, message.str() ); + this->camera.async.enqueue( message.str() ); + applied++; + } - // If there is already a correctly-sized buffer allocated, - // then don't do anything except initialize that space to zero. - // - if ((this->image_data != nullptr) && - (this->image_data_bytes != 0) && - (this->image_data_allocated == this->image_data_bytes)) { - memset(this->image_data, 0, this->image_data_bytes); - message.str(""); - message << "initialized " << this->image_data_bytes << " bytes of image_data memory"; - logwrite(function, message.str()); - } else { - // If memory needs to be re-allocated, delete the old buffer - if (this->image_data != nullptr) { - logwrite(function, "deleting old image_data buffer"); - delete [] this->image_data; - this->image_data = nullptr; - } - // Allocate new memory - // - if (this->image_data_bytes != 0) { - this->image_data = new char[this->image_data_bytes]; - this->image_data_allocated = this->image_data_bytes; - message.str(""); - message << "allocated " << this->image_data_bytes << " bytes for image_data"; - logwrite(function, message.str()); - } else { - this->camera.log_error(function, "cannot allocate zero-length image memory"); - return ERROR; - } + if (config.param[entry].compare(0, 18, "SHUTENABLE_DISABLE")==0) { // SHUTENABLE_DISABLE + int disable; + try { + disable = std::stoi( config.arg[entry] ); + + } catch (std::invalid_argument &) { + this->camera.log_error( function, "unable to convert SHUTENABLE_DISABLE to integer" ); + return ERROR; + + } catch (std::out_of_range &) { + this->camera.log_error( function, "SHUTENABLE_DISABLE out of integer range" ); + return ERROR; } + this->shutenable_disable = disable; + message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite( function, message.str() ); + this->camera.async.enqueue( message.str() ); + applied++; + } - return NO_ERROR; - } + // .firmware and .readout_time are STL maps but (for now) only one Archon per computer + // so map always to 0 + // + if (config.param[entry].compare(0, 16, "DEFAULT_FIRMWARE")==0) { + this->camera.firmware[0] = config.arg[entry]; + message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite( function, message.str() ); + this->camera.async.enqueue( message.str() ); + applied++; + } - /**************** Archon::Interface::prepare_image_buffer *******************/ + if (config.param[entry].compare(0, 16, "HDR_SHIFT")==0) { + std::string dontcare; + this->hdrshift( config.arg[entry], dontcare ); + message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite( function, message.str() ); + this->camera.async.enqueue( message.str() ); + applied++; + } + if (config.param[entry].compare(0, 12, "READOUT_TIME")==0) { + int readtime; + try { + readtime = std::stoi ( config.arg[entry] ); - /**************** Archon::Interface::connect_controller *********************/ - /** - * @fn connect_controller - * @brief - * @param none (devices_in here for future expansion) - * @return - * - */ - long Interface::connect_controller(const std::string &devices_in = "") { - std::string function = "Archon::Interface::connect_controller"; - std::stringstream message; - int adchans = 0; - long error = ERROR; + } catch (std::invalid_argument &) { + this->camera.log_error( function, "unable to convert readout time to integer" ); + return ERROR; - if (this->archon.isconnected()) { - logwrite(function, "camera connection already open"); - return NO_ERROR; + } catch (std::out_of_range &) { + this->camera.log_error( function, "readout time out of integer range" ); + return ERROR; } + this->camera.readout_time[0] = readtime; + message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite( function, message.str() ); + this->camera.async.enqueue( message.str() ); + applied++; + } - // Initialize the camera connection - // - logwrite(function, "opening a connection to the camera system"); + if (config.param[entry].compare(0, 5, "IMDIR")==0) { + this->camera.imdir( config.arg[entry] ); + message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite( function, message.str() ); + this->camera.async.enqueue( message.str() ); + applied++; + } - if (this->archon.Connect() != 0) { - message.str(""); - message << "connecting to " << this->camera_info.hostname << ":" << this->camera_info.port << ": " << - strerror(errno); - this->camera.log_error(function, message.str()); + if (config.param[entry].compare(0, 7, "DIRMODE")==0) { + std::string s( config.arg[entry] ); + std::stringstream mode_bit; + mode_t mode=0; + for ( size_t i=0; i < s.length(); i++ ) { + try { + mode = (mode << 3); + mode_bit.str(""); mode_bit << s.at(i); + mode |= std::stoi( mode_bit.str() ); + + } catch (std::invalid_argument &) { + this->camera.log_error( function, "unable to convert mode bit to integer" ); + return ERROR; + + } catch (std::out_of_range &) { + this->camera.log_error( function, "out of range converting dirmode bit" ); return ERROR; + } } + this->camera.set_dirmode( mode ); + message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite( function, message.str() ); + this->camera.async.enqueue( message.str() ); + applied++; + } - message.str(""); - message << "socket connection to " << this->camera_info.hostname << ":" << this->camera_info.port << " " - << "established on fd " << this->archon.getfd(); - logwrite(function, message.str()); + if (config.param[entry].compare(0, 8, "BASENAME")==0) { + this->camera.basename( config.arg[entry] ); + message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite( function, message.str() ); + this->camera.async.enqueue( message.str() ); + applied++; + } - // Get the current system information for the installed modules - // - std::string reply; - error = this->archon_cmd(SYSTEM, reply); // first the whole reply in one string + } + + message.str(""); + if (applied==0) { + message << "ERROR: "; + error = ERROR; - std::vector lines, tokens; - Tokenize(reply, lines, " "); // then each line in a separate token "lines" + } else { + error = NO_ERROR; + } + message << "applied " << applied << " configuration lines to controller"; + error == NO_ERROR ? logwrite( function, message.str() ) : this->camera.log_error( function, message.str() ) ; + return error; + } + /***** Archon::Interface::configure_controller ******************************/ + + + /**************** Archon::Interface::prepare_image_buffer *******************/ + /** + * @fn prepare_image_buffer + * @brief prepare image_data buffer, allocating memory as needed + * @param none + * @return NO_ERROR if successful or ERROR on error + * + */ + long Interface::prepare_image_buffer() { + std::string function = "Archon::Interface::prepare_image_buffer"; + std::stringstream message; + + // If there is already a correctly-sized buffer allocated, + // then don't do anything except initialize that space to zero. + // + if ( (this->image_data != nullptr) && + (this->image_data_bytes != 0) && + (this->image_data_allocated == this->image_data_bytes) ) { + memset(this->image_data, 0, this->image_data_bytes); + message.str(""); message << "initialized " << this->image_data_bytes << " bytes of image_data memory"; + logwrite(function, message.str()); - for (const auto &line: lines) { - Tokenize(line, tokens, "_="); // finally break each line into tokens to get module, type and version - if (tokens.size() != 3) continue; // need 3 tokens + } else { + // If memory needs to be re-allocated, delete the old buffer + if (this->image_data != nullptr) { + logwrite(function, "deleting old image_data buffer"); + delete [] this->image_data; + this->image_data=nullptr; + } + // Allocate new memory + // + if (this->image_data_bytes != 0) { + this->image_data = new char[this->image_data_bytes]; + this->image_data_allocated=this->image_data_bytes; + message.str(""); message << "allocated " << this->image_data_bytes << " bytes for image_data"; + logwrite(function, message.str()); - std::string version; - int module = 0; - int type = 0; + } else { + this->camera.log_error( function, "cannot allocate zero-length image memory" ); + return ERROR; + } + } - // get the module number - // - if (tokens[0].compare(0, 9, "BACKPLANE") == 0) { - if (tokens[1] == "VERSION") this->backplaneversion = tokens[2]; - continue; - } + return NO_ERROR; + } + /**************** Archon::Interface::prepare_image_buffer *******************/ + + + /**************** Archon::Interface::connect_controller *********************/ + /** + * @fn connect_controller + * @brief + * @param none (devices_in here for future expansion) + * @return + * + */ + long Interface::connect_controller(const std::string& devices_in="") { + std::string function = "Archon::Interface::connect_controller"; + std::stringstream message; + int adchans=0; + long error = ERROR; + + if ( this->archon.isconnected() ) { + logwrite(function, "camera connection already open"); + return NO_ERROR; + } - // get the module and type of each module from MODn_TYPE - // - if ((tokens[0].compare(0, 3, "MOD") == 0) && (tokens[1] == "TYPE")) { - try { - module = std::stoi(tokens[0].substr(3)); - type = std::stoi(tokens[2]); - } catch (std::invalid_argument &) { - message.str(""); - message << "unable to convert module or type from " << tokens[0] << "=" << tokens[1] << - " to integer"; - this->camera.log_error(function, message.str()); - return ERROR; - } catch (std::out_of_range &) { - message.str(""); - message << "module " << tokens[0].substr(3) << " or type " << tokens[1] << " out of range"; - this->camera.log_error(function, message.str()); - return ERROR; - } - } else continue; + // Initialize the camera connection + // + logwrite(function, "opening a connection to the camera system"); - // get the module version - // - if (tokens[1] == "VERSION") version = tokens[2]; - else version = ""; + if ( this->archon.Connect() != 0 ) { + message.str(""); message << "connecting to " << this->camera_info.hostname << ":" << this->camera_info.port << ": " << strerror(errno); + this->camera.log_error( function, message.str() ); + return ERROR; + } - // now store it permanently - // - if ((module > 0) && (module <= nmods)) { - try { - this->modtype.at(module - 1) = type; // store the type in a vector indexed by module - this->modversion.at(module - 1) = version; // store the type in a vector indexed by module - } catch (std::out_of_range &) { - message.str(""); - message << "requested module " << module << " out of range {1:" << nmods; - this->camera.log_error(function, message.str()); - } - } else { - // else should never happen - message.str(""); - message << "module " << module << " outside range {1:" << nmods << "}"; - this->camera.log_error(function, message.str()); - return ERROR; - } + message.str(""); + message << "socket connection to " << this->camera_info.hostname << ":" << this->camera_info.port << " " + << "established on fd " << this->archon.getfd(); + logwrite(function, message.str()); - // Use the module type to resize the gain and offset vectors, - // but always use the largest possible value allowed. - // - if (type == 2) adchans = (adchans < MAXADCCHANS ? MAXADCCHANS : adchans); // ADC module (type=2) found - if (type == 17) adchans = (adchans < MAXADMCHANS ? MAXADMCHANS : adchans); // ADM module (type=17) found - this->gain.resize(adchans); - this->offset.resize(adchans); + // Get the current system information for the installed modules + // + std::string reply; + error = this->archon_cmd( SYSTEM, reply ); // first the whole reply in one string - // Check that the AD modules are installed in the correct slot - // - if ((type == 2 || type == 17) && (module < 5 || module > 8)) { - message.str(""); - message << "AD module (type=" << type << ") cannot be in slot " << module << ". Use slots 5-8"; - this->camera.log_error(function, message.str()); - return ERROR; - } - } // end for ( auto line : lines ) + std::vector lines, tokens; + Tokenize( reply, lines, " " ); // then each line in a separate token "lines" - // empty the Archon log - // - error = this->fetchlog(); + for ( const auto& line : lines ) { + Tokenize( line, tokens, "_=" ); // finally break each line into tokens to get module, type and version + if ( tokens.size() != 3 ) continue; // need 3 tokens - // Make sure the following systemkeys are added. - // They can be changed at any time by a command but since they have defaults - // they don't require a command so this ensures they get into the systemkeys db. - // - std::stringstream keystr; - keystr << "HDRSHIFT=" << this->n_hdrshift << "// number of HDR right-shift bits"; - this->systemkeys.addkey(keystr.str()); + std::string version; + int module=0; + int type=0; - return error; - } + // get the module number + // + if ( tokens[0].compare( 0, 9, "BACKPLANE" ) == 0 ) { + if ( tokens[1] == "VERSION" ) this->backplaneversion = tokens[2]; + continue; + } - /**************** Archon::Interface::connect_controller *********************/ + // get the module and type of each module from MODn_TYPE + // + if ( ( tokens[0].compare( 0, 3, "MOD" ) == 0 ) && ( tokens[1] == "TYPE" ) ) { + try { + module = std::stoi( tokens[0].substr(3) ); + type = std::stoi( tokens[2] ); + } catch (std::invalid_argument &) { + message.str(""); message << "unable to convert module or type from " << tokens[0] << "=" << tokens[1] << " to integer"; + this->camera.log_error( function, message.str() ); + return ERROR; - /**************** Archon::Interface::disconnect_controller ******************/ - /** - * @fn disconnect_controller - * @brief - * @param none - * @return - * - */ - long Interface::disconnect_controller() { - std::string function = "Archon::Interface::disconnect_controller"; - long error; - if (!this->archon.isconnected()) { - logwrite(function, "connection already closed"); - return (NO_ERROR); + } catch (std::out_of_range &) { + message.str(""); message << "module " << tokens[0].substr(3) << " or type " << tokens[1] << " out of range"; + this->camera.log_error( function, message.str() ); + return ERROR; } - // close the socket file descriptor to the Archon controller - // - error = this->archon.Close(); - // Free the memory - // - if (this->image_data != nullptr) { - logwrite(function, "releasing allocated device memory"); - delete [] this->image_data; - this->image_data = nullptr; - } + } else continue; - // On success, write the value to the log and return - // - if (error == NO_ERROR) { - logwrite(function, "Archon connection terminated"); - } else { - // Throw an error for any other errors - this->camera.log_error(function, "disconnecting Archon camera"); + // get the module version + // + if ( tokens[1] == "VERSION" ) version = tokens[2]; else version = ""; + + // now store it permanently + // + if ( (module > 0) && (module <= nmods) ) { + try { + this->modtype.at(module-1) = type; // store the type in a vector indexed by module + this->modversion.at(module-1) = version; // store the type in a vector indexed by module + + } catch (std::out_of_range &) { + message.str(""); message << "requested module " << module << " out of range {1:" << nmods; + this->camera.log_error( function, message.str() ); } - return error; - } + } else { // else should never happen + message.str(""); message << "module " << module << " outside range {1:" << nmods << "}"; + this->camera.log_error( function, message.str() ); + return ERROR; + } - /**************** Archon::Interface::disconnect_controller ******************/ + // Use the module type to resize the gain and offset vectors, + // but always use the largest possible value allowed. + // + if ( type == 2 ) adchans = ( adchans < MAXADCCHANS ? MAXADCCHANS : adchans ); // ADC module (type=2) found + if ( type == 17 ) adchans = ( adchans < MAXADMCHANS ? MAXADMCHANS : adchans ); // ADM module (type=17) found + this->gain.resize( adchans ); + this->offset.resize( adchans ); + + // Check that the AD modules are installed in the correct slot + // + if ( ( type == 2 || type == 17 ) && ( module < 5 || module > 8 ) ) { + message.str(""); message << "AD module (type=" << type << ") cannot be in slot " << module << ". Use slots 5-8"; + this->camera.log_error( function, message.str() ); + return ERROR; + } + } // end for ( auto line : lines ) - /**************** Archon::Interface::native *********************************/ - /** - * @fn native - * @brief send native commands directly to Archon and log result - * @param std::string cmd - * @return long ret from archon_cmd() call - * - * This function simply calls archon_cmd() then breaks the reply into - * space-delimited tokens and puts each token into the asynchronous - * message queue. The result is that the reply comes out one line at - * a time on the async port. - * - */ - long Interface::native(const std::string &cmd) { - std::string function = "Archon::Interface::native"; - std::stringstream message; - std::string reply; - std::vector tokens; - long ret = archon_cmd(cmd, reply); - if (!reply.empty()) { - // Tokenize the reply and put each non-empty token into the asynchronous message queue. - // The reply message begins and ends with "CMD:BEGIN" and "CMD:END" and - // each line of the reply is prepended with "CMD:" where CMD is the native command - // which generated the message. - // - message << cmd << ":BEGIN"; - this->camera.async.enqueue(message.str()); + // empty the Archon log + // + error = this->fetchlog(); - Tokenize(reply, tokens, " "); - for (const auto &token: tokens) { - if (!token.empty() && token != "\n") { - message.str(""); - message << cmd << ":" << token; - this->camera.async.enqueue(message.str()); - } - } - message.str(""); - message << cmd << ":END"; - this->camera.async.enqueue(message.str()); - } - return ret; + // Make sure the following systemkeys are added. + // They can be changed at any time by a command but since they have defaults + // they don't require a command so this ensures they get into the systemkeys db. + // + std::stringstream keystr; + keystr << "HDRSHIFT=" << this->n_hdrshift << "// number of HDR right-shift bits"; + this->systemkeys.addkey( keystr.str() ); + + return error; + } + /**************** Archon::Interface::connect_controller *********************/ + + + /**************** Archon::Interface::disconnect_controller ******************/ + /** + * @fn disconnect_controller + * @brief + * @param none + * @return + * + */ + long Interface::disconnect_controller() { + std::string function = "Archon::Interface::disconnect_controller"; + long error; + if (!this->archon.isconnected()) { + logwrite(function, "connection already closed"); + return (NO_ERROR); } + // close the socket file descriptor to the Archon controller + // + error = this->archon.Close(); - /**************** Archon::Interface::native *********************************/ - - - /**************** Archon::Interface::archon_cmd *****************************/ - /** - * @fn archon_cmd - * @brief send a command to Archon - * @param cmd - * @param reply (optional) - * @return ERROR, BUSY or NO_ERROR - * - */ - long Interface::archon_cmd(const std::string &cmd) { - // use this form when the calling - std::string reply; // function doesn't need to look at the reply - return (archon_cmd(cmd, reply)); + // Free the memory + // + if (this->image_data != nullptr) { + logwrite(function, "releasing allocated device memory"); + delete [] this->image_data; + this->image_data=nullptr; } - long Interface::archon_cmd(const std::string &cmd, std::string &reply) { - std::string function = "Archon::Interface::archon_cmd"; - std::stringstream message; - int retval; - char check[4]; - char buffer[4096]; //!< temporary buffer for holding Archon replies - int error = NO_ERROR; + // On success, write the value to the log and return + // + if (error == NO_ERROR) { + logwrite(function, "Archon connection terminated"); - if (!this->archon.isconnected()) { - // nothing to do if no connection open to controller - this->camera.log_error(function, "connection not open to controller"); - return ERROR; - } + } else { + // Throw an error for any other errors + this->camera.log_error( function, "disconnecting Archon camera" ); + } - if (this->archon_busy) { - // only one command at a time - message.str(""); - message << "Archon busy: ignored command " << cmd; - this->camera.log_error(function, message.str()); - return BUSY; + return error; + } + /**************** Archon::Interface::disconnect_controller ******************/ + + + /**************** Archon::Interface::native *********************************/ + /** + * @fn native + * @brief send native commands directly to Archon and log result + * @param std::string cmd + * @return long ret from archon_cmd() call + * + * This function simply calls archon_cmd() then breaks the reply into + * space-delimited tokens and puts each token into the asynchronous + * message queue. The result is that the reply comes out one line at + * a time on the async port. + * + */ + long Interface::native(const std::string& cmd) { + std::string function = "Archon::Interface::native"; + std::stringstream message; + std::string reply; + std::vector tokens; + long ret = archon_cmd(cmd, reply); + if (!reply.empty()) { + // Tokenize the reply and put each non-empty token into the asynchronous message queue. + // The reply message begins and ends with "CMD:BEGIN" and "CMD:END" and + // each line of the reply is prepended with "CMD:" where CMD is the native command + // which generated the message. + // + message << cmd << ":BEGIN"; + this->camera.async.enqueue( message.str() ); + + Tokenize(reply, tokens, " "); + for (const auto & token : tokens) { + if ( ! token.empty() && token != "\n" ) { + message.str(""); message << cmd << ":" << token; + this->camera.async.enqueue( message.str() ); } + } + message.str(""); message << cmd << ":END"; + this->camera.async.enqueue( message.str() ); + } + return ret; + } + /**************** Archon::Interface::native *********************************/ + + + /**************** Archon::Interface::archon_cmd *****************************/ + /** + * @fn archon_cmd + * @brief send a command to Archon + * @param cmd + * @param reply (optional) + * @return ERROR, BUSY or NO_ERROR + * + */ + long Interface::archon_cmd(const std::string& cmd) { // use this form when the calling + std::string reply; // function doesn't need to look at the reply + return( archon_cmd(cmd, reply) ); + } + long Interface::archon_cmd(const std::string& cmd, std::string &reply) { + std::string function = "Archon::Interface::archon_cmd"; + std::stringstream message; + int retval; + char check[4]; + char buffer[4096]; //!< temporary buffer for holding Archon replies + int error = NO_ERROR; + + if (!this->archon.isconnected()) { // nothing to do if no connection open to controller + this->camera.log_error( function, "connection not open to controller" ); + return ERROR; + } - /** - * Hold a scoped lock for the duration of this function, - * to prevent multiple threads from accessing the Archon. - */ - const std::lock_guard lock(this->archon_mutex); - this->archon_busy = true; + if (this->archon_busy) { // only one command at a time + message.str(""); message << "Archon busy: ignored command " << cmd; + this->camera.log_error( function, message.str() ); + return BUSY; + } - // build command: ">xxCOMMAND\n" where xx=hex msgref and COMMAND=command - // - this->msgref = (this->msgref + 1) % 256; // increment msgref for each new command sent - std::stringstream ssprefix; - ssprefix << ">" - << std::setfill('0') - << std::setw(2) - << std::hex - << this->msgref; - std::string prefix = ssprefix.str(); - try { - std::transform(prefix.begin(), prefix.end(), prefix.begin(), ::toupper); // make uppercase - } catch (...) { - message.str(""); - message << "converting Archon command: " << prefix << " to uppercase"; - this->camera.log_error(function, message.str()); - return ERROR; - } + /** + * Hold a scoped lock for the duration of this function, + * to prevent multiple threads from accessing the Archon. + */ + const std::lock_guard lock(this->archon_mutex); + this->archon_busy = true; - std::stringstream sscmd; // sscmd = stringstream, building command - sscmd << prefix << cmd << "\n"; - std::string scmd = sscmd.str(); // scmd = string, command to send + // build command: ">xxCOMMAND\n" where xx=hex msgref and COMMAND=command + // + this->msgref = (this->msgref + 1) % 256; // increment msgref for each new command sent + std::stringstream ssprefix; + ssprefix << ">" + << std::setfill('0') + << std::setw(2) + << std::hex + << this->msgref; + std::string prefix=ssprefix.str(); + try { + std::transform( prefix.begin(), prefix.end(), prefix.begin(), ::toupper ); // make uppercase + + } catch (...) { + message.str(""); message << "converting Archon command: " << prefix << " to uppercase"; + this->camera.log_error( function, message.str() ); + return ERROR; + } - // build the command checksum: msgref used to check that reply matches command - // - SNPRINTF(check, "<%02X", this->msgref) + std::stringstream sscmd; // sscmd = stringstream, building command + sscmd << prefix << cmd << "\n"; + std::string scmd = sscmd.str(); // scmd = string, command to send - // log the command as long as it's not a STATUS, TIMER, WCONFIG or FRAME command - // - if ((cmd.compare(0, 7, "WCONFIG") != 0) && - (cmd.compare(0, 5, "TIMER") != 0) && - (cmd.compare(0, 6, "STATUS") != 0) && - (cmd.compare(0, 5, "FRAME") != 0)) { - // erase newline for logging purposes - std::string fcmd = scmd; - try { - fcmd.erase(fcmd.find('\n'), 1); - } catch (...) { - } - message.str(""); - message << "sending command: " << fcmd; - logwrite(function, message.str()); - } + // build the command checksum: msgref used to check that reply matches command + // + SNPRINTF(check, "<%02X", this->msgref) - // send the command - // - if ((this->archon.Write(scmd)) == -1) { - this->camera.log_error(function, "writing to camera socket"); - } + // log the command as long as it's not a STATUS, TIMER, WCONFIG or FRAME command + // + if ( (cmd.compare(0,7,"WCONFIG") != 0) && + (cmd.compare(0,5,"TIMER") != 0) && + (cmd.compare(0,6,"STATUS") != 0) && + (cmd.compare(0,5,"FRAME") != 0) ) { + // erase newline for logging purposes + std::string fcmd = scmd; + try { + fcmd.erase(fcmd.find('\n'), 1); + } catch(...) { } + message.str(""); message << "sending command: " << fcmd; + logwrite(function, message.str()); + } - // For the FETCH command we don't wait for a reply, but return immediately. - // FETCH results in a binary response which is handled elsewhere (in read_frame). - // Must also distinguish this from the FETCHLOG command, for which we do wait - // for a normal reply. - // - // The scoped mutex lock will be released automatically upon return. - // - if ((cmd.compare(0, 5, "FETCH") == 0) - && (cmd.compare(0, 8, "FETCHLOG") != 0)) - return (NO_ERROR); + // send the command + // + if ( (this->archon.Write(scmd)) == -1) { + this->camera.log_error( function, "writing to camera socket"); + } - // For all other commands, receive the reply - // - reply.clear(); // zero reply buffer - do { - if ((retval = this->archon.Poll()) <= 0) { - if (retval == 0) { - message.str(""); - message << "Poll timeout waiting for response from Archon command (maybe unrecognized command?)"; - error = TIMEOUT; - } - if (retval < 0) { - message.str(""); - message << "Poll error waiting for response from Archon command"; - error = ERROR; - } - if (error != NO_ERROR) this->camera.log_error(function, message.str()); - break; - } - memset(buffer, '\0', 2048); // init temporary buffer - retval = this->archon.Read(buffer, 2048); // read into temp buffer - if (retval <= 0) { - this->camera.log_error(function, "reading Archon"); - break; - } - reply.append(buffer); // append read buffer into the reply string - } while (retval > 0 && reply.find('\n') == std::string::npos); + // For the FETCH command we don't wait for a reply, but return immediately. + // FETCH results in a binary response which is handled elsewhere (in read_frame). + // Must also distinguish this from the FETCHLOG command, for which we do wait + // for a normal reply. + // + // The scoped mutex lock will be released automatically upon return. + // + if ( (cmd.compare(0,5,"FETCH")==0) + && (cmd.compare(0,8,"FETCHLOG")!=0) ) return (NO_ERROR); - // If there was an Archon error then clear the busy flag and get out now - // - if (error != NO_ERROR) { - this->archon_busy = false; - return error; + // For all other commands, receive the reply + // + reply.clear(); // zero reply buffer + do { + if ( (retval=this->archon.Poll()) <= 0) { + if (retval==0) { + message.str(""); + message << "Poll timeout waiting for response from Archon command (maybe unrecognized command?)"; + error = TIMEOUT; } - - // The first three bytes of the reply should contain the msgref of the - // command, which can be used as a check that the received reply belongs - // to the command which was sent. - // - // Error processing command (no other information is provided by Archon) - // - if (reply.compare(0, 1, "?") == 0) { - // "?" means Archon experienced an error processing command - error = ERROR; + if (retval<0) { message.str(""); - message << "Archon controller returned error processing command: " << cmd; - this->camera.log_error(function, message.str()); - } else if (reply.compare(0, 3, check) != 0) { - // First 3 bytes of reply must equal checksum else reply doesn't belong to command + message << "Poll error waiting for response from Archon command"; error = ERROR; - // std::string hdr = reply; - try { - scmd.erase(scmd.find('\n'), 1); - } catch (...) { - } - message.str(""); - message << "command-reply mismatch for command: " + scmd + ": expected " + check + " but received " + reply; - this->camera.log_error(function, message.str()); - } else { - // command and reply are a matched pair - error = NO_ERROR; - - // log the command as long as it's not a STATUS, TIMER, WCONFIG or FRAME command - if ((cmd.compare(0, 7, "WCONFIG") != 0) && - (cmd.compare(0, 5, "TIMER") != 0) && - (cmd.compare(0, 6, "STATUS") != 0) && - (cmd.compare(0, 5, "FRAME") != 0)) { - message.str(""); - message << "command 0x" << std::setfill('0') << std::setw(2) << std::uppercase << std::hex << this-> - msgref << " success"; - logwrite(function, message.str()); - } - - reply.erase(0, 3); // strip off the msgref from the reply } + if ( error != NO_ERROR ) this->camera.log_error( function, message.str() ); + break; + } + memset(buffer, '\0', 2048); // init temporary buffer + retval = this->archon.Read(buffer, 2048); // read into temp buffer + if (retval <= 0) { + this->camera.log_error( function, "reading Archon" ); + break; + } + reply.append(buffer); // append read buffer into the reply string + } while(retval>0 && reply.find('\n') == std::string::npos); - // clear the semaphore (still had the mutex this entire function) - // + // If there was an Archon error then clear the busy flag and get out now + // + if ( error != NO_ERROR ) { this->archon_busy = false; - return error; } - /**************** Archon::Interface::archon_cmd *****************************/ + // The first three bytes of the reply should contain the msgref of the + // command, which can be used as a check that the received reply belongs + // to the command which was sent. + // + // Error processing command (no other information is provided by Archon) + // + if (reply.compare(0, 1, "?")==0) { // "?" means Archon experienced an error processing command + error = ERROR; + message.str(""); message << "Archon controller returned error processing command: " << cmd; + this->camera.log_error( function, message.str() ); + + } else if (reply.compare(0, 3, check)!=0) { // First 3 bytes of reply must equal checksum else reply doesn't belong to command + error = ERROR; + // std::string hdr = reply; + try { + scmd.erase(scmd.find('\n'), 1); + } catch(...) { } + message.str(""); message << "command-reply mismatch for command: " + scmd + ": expected " + check + " but received " + reply ; + this->camera.log_error( function, message.str() ); + + } else { // command and reply are a matched pair + error = NO_ERROR; + + // log the command as long as it's not a STATUS, TIMER, WCONFIG or FRAME command + if ( (cmd.compare(0,7,"WCONFIG") != 0) && + (cmd.compare(0,5,"TIMER") != 0) && + (cmd.compare(0,6,"STATUS") != 0) && + (cmd.compare(0,5,"FRAME") != 0) ) { + message.str(""); + message << "command 0x" << std::setfill('0') << std::setw(2) << std::uppercase << std::hex << this->msgref << " success"; + logwrite(function, message.str()); + } + reply.erase(0, 3); // strip off the msgref from the reply + } - /**************** Archon::Interface::read_parameter *************************/ - /** - * @fn read_parameter - * @brief read a parameter from Archon configuration memory - * @param paramname char pointer to name of paramter - * @param value reference to string for return value - * @return ERROR on error, NO_ERROR if okay. - * - * The string reference contains the value of the parameter - * to be returned to the user. - * - * No direct calls to Archon -- this function uses archon_cmd() - * which in turn handles all of the Archon in-use locking. - * - */ - long Interface::read_parameter(const std::string ¶mname, std::string &value) { - std::string function = "Archon::Interface::read_parameter"; - std::stringstream message; - std::stringstream cmd; - std::string reply; - long error = NO_ERROR; + // clear the semaphore (still had the mutex this entire function) + // + this->archon_busy = false; + + return error; + } + /**************** Archon::Interface::archon_cmd *****************************/ + + + /**************** Archon::Interface::read_parameter *************************/ + /** + * @fn read_parameter + * @brief read a parameter from Archon configuration memory + * @param paramname char pointer to name of paramter + * @param value reference to string for return value + * @return ERROR on error, NO_ERROR if okay. + * + * The string reference contains the value of the parameter + * to be returned to the user. + * + * No direct calls to Archon -- this function uses archon_cmd() + * which in turn handles all of the Archon in-use locking. + * + */ + long Interface::read_parameter(const std::string& paramname, std::string &value) { + std::string function = "Archon::Interface::read_parameter"; + std::stringstream message; + std::stringstream cmd; + std::string reply; + long error = NO_ERROR; + + if (this->parammap.find(paramname) == this->parammap.end()) { + message.str(""); message << "parameter \"" << paramname << "\" not found in ACF"; + this->camera.log_error( function, message.str() ); + return ERROR; + } - if (this->parammap.find(paramname) == this->parammap.end()) { - message.str(""); - message << "parameter \"" << paramname << "\" not found in ACF"; - this->camera.log_error(function, message.str()); - return ERROR; - } + // form the RCONFIG command to send to Archon + // + cmd.str(""); + cmd << "RCONFIG" + << std::uppercase << std::setfill('0') << std::setw(4) << std::hex + << this->parammap[paramname.c_str()].line; + error = this->archon_cmd(cmd.str(), reply); // send RCONFIG command here + + if ( error != NO_ERROR ) { + message.str(""); message << "ERROR: sending archon_cmd(" << cmd.str() << ")"; + logwrite( function, message.str() ); + return error; + } - // form the RCONFIG command to send to Archon - // - cmd.str(""); - cmd << "RCONFIG" - << std::uppercase << std::setfill('0') << std::setw(4) << std::hex - << this->parammap[paramname.c_str()].line; - error = this->archon_cmd(cmd.str(), reply); // send RCONFIG command here + try { + reply.erase(reply.find('\n'), 1); + } catch(...) { } // strip newline - if (error != NO_ERROR) { - message.str(""); - message << "ERROR: sending archon_cmd(" << cmd.str() << ")"; - logwrite(function, message.str()); - return error; - } + // reply should now be of the form PARAMETERn=PARAMNAME=VALUE, + // and we want just the VALUE here + // - try { - reply.erase(reply.find('\n'), 1); - } catch (...) { - } // strip newline + size_t loc; + value = reply; + if (value.compare(0, 9, "PARAMETER") == 0) { // value: PARAMETERn=PARAMNAME=VALUE + if ( (loc=value.find('=')) != std::string::npos ) value = value.substr(++loc); // value: PARAMNAME=VALUE + else { + value="NaN"; + error = ERROR; + } + if ( (loc=value.find('=')) != std::string::npos ) value = value.substr(++loc); // value: VALUE + else { + value="NaN"; + error = ERROR; + } - // reply should now be of the form PARAMETERn=PARAMNAME=VALUE, - // and we want just the VALUE here - // + } else { + value="NaN"; + error = ERROR; + } - size_t loc; - value = reply; - if (value.compare(0, 9, "PARAMETER") == 0) { - // value: PARAMETERn=PARAMNAME=VALUE - if ((loc = value.find('=')) != std::string::npos) value = value.substr(++loc); // value: PARAMNAME=VALUE - else { - value = "NaN"; - error = ERROR; - } - if ((loc = value.find('=')) != std::string::npos) value = value.substr(++loc); // value: VALUE - else { - value = "NaN"; - error = ERROR; - } - } else { - value = "NaN"; - error = ERROR; - } + if (error==ERROR) { + message << "malformed reply: " << reply << " to Archon command " << cmd.str() << ": Expected PARAMETERn=PARAMNAME=VALUE"; + this->camera.log_error( function, message.str() ); - if (error == ERROR) { - message << "malformed reply: " << reply << " to Archon command " << cmd.str() << - ": Expected PARAMETERn=PARAMNAME=VALUE"; - this->camera.log_error(function, message.str()); - } else { - message.str(""); - message << paramname << " = " << value; - logwrite(function, message.str()); - } - return error; + } else { + message.str(""); message << paramname << " = " << value; + logwrite(function, message.str()); } + return error; + } + /**************** Archon::Interface::read_parameter *************************/ + + + /**************** Archon::Interface::prep_parameter *************************/ + /** + * @fn prep_parameter + * @brief + * @param + * @return NO_ERROR or ERROR, return value from archon_cmd call + * + */ + long Interface::prep_parameter(const std::string& paramname, std::string value) { + std::string function = "Archon::Interface::prep_parameter"; + std::stringstream message; + std::stringstream scmd; + long error = NO_ERROR; + + // Prepare to apply it to the system -- will be loaded on next EXTLOAD signal + // + scmd << "FASTPREPPARAM " << paramname << " " << value; + error = this->archon_cmd(scmd.str()); - /**************** Archon::Interface::read_parameter *************************/ + if (error != NO_ERROR) { + message << "ERROR: prepping parameter \"" << paramname << "=" << value; + } + logwrite( function, message.str() ); + return error; + } + /**************** Archon::Interface::prep_parameter *************************/ + + + /**************** Archon::Interface::load_parameter *************************/ + /** + * @fn load_parameter + * @brief + * @param + * @return NO_ERROR or ERROR, return value from archon_cmd call + * + */ + long Interface::load_parameter(std::string paramname, std::string value) { + std::string function = "Archon::Interface::load_parameter"; + std::stringstream message; + std::stringstream scmd; + long error = NO_ERROR; + + scmd << "FASTLOADPARAM " << paramname << " " << value; + error = this->archon_cmd(scmd.str()); + + if (error != NO_ERROR) { + message << "ERROR: loading parameter \"" << paramname << "=" << value << "\" into Archon"; + + } else { + message << "parameter \"" << paramname << "=" << value << "\" loaded into Archon"; + } - /**************** Archon::Interface::prep_parameter *************************/ - /** - * @fn prep_parameter - * @brief - * @param - * @return NO_ERROR or ERROR, return value from archon_cmd call - * - */ - long Interface::prep_parameter(const std::string ¶mname, std::string value) { - std::string function = "Archon::Interface::prep_parameter"; - std::stringstream message; - std::stringstream scmd; - long error = NO_ERROR; + logwrite( function, message.str() ); + return error; + } + /**************** Archon::Interface::load_parameter *************************/ + + + /**************** Archon::Interface::fetchlog *******************************/ + /** + * @fn fetchlog + * @brief fetch the archon log entry and log the response + * @param none + * @return NO_ERROR or ERROR, return value from archon_cmd call + * + * Send the FETCHLOG command to, then read the reply from Archon. + * Fetch until the log is empty. Log the response. + * + */ + long Interface::fetchlog() { + std::string function = "Archon::Interface::fetchlog"; + std::string reply; + std::stringstream message; + long retval; + + // send FETCHLOG command while reply is not (null) + // + do { + if ( (retval=this->archon_cmd(FETCHLOG, reply)) != NO_ERROR ) { // send command here + logwrite( function, "ERROR: calling FETCHLOG" ); + return retval; + } + if (reply != "(null)") { + try { + reply.erase(reply.find('\n'), 1); + } catch(...) { } // strip newline + logwrite(function, reply); // log reply here + } + } while (reply != "(null)"); // stop when reply is (null) + + return retval; + } + /**************** Archon::Interface::fetchlog *******************************/ + + + /**************** Archon::Interface::load_timing ****************************/ + /** + * @fn load_timing + * @brief loads the ACF file and applies the timing script and parameters only + * @param acffile, specified ACF to load + * @param retstring, reference to string for return values // TODO not yet implemented + * @return + * + * This function is overloaded. + * + * This function loads the ACF file then sends the LOADTIMING command + * which parses and compiles only the timing script and parameters. + * + */ + long Interface::load_timing(std::string acffile, std::string &retstring) { + return( this->load_timing( acffile ) ); + } + long Interface::load_timing(std::string acffile) { + std::string function = "Archon::Interface::load_timing"; + + // load the ACF file into configuration memory + // + long error = this->load_acf( acffile ); - // Prepare to apply it to the system -- will be loaded on next EXTLOAD signal - // - scmd << "FASTPREPPARAM " << paramname << " " << value; - error = this->archon_cmd(scmd.str()); + // parse timing script and parameters and apply them to the system + // + if (error == NO_ERROR) error = this->archon_cmd(LOADTIMING); + + return error; + } + /**************** Archon::Interface::load_timing ****************************/ + + + /**************** Archon::Interface::load_firmware **************************/ + /** + * @fn load_firmware + * @brief loads the ACF file and applies the complete system configuration + * @param none + * @return + * + * This function is overloaded. + * + * This version takes a single argument for the acf file to load. + * + * This function loads the ACF file and then sends an APPLYALL which + * parses and applies the complete system configuration from the + * configuration memory just loaded. The detector power will be off. + * + */ + long Interface::load_firmware(std::string acffile) { + // load the ACF file into configuration memory + // + long error = this->load_acf( acffile ); - if (error != NO_ERROR) { - message << "ERROR: prepping parameter \"" << paramname << "=" << value; - } + // Parse and apply the complete system configuration from configuration memory. + // Detector power will be off after this. + // + if (error == NO_ERROR) error = this->archon_cmd(APPLYALL); - logwrite(function, message.str()); - return error; + if ( error != NO_ERROR ) this->fetchlog(); + + // If no errors then automatically set the mode to DEFAULT. + // This should come after APPLYALL in case any new parameters need to be written, + // which shouldn't be done until after they have been applied. + // + if ( error == NO_ERROR ) error = this->set_camera_mode( std::string( "DEFAULT" ) ); + + return error; + } + /**************** Archon::Interface::load_firmware **************************/ + /** + * @fn load_firmware + * @brief + * @param none + * @return + * + * This function is overloaded. + * + * This version is for future compatibility. + * The multiple-controller version will pass a reference to a return string. + * + */ + long Interface::load_firmware(std::string acffile, std::string &retstring) { + return( this->load_firmware( acffile ) ); + } + /**************** Archon::Interface::load_firmware **************************/ + + + /**************** Archon::Interface::load_acf *******************************/ + /** + * @fn load_acf + * @brief loads the ACF file into configuration memory (no APPLY!) + * @param acffile + * @return ERROR or NO_ERROR + * + * This function loads the specfied file into configuration memory. + * While the ACF is being read, an internal database (STL map) is being + * created to allow lookup access to the ACF file or parameters. + * + * The [MODE_XXX] sections are also parsed and parameters as a function + * of mode are saved in their own database. + * + * This function only loads (WCONFIGxxx) the configuration memory; it does + * not apply it to the system. Therefore, this function must be followed + * with a LOADTIMING or APPLYALL command, for example. + * + */ + long Interface::load_acf(std::string acffile) { + std::string function = "Archon::Interface::load_acf"; + std::stringstream message; + std::fstream filestream; // I/O stream class + std::string line; // the line read from the acffile + std::string mode; + std::string keyword, keystring, keyvalue, keytype, keycomment; + std::stringstream sscmd; + std::string key, value; + + int linecount; // the Archon configuration line number is required for writing back to config memory + long error=NO_ERROR; + bool parse_config=false; + + // get the acf filename, either passed here or from loaded default + // + if ( acffile.empty() ) { + acffile = this->camera.firmware[0]; + + } else { + this->camera.firmware[0] = acffile; } - /**************** Archon::Interface::prep_parameter *************************/ + // try to open the file + // + try { + filestream.open(acffile, std::ios_base::in); + } catch(...) { + message << "opening acf file " << acffile << ": " << std::strerror(errno); + this->camera.log_error( function, message.str() ); + return ERROR; + } - /**************** Archon::Interface::load_parameter *************************/ - /** - * @fn load_parameter - * @brief - * @param - * @return NO_ERROR or ERROR, return value from archon_cmd call - * - */ - long Interface::load_parameter(std::string paramname, std::string value) { - std::string function = "Archon::Interface::load_parameter"; - std::stringstream message; - std::stringstream scmd; - long error = NO_ERROR; + if ( ! filestream.is_open() || ! filestream.good() ) { + message << "acf file " << acffile << " could not be opened"; + this->camera.log_error( function, message.str() ); + return ERROR; + } - scmd << "FASTLOADPARAM " << paramname << " " << value; - error = this->archon_cmd(scmd.str()); + logwrite(function, acffile); - if (error != NO_ERROR) { - message << "ERROR: loading parameter \"" << paramname << "=" << value << "\" into Archon"; - } else { - message << "parameter \"" << paramname << "=" << value << "\" loaded into Archon"; - } + // The CPU in Archon is single threaded, so it checks for a network + // command, then does some background polling (reading bias voltages etc.), + // then checks again for a network command. "POLLOFF" disables this + // background checking, so network command responses are very fast. + // The downside is that bias voltages, temperatures, etc. are not updated + // until you give a "POLLON". + // + error = this->archon_cmd(POLLOFF); - logwrite(function, message.str()); + // clear configuration memory for this controller + // + if (error == NO_ERROR) error = this->archon_cmd(CLEARCONFIG); + + if ( error != NO_ERROR ) { + logwrite( function, "ERROR: could not prepare Archon for new ACF" ); return error; } - /**************** Archon::Interface::load_parameter *************************/ + // Any failure after clearing the configuration memory will mean + // no firmware is loaded. + // + this->firmwareloaded = false; + modemap.clear(); // file is open, clear all modes - /**************** Archon::Interface::fetchlog *******************************/ - /** - * @fn fetchlog - * @brief fetch the archon log entry and log the response - * @param none - * @return NO_ERROR or ERROR, return value from archon_cmd call - * - * Send the FETCHLOG command to, then read the reply from Archon. - * Fetch until the log is empty. Log the response. - * - */ - long Interface::fetchlog() { - std::string function = "Archon::Interface::fetchlog"; - std::string reply; - std::stringstream message; - long retval; + linecount = 0; // init Archon configuration line number - // send FETCHLOG command while reply is not (null) - // - do { - if ((retval = this->archon_cmd(FETCHLOG, reply)) != NO_ERROR) { - // send command here - logwrite(function, "ERROR: calling FETCHLOG"); - return retval; - } - if (reply != "(null)") { - try { - reply.erase(reply.find('\n'), 1); - } catch (...) { - } // strip newline - logwrite(function, reply); // log reply here - } - } while (reply != "(null)"); // stop when reply is (null) + while ( getline(filestream, line) ) { // note that getline discards the newline "\n" character - return retval; - } + // don't start parsing until [CONFIG] and stop on a newline or [SYSTEM] + // + if (line == "[CONFIG]") { parse_config=true; continue; } + if (line == "\n" ) { parse_config=false; continue; } + if (line == "[SYSTEM]") { parse_config=false; continue; } - /**************** Archon::Interface::fetchlog *******************************/ + std::string savedline = line; // save un-edited line for error reporting + // parse mode sections, looking for "[MODE_xxxxx]" + // + if (line.substr(0,6)=="[MODE_") { // this is a mode section + try { + line.erase(line.find('['), 1); // erase the opening square bracket + line.erase(line.find(']'), 1); // erase the closing square bracket + + } catch(...) { + message.str(""); message << "malformed mode section: " << savedline << ": expected [MODE_xxxx]"; + this->camera.log_error( function, message.str() ); + filestream.close(); + return ERROR; + } + if ( ! line.empty() ) { // What's remaining should be MODE_xxxxx + mode = line.substr(5); // everything after "MODE_" is the mode name + std::transform( mode.begin(), mode.end(), mode.begin(), ::toupper ); // make uppercase + + // got a mode, now check if one of this name has already been located + // and put into the modemap + // + if ( this->modemap.find(mode) != this->modemap.end() ) { + message.str(""); message << "duplicate definition of mode: " << mode << ": load aborted"; + this->camera.log_error( function, message.str() ); + filestream.close(); + return ERROR; - /**************** Archon::Interface::load_timing ****************************/ - /** - * @fn load_timing - * @brief loads the ACF file and applies the timing script and parameters only - * @param acffile, specified ACF to load - * @param retstring, reference to string for return values // TODO not yet implemented - * @return - * - * This function is overloaded. - * - * This function loads the ACF file then sends the LOADTIMING command - * which parses and compiles only the timing script and parameters. - * - */ - long Interface::load_timing(std::string acffile, std::string &retstring) { - return (this->load_timing(acffile)); - } + } else { + parse_config = true; + message.str(""); message << "detected mode: " << mode; logwrite(function, message.str()); + this->modemap[mode].rawenable=-1; // initialize to -1, require it be set somewhere in the ACF + // this also ensures something is saved in the modemap for this mode + } - long Interface::load_timing(std::string acffile) { - std::string function = "Archon::Interface::load_timing"; + } else { // somehow there's no xxx left after removing "[MODE_" and "]" + message.str(""); message << "malformed mode section: " << savedline << ": expected [MODE_xxxx]"; + this->camera.log_error( function, message.str() ); + filestream.close(); + return ERROR; + } + } - // load the ACF file into configuration memory - // - long error = this->load_acf(acffile); + // Everything else is for parsing configuration lines so if we didn't get [CONFIG] then + // skip to the next line. + // + if (!parse_config) continue; + + // replace any TAB characters with a space + // + string_replace_char(line, "\t", " "); + + // replace any backslash characters with a forward slash + // + string_replace_char(line, "\\", "/"); + + // erase all quotes + try { + line.erase( std::remove(line.begin(), line.end(), '"'), line.end() ); + } catch(...) { } + + // Initialize key, value strings used to form WCONFIG KEY=VALUE command. + // As long as key stays empty then the WCONFIG command is not written to the Archon. + // This is what keeps TAGS: in the [MODE_xxxx] sections from being written to Archon, + // because these do not populate key. + // + key=""; + value=""; + + // ************************************************************ + // Store actual Archon parameters in their own STL map IN ADDITION to the map + // in which all other keywords are store, so that they can be accessed in + // a different way. Archon PARAMETER KEY=VALUE paris are formatted as: + // PARAMETERn=ParameterName=value + // where "PARAMETERn" is the key and "ParameterName=value" is the value. + // However, it is logical to access them by ParameterName only. That is what the + // parammap is for, hence the need for this STL map indexed on only the "ParameterName" + // portion of the value. Conversely, the configmap is indexed by the key. + // + // parammap stores ONLY the parameters, which are identified as PARAMETERxx="paramname=value" + // configmap stores every configuration line sent to Archon (which includes parameters) + // + // In order to modify these keywords in Archon, the entire above phrase + // (KEY=VALUE pair) must be preserved along with the line number on which it + // occurs in the config file. + // ************************************************************ + + // Look for TAGS: in the .acf file mode sections + // + // If tag is "ACF:" then it's a .acf line (could be a parameter or configuration) + // + if (line.compare(0,4,"ACF:")==0) { + std::vector tokens; + line = line.substr(4); // strip off the "ACF:" portion + std::string acf_key, acf_value; - // parse timing script and parameters and apply them to the system - // - if (error == NO_ERROR) error = this->archon_cmd(LOADTIMING); + try { + Tokenize(line, tokens, "="); // separate into tokens by "=" - return error; - } + if (tokens.size() == 1) { // KEY=, the VALUE is empty + acf_key = tokens[0]; + acf_value = ""; - /**************** Archon::Interface::load_timing ****************************/ + } else if (tokens.size() == 2) { // KEY=VALUE + acf_key = tokens[0]; + acf_value = tokens[1]; + } else { + message.str(""); message << "malformed ACF line: " << savedline << ": expected KEY=VALUE"; + this->camera.log_error( function, message.str() ); + filestream.close(); + return ERROR; + } + + bool keymatch = false; + + // If this key is in the main parammap then store it in the modemap's parammap for this mode + if (this->parammap.find( acf_key ) != this->parammap.end()) { + this->modemap[mode].parammap[ acf_key ].name = acf_key; + this->modemap[mode].parammap[ acf_key ].value = acf_value; + keymatch = true; + } + + // If this key is in the main configmap, then store it in the modemap's configmap for this mode + // + if (this->configmap.find( acf_key ) != this->configmap.end()) { + this->modemap[mode].configmap[ acf_key ].value = acf_value; + keymatch = true; + } + + // If this key is neither in the parammap nor in the configmap then return an error + // + if ( ! keymatch ) { + message.str(""); + message << "[MODE_" << mode << "] ACF directive: " << acf_key << "=" << acf_value << " is not a valid parameter or configuration key"; + logwrite(function, message.str()); + filestream.close(); + return ERROR; + } + + } catch ( ... ) { + message.str(""); message << "extracting KEY=VALUE pair from ACF line: " << savedline; + this->camera.log_error( function, message.str() ); + filestream.close(); + return ERROR; + } + // end if (line.compare(0,4,"ACF:")==0) + + } else if (line.compare(0,5,"ARCH:")==0) { + // The "ARCH:" tag is for internal (Archon_interface) variables + // using the KEY=VALUE format. + // + std::vector tokens; + line = line.substr(5); // strip off the "ARCH:" portion + Tokenize(line, tokens, "="); // separate into KEY, VALUE tokens + if (tokens.size() != 2) { + message.str(""); message << "malformed ARCH line: " << savedline << ": expected ARCH:KEY=VALUE"; + this->camera.log_error( function, message.str() ); + filestream.close(); + return ERROR; + } + if ( tokens[0] == "NUM_DETECT" ) { + this->modemap[mode].geometry.num_detect = std::stoi( tokens[1] ); - /**************** Archon::Interface::load_firmware **************************/ - /** - * @fn load_firmware - * @brief loads the ACF file and applies the complete system configuration - * @param none - * @return - * - * This function is overloaded. - * - * This version takes a single argument for the acf file to load. - * - * This function loads the ACF file and then sends an APPLYALL which - * parses and applies the complete system configuration from the - * configuration memory just loaded. The detector power will be off. - * - */ - long Interface::load_firmware(std::string acffile) { - // load the ACF file into configuration memory - // - long error = this->load_acf(acffile); + } else if ( tokens[0] == "HORI_AMPS" ) { + this->modemap[mode].geometry.amps[0] = std::stoi( tokens[1] ); - // Parse and apply the complete system configuration from configuration memory. - // Detector power will be off after this. - // - if (error == NO_ERROR) error = this->archon_cmd(APPLYALL); + } else if ( tokens[0] == "VERT_AMPS" ) { + this->modemap[mode].geometry.amps[1] = std::stoi( tokens[1] ); - if (error != NO_ERROR) this->fetchlog(); + } else { + message.str(""); message << "unrecognized internal parameter specified: "<< tokens[0]; + this->camera.log_error( function, message.str() ); + filestream.close(); + return ERROR; + } + // end else if (line.compare(0,5,"ARCH:")==0) + + } else if (line.compare(0,5,"FITS:")==0) { + // the "FITS:" tag is used to write custom keyword entries of the form "FITS:KEYWORD=VALUE/COMMENT" + // + std::vector tokens; + line = line.substr(5); // strip off the "FITS:" portion + + // First, tokenize on the equal sign "=". + // The token left of "=" is the keyword. Immediate right is the value + Tokenize(line, tokens, "="); + if (tokens.size() != 2) { // need at least two tokens at this point + message.str(""); message << "malformed FITS command: " << savedline << ": expected KEYWORD=value/comment"; + this->camera.log_error( function, message.str() ); + filestream.close(); + return ERROR; + } + keyword = tokens[0].substr(0,8); // truncate keyword to 8 characters + keystring = tokens[1]; // tokenize the rest in a moment + keycomment = ""; // initialize comment, assumed empty unless specified below + + // Next, tokenize on the slash "/". + // The token left of "/" is the value. Anything to the right is a comment. + // + Tokenize(keystring, tokens, "/"); + + if (tokens.empty()) { // no tokens found means no "/" characeter which means no comment + keyvalue = keystring; // therefore the keyvalue is the entire string + } + + if (not tokens.empty()) { // at least one token + keyvalue = tokens[0]; + } + + if (tokens.size() == 2) { // If there are two tokens here then the second is a comment + keycomment = tokens[1]; + } + + if (tokens.size() > 2) { // everything below this has been covered + message.str(""); message << "malformed FITS command: " << savedline << ": expected KEYWORD=VALUE/COMMENT"; + this->camera.log_error( function, message.str() ); + message.str(""); message << "too many \"/\" in comment string? " << keystring; + this->camera.log_error( function, message.str() ); + filestream.close(); + return ERROR; + } + + // Save all the user keyword information in a map for later + this->modemap[mode].acfkeys.keydb[keyword].keyword = keyword; + this->modemap[mode].acfkeys.keydb[keyword].keytype = this->camera_info.userkeys.get_keytype(keyvalue); + this->modemap[mode].acfkeys.keydb[keyword].keyvalue = keyvalue; + this->modemap[mode].acfkeys.keydb[keyword].keycomment = keycomment; + // end if (line.compare(0,5,"FITS:")==0) + // + // ----- all done looking for "TAGS:" ----- + // + + } else if ( (line.compare(0,11,"PARAMETERS=")!=0) && // not the "PARAMETERS=xx line + (line.compare(0, 9,"PARAMETER" )==0) ) { // but must start with "PARAMETER" + // If this is a PARAMETERn=ParameterName=value KEY=VALUE pair... + // + + std::vector tokens; + Tokenize(line, tokens, "="); // separate into PARAMETERn, ParameterName, value tokens + + if (tokens.size() != 3) { + message.str(""); message << "malformed paramter line: " << savedline << ": expected PARAMETERn=Param=value"; + this->camera.log_error( function, message.str() ); + filestream.close(); + return ERROR; + } + + // Tokenize broke everything up at the "=" and + // we need all three parts, but we also need a version containing the last + // two parts together, "ParameterName=value" so re-assemble them here. + // + std::stringstream paramnamevalue; + paramnamevalue << tokens[1] << "=" << tokens[2]; // reassemble ParameterName=value string + + // build an STL map "configmap" indexed on PARAMETERn, the part before the first "=" sign + // + this->configmap[ tokens[0] ].line = linecount; // configuration line number + this->configmap[ tokens[0] ].value = paramnamevalue.str(); // configuration value for PARAMETERn + + // build an STL map "parammap" indexed on ParameterName so that we can look up by the actual name + // + this->parammap[ tokens[1] ].key = tokens[0]; // PARAMETERn + this->parammap[ tokens[1] ].name = tokens[1] ; // ParameterName + this->parammap[ tokens[1] ].value = tokens[2]; // value + this->parammap[ tokens[1] ].line = linecount; // line number + + // assemble a KEY=VALUE pair used to form the WCONFIG command + key = tokens[0]; // PARAMETERn + value = paramnamevalue.str(); // ParameterName=value + // end If this is a PARAMETERn=ParameterName=value KEY=VALUE pair... + + } else { + // ...otherwise, for all other KEY=VALUE pairs, there is only the value and line number + // to be indexed by the key. Some lines may be equal to blank, e.g. "CONSTANTx=" so that + // only one token is made + // + std::vector tokens; + // Tokenize will return a size=1 even if there are no delimiters, + // so work around this by first checking for delimiters + // before calling Tokenize. + // + if (line.find_first_of('=', 0) == std::string::npos) { + continue; + } + Tokenize(line, tokens, "="); // separate into KEY, VALUE tokens + if (tokens.empty()) { + continue; // nothing to do here if no tokens (ie no "=") + } + if (!tokens.empty() ) { // at least one token is the key + key = tokens[0]; // KEY + value = ""; // VALUE can be empty (e.g. labels not required) + this->configmap[ tokens[0] ].line = linecount; + this->configmap[ tokens[0] ].value = value; + } + if (tokens.size() > 1 ) { // if a second token then that's the value + value = tokens[1]; // VALUE (there is a second token) + this->configmap[ tokens[0] ].value = tokens[1]; + } + } // end else + + // Form the WCONFIG command to Archon and + // write the config line to the controller memory (if key is not empty). + // + if ( !key.empty() ) { // value can be empty but key cannot + sscmd.str(""); + sscmd << "WCONFIG" + << std::uppercase << std::setfill('0') << std::setw(4) << std::hex + << linecount + << key << "=" << value << "\n"; + // send the WCONFIG command here + if (error == NO_ERROR) error = this->archon_cmd(sscmd.str()); + } // end if ( !key.empty() && !value.empty() ) + linecount++; + } // end while ( getline(filestream, line) ) + + // re-enable background polling + // + if (error == NO_ERROR) error = this->archon_cmd(POLLON); + + filestream.close(); + if (error == NO_ERROR) { + logwrite(function, "loaded Archon config file OK"); + this->firmwareloaded = true; + + // add to systemkeys keyword database + // + std::stringstream keystr; + keystr << "FIRMWARE=" << acffile << "// controller firmware"; + this->systemkeys.addkey( keystr.str() ); + } - // If no errors then automatically set the mode to DEFAULT. - // This should come after APPLYALL in case any new parameters need to be written, - // which shouldn't be done until after they have been applied. - // - if (error == NO_ERROR) error = this->set_camera_mode(std::string("DEFAULT")); + // If there was an Archon error then read the Archon error log + // + if (error != NO_ERROR) error = this->fetchlog(); - return error; + this->modeselected = false; // require that a mode be selected after loading new firmware + + // Even if exptime, longexposure were previously set, a new ACF could have different + // default values than the server has, so reset these to "undefined" in order to + // force the server to ask for them. + // + this->camera_info.exposure_time = -1; + this->camera_info.exposure_factor = -1; + this->camera_info.exposure_unit.clear(); + + return error; + } + /**************** Archon::Interface::load_acf *******************************/ + + + /**************** Archon::Interface::set_camera_mode ************************/ + /** + * @fn set_camera_mode + * @brief + * @param none + * @return + * + */ + long Interface::set_camera_mode(std::string mode) { + std::string function = "Archon::Interface::set_camera_mode"; + std::stringstream message; + bool configchanged = false; + bool paramchanged = false; + long error; + + // No point in trying anything if no firmware has been loaded yet + // + if ( ! this->firmwareloaded ) { + this->camera.log_error( function, "no firmware loaded" ); + return ERROR; } - /**************** Archon::Interface::load_firmware **************************/ - /** - * @fn load_firmware - * @brief - * @param none - * @return - * - * This function is overloaded. - * - * This version is for future compatibility. - * The multiple-controller version will pass a reference to a return string. - * - */ - long Interface::load_firmware(std::string acffile, std::string &retstring) { - return (this->load_firmware(acffile)); + std::transform( mode.begin(), mode.end(), mode.begin(), ::toupper ); // make uppercase + + // The requested mode must have been read in the current ACF file + // and put into the modemap... + // + if (this->modemap.find(mode) == this->modemap.end()) { + message.str(""); message << "undefined mode " << mode << " in ACF file " << this->camera.firmware[0]; + this->camera.log_error( function, message.str() ); + return ERROR; } - /**************** Archon::Interface::load_firmware **************************/ + // load specific mode settings from .acf and apply to Archon + // + if ( load_mode_settings(mode) != NO_ERROR) { + message.str(""); message << "ERROR: failed to load mode settings for mode: " << mode; + logwrite( function, message.str() ); + return ERROR; + } + // set internal variables based on new .acf values loaded + // + error = NO_ERROR; + if (error==NO_ERROR) error = get_configmap_value("FRAMEMODE", this->modemap[mode].geometry.framemode); + if (error==NO_ERROR) error = get_configmap_value("LINECOUNT", this->modemap[mode].geometry.linecount); + if (error==NO_ERROR) error = get_configmap_value("PIXELCOUNT", this->modemap[mode].geometry.pixelcount); + if (error==NO_ERROR) error = get_configmap_value("RAWENABLE", this->modemap[mode].rawenable); + if (error==NO_ERROR) error = get_configmap_value("RAWSEL", this->rawinfo.adchan); + if (error==NO_ERROR) error = get_configmap_value("RAWSAMPLES", this->rawinfo.rawsamples); + if (error==NO_ERROR) error = get_configmap_value("RAWENDLINE", this->rawinfo.rawlines); + + #ifdef LOGLEVEL_DEBUG + message.str(""); + message << "[DEBUG] mode=" << mode << " RAWENABLE=" << this->modemap[mode].rawenable + << " RAWSAMPLES=" << this->rawinfo.rawsamples << " RAWLINES=" << this->rawinfo.rawlines; + logwrite(function, message.str()); + #endif - /**************** Archon::Interface::load_acf *******************************/ - /** - * @fn load_acf - * @brief loads the ACF file into configuration memory (no APPLY!) - * @param acffile - * @return ERROR or NO_ERROR - * - * This function loads the specfied file into configuration memory. - * While the ACF is being read, an internal database (STL map) is being - * created to allow lookup access to the ACF file or parameters. - * - * The [MODE_XXX] sections are also parsed and parameters as a function - * of mode are saved in their own database. - * - * This function only loads (WCONFIGxxx) the configuration memory; it does - * not apply it to the system. Therefore, this function must be followed - * with a LOADTIMING or APPLYALL command, for example. - * - */ - long Interface::load_acf(std::string acffile) { - std::string function = "Archon::Interface::load_acf"; - std::stringstream message; - std::fstream filestream; // I/O stream class - std::string line; // the line read from the acffile - std::string mode; - std::string keyword, keystring, keyvalue, keytype, keycomment; - std::stringstream sscmd; - std::string key, value; - - int linecount; // the Archon configuration line number is required for writing back to config memory - long error = NO_ERROR; - bool parse_config = false; - - // get the acf filename, either passed here or from loaded default - // - if (acffile.empty()) { - acffile = this->camera.firmware[0]; - } else { - this->camera.firmware[0] = acffile; - } - - // try to open the file - // - try { - filestream.open(acffile, std::ios_base::in); - } catch (...) { - message << "opening acf file " << acffile << ": " << std::strerror(errno); - this->camera.log_error(function, message.str()); - return ERROR; - } - - if (!filestream.is_open() || !filestream.good()) { - message << "acf file " << acffile << " could not be opened"; - this->camera.log_error(function, message.str()); - return ERROR; - } - - logwrite(function, acffile); - - // The CPU in Archon is single threaded, so it checks for a network - // command, then does some background polling (reading bias voltages etc.), - // then checks again for a network command. "POLLOFF" disables this - // background checking, so network command responses are very fast. - // The downside is that bias voltages, temperatures, etc. are not updated - // until you give a "POLLON". - // - error = this->archon_cmd(POLLOFF); - - // clear configuration memory for this controller - // - if (error == NO_ERROR) error = this->archon_cmd(CLEARCONFIG); - - if (error != NO_ERROR) { - logwrite(function, "ERROR: could not prepare Archon for new ACF"); - return error; - } - - // Any failure after clearing the configuration memory will mean - // no firmware is loaded. - // - this->firmwareloaded = false; - - modemap.clear(); // file is open, clear all modes - - linecount = 0; // init Archon configuration line number - - while (getline(filestream, line)) { - // note that getline discards the newline "\n" character - - // don't start parsing until [CONFIG] and stop on a newline or [SYSTEM] - // - if (line == "[CONFIG]") { - parse_config = true; - continue; - } - if (line == "\n") { - parse_config = false; - continue; - } - if (line == "[SYSTEM]") { - parse_config = false; - continue; - } - - std::string savedline = line; // save un-edited line for error reporting - - // parse mode sections, looking for "[MODE_xxxxx]" - // - if (line.substr(0, 6) == "[MODE_") { - // this is a mode section - try { - line.erase(line.find('['), 1); // erase the opening square bracket - line.erase(line.find(']'), 1); // erase the closing square bracket - } catch (...) { - message.str(""); - message << "malformed mode section: " << savedline << ": expected [MODE_xxxx]"; - this->camera.log_error(function, message.str()); - filestream.close(); - return ERROR; - } - if (!line.empty()) { - // What's remaining should be MODE_xxxxx - mode = line.substr(5); // everything after "MODE_" is the mode name - std::transform(mode.begin(), mode.end(), mode.begin(), ::toupper); // make uppercase - - // got a mode, now check if one of this name has already been located - // and put into the modemap - // - if (this->modemap.find(mode) != this->modemap.end()) { - message.str(""); - message << "duplicate definition of mode: " << mode << ": load aborted"; - this->camera.log_error(function, message.str()); - filestream.close(); - return ERROR; - } else { - parse_config = true; - message.str(""); - message << "detected mode: " << mode; - logwrite(function, message.str()); - this->modemap[mode].rawenable = -1; // initialize to -1, require it be set somewhere in the ACF - // this also ensures something is saved in the modemap for this mode - } - } else { - // somehow there's no xxx left after removing "[MODE_" and "]" - message.str(""); - message << "malformed mode section: " << savedline << ": expected [MODE_xxxx]"; - this->camera.log_error(function, message.str()); - filestream.close(); - return ERROR; - } - } - - // Everything else is for parsing configuration lines so if we didn't get [CONFIG] then - // skip to the next line. - // - if (!parse_config) continue; - - // replace any TAB characters with a space - // - string_replace_char(line, "\t", " "); - - // replace any backslash characters with a forward slash - // - string_replace_char(line, "\\", "/"); - - // erase all quotes - try { - line.erase(std::remove(line.begin(), line.end(), '"'), line.end()); - } catch (...) { - } - - // Initialize key, value strings used to form WCONFIG KEY=VALUE command. - // As long as key stays empty then the WCONFIG command is not written to the Archon. - // This is what keeps TAGS: in the [MODE_xxxx] sections from being written to Archon, - // because these do not populate key. - // - key = ""; - value = ""; - - // ************************************************************ - // Store actual Archon parameters in their own STL map IN ADDITION to the map - // in which all other keywords are store, so that they can be accessed in - // a different way. Archon PARAMETER KEY=VALUE paris are formatted as: - // PARAMETERn=ParameterName=value - // where "PARAMETERn" is the key and "ParameterName=value" is the value. - // However, it is logical to access them by ParameterName only. That is what the - // parammap is for, hence the need for this STL map indexed on only the "ParameterName" - // portion of the value. Conversely, the configmap is indexed by the key. - // - // parammap stores ONLY the parameters, which are identified as PARAMETERxx="paramname=value" - // configmap stores every configuration line sent to Archon (which includes parameters) - // - // In order to modify these keywords in Archon, the entire above phrase - // (KEY=VALUE pair) must be preserved along with the line number on which it - // occurs in the config file. - // ************************************************************ - - // Look for TAGS: in the .acf file mode sections - // - // If tag is "ACF:" then it's a .acf line (could be a parameter or configuration) - // - if (line.compare(0, 4, "ACF:") == 0) { - std::vector tokens; - line = line.substr(4); // strip off the "ACF:" portion - std::string acf_key, acf_value; - - try { - Tokenize(line, tokens, "="); // separate into tokens by "=" - - if (tokens.size() == 1) { - // KEY=, the VALUE is empty - acf_key = tokens[0]; - acf_value = ""; - } else if (tokens.size() == 2) { - // KEY=VALUE - acf_key = tokens[0]; - acf_value = tokens[1]; - } else { - message.str(""); - message << "malformed ACF line: " << savedline << ": expected KEY=VALUE"; - this->camera.log_error(function, message.str()); - filestream.close(); - return ERROR; - } - - bool keymatch = false; - - // If this key is in the main parammap then store it in the modemap's parammap for this mode - if (this->parammap.find(acf_key) != this->parammap.end()) { - this->modemap[mode].parammap[acf_key].name = acf_key; - this->modemap[mode].parammap[acf_key].value = acf_value; - keymatch = true; - } - - // If this key is in the main configmap, then store it in the modemap's configmap for this mode - // - if (this->configmap.find(acf_key) != this->configmap.end()) { - this->modemap[mode].configmap[acf_key].value = acf_value; - keymatch = true; - } - - // If this key is neither in the parammap nor in the configmap then return an error - // - if (!keymatch) { - message.str(""); - message << "[MODE_" << mode << "] ACF directive: " << acf_key << "=" << acf_value << - " is not a valid parameter or configuration key"; - logwrite(function, message.str()); - filestream.close(); - return ERROR; - } - } catch (...) { - message.str(""); - message << "extracting KEY=VALUE pair from ACF line: " << savedline; - this->camera.log_error(function, message.str()); - filestream.close(); - return ERROR; - } - // end if (line.compare(0,4,"ACF:")==0) - } else if (line.compare(0, 5, "ARCH:") == 0) { - // The "ARCH:" tag is for internal (Archon_interface) variables - // using the KEY=VALUE format. - // - std::vector tokens; - line = line.substr(5); // strip off the "ARCH:" portion - Tokenize(line, tokens, "="); // separate into KEY, VALUE tokens - if (tokens.size() != 2) { - message.str(""); - message << "malformed ARCH line: " << savedline << ": expected ARCH:KEY=VALUE"; - this->camera.log_error(function, message.str()); - filestream.close(); - return ERROR; - } - if (tokens[0] == "NUM_DETECT") { - this->modemap[mode].geometry.num_detect = std::stoi(tokens[1]); - } else if (tokens[0] == "HORI_AMPS") { - this->modemap[mode].geometry.amps[0] = std::stoi(tokens[1]); - } else if (tokens[0] == "VERT_AMPS") { - this->modemap[mode].geometry.amps[1] = std::stoi(tokens[1]); - } else { - message.str(""); - message << "unrecognized internal parameter specified: " << tokens[0]; - this->camera.log_error(function, message.str()); - filestream.close(); - return ERROR; - } - // end else if (line.compare(0,5,"ARCH:")==0) - } else if (line.compare(0, 5, "FITS:") == 0) { - // the "FITS:" tag is used to write custom keyword entries of the form "FITS:KEYWORD=VALUE/COMMENT" - // - std::vector tokens; - line = line.substr(5); // strip off the "FITS:" portion - - // First, tokenize on the equal sign "=". - // The token left of "=" is the keyword. Immediate right is the value - Tokenize(line, tokens, "="); - if (tokens.size() != 2) { - // need at least two tokens at this point - message.str(""); - message << "malformed FITS command: " << savedline << ": expected KEYWORD=value/comment"; - this->camera.log_error(function, message.str()); - filestream.close(); - return ERROR; - } - keyword = tokens[0].substr(0, 8); // truncate keyword to 8 characters - keystring = tokens[1]; // tokenize the rest in a moment - keycomment = ""; // initialize comment, assumed empty unless specified below - - // Next, tokenize on the slash "/". - // The token left of "/" is the value. Anything to the right is a comment. - // - Tokenize(keystring, tokens, "/"); - - if (tokens.empty()) { - // no tokens found means no "/" characeter which means no comment - keyvalue = keystring; // therefore the keyvalue is the entire string - } - - if (not tokens.empty()) { - // at least one token - keyvalue = tokens[0]; - } - - if (tokens.size() == 2) { - // If there are two tokens here then the second is a comment - keycomment = tokens[1]; - } - - if (tokens.size() > 2) { - // everything below this has been covered - message.str(""); - message << "malformed FITS command: " << savedline << ": expected KEYWORD=VALUE/COMMENT"; - this->camera.log_error(function, message.str()); - message.str(""); - message << "too many \"/\" in comment string? " << keystring; - this->camera.log_error(function, message.str()); - filestream.close(); - return ERROR; - } - - // Save all the user keyword information in a map for later - this->modemap[mode].acfkeys.keydb[keyword].keyword = keyword; - this->modemap[mode].acfkeys.keydb[keyword].keytype = this->camera_info.userkeys.get_keytype(keyvalue); - this->modemap[mode].acfkeys.keydb[keyword].keyvalue = keyvalue; - this->modemap[mode].acfkeys.keydb[keyword].keycomment = keycomment; - // end if (line.compare(0,5,"FITS:")==0) - // - // ----- all done looking for "TAGS:" ----- - // - } else if ((line.compare(0, 11, "PARAMETERS=") != 0) && // not the "PARAMETERS=xx line - (line.compare(0, 9, "PARAMETER") == 0)) { - // but must start with "PARAMETER" - // If this is a PARAMETERn=ParameterName=value KEY=VALUE pair... - // - - std::vector tokens; - Tokenize(line, tokens, "="); // separate into PARAMETERn, ParameterName, value tokens - - if (tokens.size() != 3) { - message.str(""); - message << "malformed paramter line: " << savedline << ": expected PARAMETERn=Param=value"; - this->camera.log_error(function, message.str()); - filestream.close(); - return ERROR; - } - - // Tokenize broke everything up at the "=" and - // we need all three parts, but we also need a version containing the last - // two parts together, "ParameterName=value" so re-assemble them here. - // - std::stringstream paramnamevalue; - paramnamevalue << tokens[1] << "=" << tokens[2]; // reassemble ParameterName=value string - - // build an STL map "configmap" indexed on PARAMETERn, the part before the first "=" sign - // - this->configmap[tokens[0]].line = linecount; // configuration line number - this->configmap[tokens[0]].value = paramnamevalue.str(); // configuration value for PARAMETERn - - // build an STL map "parammap" indexed on ParameterName so that we can look up by the actual name - // - this->parammap[tokens[1]].key = tokens[0]; // PARAMETERn - this->parammap[tokens[1]].name = tokens[1]; // ParameterName - this->parammap[tokens[1]].value = tokens[2]; // value - this->parammap[tokens[1]].line = linecount; // line number - - // assemble a KEY=VALUE pair used to form the WCONFIG command - key = tokens[0]; // PARAMETERn - value = paramnamevalue.str(); // ParameterName=value - // end If this is a PARAMETERn=ParameterName=value KEY=VALUE pair... - } else { - // ...otherwise, for all other KEY=VALUE pairs, there is only the value and line number - // to be indexed by the key. Some lines may be equal to blank, e.g. "CONSTANTx=" so that - // only one token is made - // - std::vector tokens; - // Tokenize will return a size=1 even if there are no delimiters, - // so work around this by first checking for delimiters - // before calling Tokenize. - // - if (line.find_first_of('=', 0) == std::string::npos) { - continue; - } - Tokenize(line, tokens, "="); // separate into KEY, VALUE tokens - if (tokens.empty()) { - continue; // nothing to do here if no tokens (ie no "=") - } - if (!tokens.empty()) { - // at least one token is the key - key = tokens[0]; // KEY - value = ""; // VALUE can be empty (e.g. labels not required) - this->configmap[tokens[0]].line = linecount; - this->configmap[tokens[0]].value = value; - } - if (tokens.size() > 1) { - // if a second token then that's the value - value = tokens[1]; // VALUE (there is a second token) - this->configmap[tokens[0]].value = tokens[1]; - } - } // end else - - // Form the WCONFIG command to Archon and - // write the config line to the controller memory (if key is not empty). - // - if (!key.empty()) { - // value can be empty but key cannot - sscmd.str(""); - sscmd << "WCONFIG" - << std::uppercase << std::setfill('0') << std::setw(4) << std::hex - << linecount - << key << "=" << value << "\n"; - // send the WCONFIG command here - if (error == NO_ERROR) error = this->archon_cmd(sscmd.str()); - } // end if ( !key.empty() && !value.empty() ) - linecount++; - } // end while ( getline(filestream, line) ) - - // re-enable background polling - // - if (error == NO_ERROR) error = this->archon_cmd(POLLON); - - filestream.close(); - if (error == NO_ERROR) { - logwrite(function, "loaded Archon config file OK"); - this->firmwareloaded = true; - - // add to systemkeys keyword database - // - std::stringstream keystr; - keystr << "FIRMWARE=" << acffile << "// controller firmware"; - this->systemkeys.addkey(keystr.str()); - } - - // If there was an Archon error then read the Archon error log - // - if (error != NO_ERROR) error = this->fetchlog(); - - this->modeselected = false; // require that a mode be selected after loading new firmware - - // Even if exptime, longexposure were previously set, a new ACF could have different - // default values than the server has, so reset these to "undefined" in order to - // force the server to ask for them. - // - this->camera_info.exposure_time = -1; - this->camera_info.exposure_factor = -1; - this->camera_info.exposure_unit.clear(); - - return error; - } - - /**************** Archon::Interface::load_acf *******************************/ - - - /**************** Archon::Interface::set_camera_mode ************************/ - /** - * @fn set_camera_mode - * @brief - * @param none - * @return - * - */ - long Interface::set_camera_mode(std::string mode) { - std::string function = "Archon::Interface::set_camera_mode"; - std::stringstream message; - bool configchanged = false; - bool paramchanged = false; - long error; - - // No point in trying anything if no firmware has been loaded yet - // - if (!this->firmwareloaded) { - this->camera.log_error(function, "no firmware loaded"); - return ERROR; - } - - std::transform(mode.begin(), mode.end(), mode.begin(), ::toupper); // make uppercase - - // The requested mode must have been read in the current ACF file - // and put into the modemap... - // - if (this->modemap.find(mode) == this->modemap.end()) { - message.str(""); - message << "undefined mode " << mode << " in ACF file " << this->camera.firmware[0]; - this->camera.log_error(function, message.str()); - return ERROR; - } - - // load specific mode settings from .acf and apply to Archon - // - if (load_mode_settings(mode) != NO_ERROR) { - message.str(""); - message << "ERROR: failed to load mode settings for mode: " << mode; - logwrite(function, message.str()); - return ERROR; - } - - // set internal variables based on new .acf values loaded - // - error = NO_ERROR; - if (error == NO_ERROR) error = get_configmap_value("FRAMEMODE", this->modemap[mode].geometry.framemode); - if (error == NO_ERROR) error = get_configmap_value("LINECOUNT", this->modemap[mode].geometry.linecount); - if (error == NO_ERROR) error = get_configmap_value("PIXELCOUNT", this->modemap[mode].geometry.pixelcount); - if (error == NO_ERROR) error = get_configmap_value("RAWENABLE", this->modemap[mode].rawenable); - if (error == NO_ERROR) error = get_configmap_value("RAWSEL", this->rawinfo.adchan); - if (error == NO_ERROR) error = get_configmap_value("RAWSAMPLES", this->rawinfo.rawsamples); - if (error == NO_ERROR) error = get_configmap_value("RAWENDLINE", this->rawinfo.rawlines); - -#ifdef LOGLEVEL_DEBUG - message.str(""); - message << "[DEBUG] mode=" << mode << " RAWENABLE=" << this->modemap[mode].rawenable - << " RAWSAMPLES=" << this->rawinfo.rawsamples << " RAWLINES=" << this->rawinfo.rawlines; - logwrite(function, message.str()); -#endif - - // get out if any errors at this point - // - if (error != NO_ERROR) { - logwrite(function, "ERROR: one or more internal variables missing from configmap"); - return error; - } + // get out if any errors at this point + // + if ( error != NO_ERROR ) { logwrite( function, "ERROR: one or more internal variables missing from configmap" ); return error; } - int num_detect = this->modemap[mode].geometry.num_detect; // for convenience + int num_detect = this->modemap[mode].geometry.num_detect; // for convenience - // set current number of Archon buffers and resize local memory - // get out if an error - // - int bigbuf = -1; - if (error == NO_ERROR) error = get_configmap_value("BIGBUF", bigbuf); - // get value of BIGBUF from loaded acf file - this->camera_info.activebufs = (bigbuf == 1) ? 2 : 3; // set number of active buffers based on BIGBUF - if (error != NO_ERROR) { - logwrite(function, "ERROR: unable to read BIGBUF from ACF"); - return error; - } + // set current number of Archon buffers and resize local memory + // get out if an error + // + int bigbuf=-1; + if (error==NO_ERROR) error = get_configmap_value("BIGBUF", bigbuf); // get value of BIGBUF from loaded acf file + this->camera_info.activebufs = (bigbuf==1) ? 2 : 3; // set number of active buffers based on BIGBUF + if ( error != NO_ERROR ) { logwrite( function, "ERROR: unable to read BIGBUF from ACF" ); return error; } - // There is one special reserved mode name, "RAW" - // - if (mode == "RAW") { - this->camera_info.detector_pixels[0] = this->rawinfo.rawsamples; - this->camera_info.detector_pixels[1] = this->rawinfo.rawlines; - this->camera_info.detector_pixels[1]++; - // frame_type will determine the bits per pixel and where the detector_axes come from - this->camera_info.frame_type = Camera::FRAME_RAW; - this->camera_info.region_of_interest[0] = 1; - this->camera_info.region_of_interest[1] = this->camera_info.detector_pixels[0]; - this->camera_info.region_of_interest[2] = 1; - this->camera_info.region_of_interest[3] = this->camera_info.detector_pixels[1]; - // Binning factor (no binning) - this->camera_info.binning[0] = 1; - this->camera_info.binning[1] = 1; -#ifdef LOGLEVEL_DEBUG + // There is one special reserved mode name, "RAW" + // + if (mode=="RAW") { + this->camera_info.detector_pixels[0] = this->rawinfo.rawsamples; + this->camera_info.detector_pixels[1] = this->rawinfo.rawlines; + this->camera_info.detector_pixels[1]++; + // frame_type will determine the bits per pixel and where the detector_axes come from + this->camera_info.frame_type = Camera::FRAME_RAW; + this->camera_info.region_of_interest[0] = 1; + this->camera_info.region_of_interest[1] = this->camera_info.detector_pixels[0]; + this->camera_info.region_of_interest[2] = 1; + this->camera_info.region_of_interest[3] = this->camera_info.detector_pixels[1]; + // Binning factor (no binning) + this->camera_info.binning[0] = 1; + this->camera_info.binning[1] = 1; + #ifdef LOGLEVEL_DEBUG message.str(""); message << "[DEBUG] this->camera_info.detector_pixels[0] (RAWSAMPLES) = " << this->camera_info.detector_pixels[0]; logwrite(function, message.str()); message.str(""); message << "[DEBUG] this->camera_info.detector_pixels[1] (RAWENDLINE) = " << this->camera_info.detector_pixels[1]; logwrite(function, message.str()); -#endif - } else { - // Any other mode falls under here - if (error == NO_ERROR) error = get_configmap_value("PIXELCOUNT", this->camera_info.detector_pixels[0]); - if (error == NO_ERROR) error = get_configmap_value("LINECOUNT", this->camera_info.detector_pixels[1]); -#ifdef LOGLEVEL_DEBUG + #endif + + } else { + // Any other mode falls under here + if (error==NO_ERROR) error = get_configmap_value("PIXELCOUNT", this->camera_info.detector_pixels[0]); + if (error==NO_ERROR) error = get_configmap_value("LINECOUNT", this->camera_info.detector_pixels[1]); + #ifdef LOGLEVEL_DEBUG message.str(""); message << "[DEBUG] mode=" << mode; logwrite(function, message.str()); message.str(""); message << "[DEBUG] this->camera_info.detector_pixels[0] (PIXELCOUNT) = " << this->camera_info.detector_pixels[0] << " amps[0] = " << this->modemap[mode].geometry.amps[0]; @@ -1831,145 +1750,125 @@ namespace Archon { message.str(""); message << "[DEBUG] this->camera_info.detector_pixels[1] (LINECOUNT) = " << this->camera_info.detector_pixels[1] << " amps[1] = " << this->modemap[mode].geometry.amps[1]; logwrite(function, message.str()); -#endif - this->camera_info.detector_pixels[0] *= this->modemap[mode].geometry.amps[0]; - this->camera_info.detector_pixels[1] *= this->modemap[mode].geometry.amps[1]; - this->camera_info.frame_type = Camera::FRAME_IMAGE; - // ROI is the full detector - this->camera_info.region_of_interest[0] = 1; - this->camera_info.region_of_interest[1] = this->camera_info.detector_pixels[0]; - this->camera_info.region_of_interest[2] = 1; - this->camera_info.region_of_interest[3] = this->camera_info.detector_pixels[1]; - // Binning factor (no binning) - this->camera_info.binning[0] = 1; - this->camera_info.binning[1] = 1; -#ifdef LOGLEVEL_DEBUG + #endif + this->camera_info.detector_pixels[0] *= this->modemap[mode].geometry.amps[0]; + this->camera_info.detector_pixels[1] *= this->modemap[mode].geometry.amps[1]; + this->camera_info.frame_type = Camera::FRAME_IMAGE; + // ROI is the full detector + this->camera_info.region_of_interest[0] = 1; + this->camera_info.region_of_interest[1] = this->camera_info.detector_pixels[0]; + this->camera_info.region_of_interest[2] = 1; + this->camera_info.region_of_interest[3] = this->camera_info.detector_pixels[1]; + // Binning factor (no binning) + this->camera_info.binning[0] = 1; + this->camera_info.binning[1] = 1; + #ifdef LOGLEVEL_DEBUG message.str(""); message << "[DEBUG] this->camera_info.detector_pixels[0] (PIXELCOUNT) = " << this->camera_info.detector_pixels[0]; logwrite(function, message.str()); message.str(""); message << "[DEBUG] this->camera_info.detector_pixels[1] (LINECOUNT) = " << this->camera_info.detector_pixels[1]; logwrite(function, message.str()); -#endif - if (error != NO_ERROR) { - logwrite(function, "ERROR: unable to get PIXELCOUNT,LINECOUNT from ACF"); - return error; - } - } + #endif + if ( error != NO_ERROR ) { logwrite( function, "ERROR: unable to get PIXELCOUNT,LINECOUNT from ACF" ); return error; } + } - // set bitpix based on SAMPLEMODE - // - int samplemode = -1; - if (error == NO_ERROR) error = get_configmap_value("SAMPLEMODE", samplemode); - // SAMPLEMODE=0 for 16bpp, =1 for 32bpp - if (error != NO_ERROR) { - logwrite(function, "ERROR: unable to get SAMPLEMODE from ACF"); - return error; - } - if (samplemode < 0) { - this->camera.log_error(function, "bad or missing SAMPLEMODE from ACF"); - return ERROR; - } - this->camera_info.bitpix = (samplemode == 0) ? 16 : 32; + // set bitpix based on SAMPLEMODE + // + int samplemode=-1; + if (error==NO_ERROR) error = get_configmap_value("SAMPLEMODE", samplemode); // SAMPLEMODE=0 for 16bpp, =1 for 32bpp + if ( error != NO_ERROR ) { logwrite( function, "ERROR: unable to get SAMPLEMODE from ACF" ); return error; } + if (samplemode < 0) { this->camera.log_error( function, "bad or missing SAMPLEMODE from ACF" ); return ERROR; } + this->camera_info.bitpix = (samplemode==0) ? 16 : 32; + + // Load parameters and Apply CDS/Deint configuration if any of them changed + if ((error == NO_ERROR) && paramchanged) error = this->archon_cmd(LOADPARAMS); // TODO I think paramchanged is never set! + if ((error == NO_ERROR) && configchanged) error = this->archon_cmd(APPLYCDS); // TODO I think configchanged is never set! + + // Get the current frame buffer status + if (error == NO_ERROR) error = this->get_frame_status(); + if (error != NO_ERROR) { + logwrite( function, "ERROR: unable to get frame status" ); + return error; + } - // Load parameters and Apply CDS/Deint configuration if any of them changed - if ((error == NO_ERROR) && paramchanged) error = this->archon_cmd(LOADPARAMS); - // TODO I think paramchanged is never set! - if ((error == NO_ERROR) && configchanged) error = this->archon_cmd(APPLYCDS); - // TODO I think configchanged is never set! + // Set axes, image dimensions, calculate image_memory, etc. + // Raw will always be 16 bpp (USHORT). + // Image can be 16 or 32 bpp depending on SAMPLEMODE setting in ACF. + // Call set_axes(datatype) with the FITS data type needed, which will set the info.datatype variable. + // + error = this->camera_info.set_axes(); // 16 bit raw is unsigned short int +/********* + if (this->camera_info.frame_type == Camera::FRAME_RAW) { + error = this->camera_info.set_axes(USHORT_IMG); // 16 bit raw is unsigned short int + } + if (this->camera_info.frame_type == Camera::FRAME_IMAGE) { + if (this->camera_info.bitpix == 16) error = this->camera_info.set_axes(SHORT_IMG); // 16 bit image is short int + else + if (this->camera_info.bitpix == 32) error = this->camera_info.set_axes(FLOAT_IMG); // 32 bit image is float + else { + message.str(""); message << "bad bitpix " << this->camera_info.bitpix << ": expected 16 | 32"; + this->camera.log_error( function, message.str() ); + return (ERROR); + } + } +*********/ + if (error != NO_ERROR) { + this->camera.log_error( function, "setting axes" ); + return (ERROR); + } - // Get the current frame buffer status - if (error == NO_ERROR) error = this->get_frame_status(); - if (error != NO_ERROR) { - logwrite(function, "ERROR: unable to get frame status"); - return error; - } + // allocate image_data in blocks because the controller outputs data in units of blocks + // + this->image_data_bytes = (uint32_t) floor( ((this->camera_info.image_memory * num_detect) + BLOCK_LEN - 1 ) / BLOCK_LEN ) * BLOCK_LEN; - // Set axes, image dimensions, calculate image_memory, etc. - // Raw will always be 16 bpp (USHORT). - // Image can be 16 or 32 bpp depending on SAMPLEMODE setting in ACF. - // Call set_axes(datatype) with the FITS data type needed, which will set the info.datatype variable. - // - error = this->camera_info.set_axes(); // 16 bit raw is unsigned short int - /********* - if (this->camera_info.frame_type == Camera::FRAME_RAW) { - error = this->camera_info.set_axes(USHORT_IMG); // 16 bit raw is unsigned short int - } - if (this->camera_info.frame_type == Camera::FRAME_IMAGE) { - if (this->camera_info.bitpix == 16) error = this->camera_info.set_axes(SHORT_IMG); // 16 bit image is short int - else - if (this->camera_info.bitpix == 32) error = this->camera_info.set_axes(FLOAT_IMG); // 32 bit image is float - else { - message.str(""); message << "bad bitpix " << this->camera_info.bitpix << ": expected 16 | 32"; - this->camera.log_error( function, message.str() ); - return (ERROR); - } - } - *********/ - if (error != NO_ERROR) { - this->camera.log_error(function, "setting axes"); - return (ERROR); - } + if (this->image_data_bytes == 0) { + this->camera.log_error( function, "image data size is zero! check NUM_DETECT, HORI_AMPS, VERT_AMPS in .acf file" ); + error = ERROR; + } - // allocate image_data in blocks because the controller outputs data in units of blocks - // - this->image_data_bytes = (uint32_t) floor( - ((this->camera_info.image_memory * num_detect) + BLOCK_LEN - 1) / BLOCK_LEN) * - BLOCK_LEN; + this->camera_info.current_observing_mode = mode; // identify the newly selected mode in the camera_info class object + this->modeselected = true; // a valid mode has been selected - if (this->image_data_bytes == 0) { - this->camera.log_error( - function, "image data size is zero! check NUM_DETECT, HORI_AMPS, VERT_AMPS in .acf file"); - error = ERROR; - } + message.str(""); message << "new mode: " << mode << " will use " << this->camera_info.bitpix << " bits per pixel"; + logwrite(function, message.str()); - this->camera_info.current_observing_mode = mode; - // identify the newly selected mode in the camera_info class object - this->modeselected = true; // a valid mode has been selected + // Calculate amplifier sections + // + int rows = this->modemap[mode].geometry.linecount; // rows per section + int cols = this->modemap[mode].geometry.pixelcount; // cols per section - message.str(""); - message << "new mode: " << mode << " will use " << this->camera_info.bitpix << " bits per pixel"; - logwrite(function, message.str()); + int hamps = this->modemap[mode].geometry.amps[0]; // horizontal amplifiers + int vamps = this->modemap[mode].geometry.amps[1]; // vertical amplifiers - // Calculate amplifier sections - // - int rows = this->modemap[mode].geometry.linecount; // rows per section - int cols = this->modemap[mode].geometry.pixelcount; // cols per section + int x0=-1, x1, y0, y1; // for indexing + std::vector coords; // vector of coordinates, convention is x0,x1,y0,y1 + int framemode = this->modemap[mode].geometry.framemode; - int hamps = this->modemap[mode].geometry.amps[0]; // horizontal amplifiers - int vamps = this->modemap[mode].geometry.amps[1]; // vertical amplifiers + this->camera_info.amp_section.clear(); // vector of coords vectors, one set of coords per extension - int x0 = -1, x1, y0, y1; // for indexing - std::vector coords; // vector of coordinates, convention is x0,x1,y0,y1 - int framemode = this->modemap[mode].geometry.framemode; + for ( int y=0; ycamera_info.amp_section.clear(); // vector of coords vectors, one set of coords per extension + } else { + x0++; x1=x0+1; + y0 = 0; y1=1; + } + coords.clear(); + coords.push_back( (x0*cols + 1) ); // x0 is xstart + coords.push_back( (x1)*cols ); // x1 is xstop, xrange = x0:x1 + coords.push_back( (y0*rows + 1) ); // y0 is ystart + coords.push_back( (y1)*rows ); // y1 is ystop, yrange = y0:y1 - for (int y = 0; y < vamps; y++) { - for (int x = 0; x < hamps; x++) { - if (framemode == 2) { - x0 = x; - x1 = x + 1; - y0 = y; - y1 = y + 1; - } else { - x0++; - x1 = x0 + 1; - y0 = 0; - y1 = 1; - } - coords.clear(); - coords.push_back((x0 * cols + 1)); // x0 is xstart - coords.push_back((x1) * cols); // x1 is xstop, xrange = x0:x1 - coords.push_back((y0 * rows + 1)); // y0 is ystart - coords.push_back((y1) * rows); // y1 is ystop, yrange = y0:y1 + this->camera_info.amp_section.push_back( coords ); // (x0,x1,y0,y1) for this extension - this->camera_info.amp_section.push_back(coords); // (x0,x1,y0,y1) for this extension - } - } - message.str(""); - message << "identified " << this->camera_info.amp_section.size() << " amplifier sections"; - logwrite(function, message.str()); + } + } + message.str(""); message << "identified " << this->camera_info.amp_section.size() << " amplifier sections"; + logwrite( function, message.str() ); -#ifdef LOGLEVEL_DEBUG + #ifdef LOGLEVEL_DEBUG int ext=0; for ( const auto &sec : this->camera_info.amp_section ) { message.str(""); message << "[DEBUG] extension " << ext++ << ":"; @@ -1978,1006 +1877,749 @@ namespace Archon { } logwrite( function, message.str() ); } -#endif - - return error; - } - - /**************** Archon::Interface::set_camera_mode ************************/ - - - /**************** Archon::Interface::load_mode_settings *********************/ - /** - * @fn load_mode_settings - * @brief load into Archon settings specified in mode section of .acf file - * @param camera mode - * @return none - * - * At the end of the .acf file are optional sections for each camera - * observing mode. These sections can contain any number of configuration - * lines and parameters to set for the given mode. Those lines are read - * when the configuration file is loaded. This function writes them to - * the Archon controller. - */ - long Interface::load_mode_settings(std::string mode) { - std::string function = "Archon::Interface::load_mode_settings"; - std::stringstream message; - - long error = NO_ERROR; - cfg_map_t::iterator cfg_it; - param_map_t::iterator param_it; - bool paramchanged = false; - bool configchanged = false; - - std::stringstream errstr; - - /** - * iterate through configmap, writing each config key in the map - */ - for (cfg_it = this->modemap[mode].configmap.begin(); - cfg_it != this->modemap[mode].configmap.end(); - cfg_it++) { - error = this->write_config_key(cfg_it->first.c_str(), cfg_it->second.value.c_str(), configchanged); - if (error != NO_ERROR) { - errstr << "ERROR: writing config key:" << cfg_it->first << " value:" << cfg_it->second.value << - " for mode " << mode; - break; - } - } - - /** - * if no errors from writing config keys, then - * iterate through the parammap, writing each parameter in the map - */ - if (error == NO_ERROR) { - for (param_it = this->modemap[mode].parammap.begin(); - param_it != this->modemap[mode].parammap.end(); - param_it++) { - error = this->write_parameter(param_it->first.c_str(), param_it->second.value.c_str(), paramchanged); - message.str(""); - message << "paramchanged=" << (paramchanged ? "true" : "false"); - logwrite(function, message.str()); - if (error != NO_ERROR) { - errstr << "ERROR: writing parameter key:" << param_it->first << " value:" << param_it->second.value - << " for mode " << mode; - break; - } - } - } - - /** - * apply the new settings to the system here, only if something changed - */ - if ((error == NO_ERROR) && paramchanged) error = this->archon_cmd(LOADPARAMS); - if ((error == NO_ERROR) && configchanged) error = this->archon_cmd(APPLYCDS); - - if (error == NO_ERROR) { - message.str(""); - message << "loaded mode: " << mode; - logwrite(function, message.str()); - } else { - logwrite(function, errstr.str()); - return error; - } - - // The new mode could contain a ShutterEnable param, - // and if it does then the server needs to know about that. - // - if (!this->shutenableparam.empty()) { - // first read the parameter - // - std::string lshutten; - if (read_parameter(this->shutenableparam, lshutten) != NO_ERROR) { - message.str(""); - message << "ERROR: reading \"" << this->shutenableparam << "\" parameter from Archon"; - logwrite(function, message.str()); - return ERROR; - } - - // parse the parameter value - // and convert it to a string for the shutter command - // - std::string shuttenstr; - if (lshutten == "1") shuttenstr = "enable"; - else if (lshutten == "0") shuttenstr = "disable"; - else { - message.str(""); - message << "ERROR: unrecognized shutter enable parameter value " << lshutten << ": expected {0,1}"; - logwrite(function, message.str()); - return ERROR; - } - - // Tell the server - // - std::string dontcare; - if (this->shutter(shuttenstr, dontcare) != NO_ERROR) { - logwrite(function, "ERROR: setting shutter enable parameter"); - return ERROR; - } - } - - /** - * read back some TAPLINE information - */ - if (error == NO_ERROR) error = get_configmap_value("TAPLINES", this->taplines); // total number of taps - - std::vector tokens; - std::stringstream tap; - std::string adchan; - - // Remove all GAIN* and OFFSET* keywords from the systemkeys database - // because the new mode could have different channels (so simply - // over-writing existing keys might not be sufficient). - // - // The new GAIN* and OFFSET* system keys will be added in the next loop. - // - this->systemkeys.EraseKeys("GAIN"); - this->systemkeys.EraseKeys("OFFSET"); - - // Loop through every tap to get the offset for each - // - for (int tapn = 0; tapn < this->taplines; tapn++) { - tap.str(""); - tap << "TAPLINE" << tapn; // used to find the tapline in the configmap - - // The value of TAPLINEn = ADxx,gain,offset -- - // tokenize by comma to separate out each parameter... - // - Tokenize(this->configmap[tap.str().c_str()].value, tokens, ","); - - // If all three tokens present (A?xx,gain,offset) then parse it, - // otherwise it's an unused tap, and we can skip it. - // - if (tokens.size() == 3) { - // defined tap has three tokens - adchan = tokens[0]; // AD channel is the first (0th) token - char chars[] = "ADMLR"; // characters to remove in order to get just the AD channel number - - // Before removing these characters, set the max allowed AD number based on the tapline syntax. - // "ADxx,gain,offset" is for ADC module - // "AMxx,gain,offset" is for ADM module - // - int admax = 0; - if (adchan.find("AD") != std::string::npos) admax = MAXADCCHANS; - else if (adchan.find("AM") != std::string::npos) admax = MAXADMCHANS; - else { - message.str(""); - message << "bad tapline syntax. Expected ADn or AMn but got " << adchan; - this->camera.log_error(function, message.str()); - return ERROR; - } - - // remove AD, AM, L, R from the adchan string, to get just the AD channel number - // - for (unsigned int j = 0; j < strlen(chars); j++) { - adchan.erase(std::remove(adchan.begin(), adchan.end(), chars[j]), adchan.end()); - } - - // AD# in TAPLINE is 1-based (numbered 1,2,3,...) - // but convert here to 0-based (numbered 0,1,2,...) and check value before using - // - int adnum; - try { - adnum = std::stoi(adchan) - 1; - } catch (std::invalid_argument &) { - message.str(""); - message << "unable to convert AD number \'" << adchan << "\' to integer"; - this->camera.log_error(function, message.str()); - return ERROR; - } catch (std::out_of_range &) { - this->camera.log_error(function, "AD number out of integer range"); - return ERROR; - } - - if ((adnum < 0) || (adnum > admax)) { - message.str(""); - message << "ADC channel " << adnum << " outside range {0:" << admax << "}"; - this->camera.log_error(function, message.str()); - return ERROR; - } - // Now that adnum is OK, convert next two tokens to gain, offset - // - int gain_try = 0, offset_try = 0; - try { - gain_try = std::stoi(tokens[1]); // gain as function of AD channel - offset_try = std::stoi(tokens[2]); // offset as function of AD channel - } catch (std::invalid_argument &) { - message.str(""); - message << "unable to convert GAIN \"" << tokens[1] << "\" and/or OFFSET \"" << tokens[2] << - "\" to integer"; - this->camera.log_error(function, message.str()); - return ERROR; - } catch (std::out_of_range &) { - message.str(""); - message << "GAIN " << tokens[1] << ", OFFSET " << tokens[2] << " outside integer range"; - this->camera.log_error(function, message.str()); - return ERROR; - } - // Now assign the gain,offsets to their appropriate position in the vectors - // - try { - this->gain.at(adnum) = gain_try; // gain as function of AD channel - this->offset.at(adnum) = offset_try; // offset as function of AD channel - - // Add the gain/offset as system header keywords. - // - std::stringstream keystr; - keystr.str(""); - keystr << "GAIN" << std::setfill('0') << std::setw(2) << adnum << "=" << this->gain.at(adnum) << - "// gain for AD chan " << adnum; - this->systemkeys.addkey(keystr.str()); - keystr.str(""); - keystr << "OFFSET" << std::setfill('0') << std::setw(2) << adnum << "=" << this->offset.at(adnum) << - "// offset for AD chan " << adnum; - this->systemkeys.addkey(keystr.str()); - } catch (std::out_of_range &) { - message.str(""); - message << "AD# " << adnum << " outside range {0:" << (this->gain.size() & this->offset.size()) << - "}"; - this->camera.log_error(function, message.str()); - if (this->gain.empty() || this->offset.empty()) { - this->camera.log_error( - function, "gain/offset vectors are empty: no ADC or ADM board installed?"); - } - return ERROR; - } -#ifdef LOGLEVEL_DEBUG - message.str(""); message << "[DEBUG] tap #" << tapn << " ad#" << adnum; - logwrite( function, message.str() ); -#endif - } // end if (tokens.size() == 3) - } // end for (int tapn=0; tapntaplines; tapn++) - - return error; - } + #endif + + return error; + } + /**************** Archon::Interface::set_camera_mode ************************/ + + + /**************** Archon::Interface::load_mode_settings *********************/ + /** + * @fn load_mode_settings + * @brief load into Archon settings specified in mode section of .acf file + * @param camera mode + * @return none + * + * At the end of the .acf file are optional sections for each camera + * observing mode. These sections can contain any number of configuration + * lines and parameters to set for the given mode. Those lines are read + * when the configuration file is loaded. This function writes them to + * the Archon controller. + */ + long Interface::load_mode_settings(std::string mode) { + std::string function = "Archon::Interface::load_mode_settings"; + std::stringstream message; + + long error=NO_ERROR; + cfg_map_t::iterator cfg_it; + param_map_t::iterator param_it; + bool paramchanged = false; + bool configchanged = false; + + std::stringstream errstr; - /**************** Archon::Interface::load_mode_settings *********************/ - - - /**************** Archon::Interface::get_frame_status ***********************/ /** - * @fn get_frame_status - * @brief get the current frame buffer status - * @param none - * @return - * - * Sends the "FRAME" command to Archon, reads back the reply, then parses the - * reply and stores parameters into the frame structure (of type frame_data_t). - * + * iterate through configmap, writing each config key in the map */ - long Interface::get_frame_status() { - std::string function = "Archon::Interface::get_frame_status"; - std::string reply; - std::stringstream message; - int newestframe, newestbuf; - long error = NO_ERROR; - - // send FRAME command to get frame buffer status - // - if ((error = this->archon_cmd(FRAME, reply))) { - if (error == ERROR) logwrite(function, "ERROR: sending FRAME command"); // don't log here if BUSY - return error; - } - - // First Tokenize breaks the single, continuous reply string into vector of individual strings, - // from "TIMER=xxxx RBUF=xxxx " to: - // tokens[0] : TIMER=xxxx - // tokens[1] : RBUF=xxxx - // tokens[2] : etc. - std::vector tokens; - Tokenize(reply, tokens, " "); - - // loop over all tokens in reply - for (const auto &token: tokens) { - // Second Tokenize separates the parameter from the value - // subtokens[0] = keyword - // subtokens[1] = value - std::vector subtokens; - subtokens.clear(); - Tokenize(token, subtokens, "="); - - // Each entry in the FRAME message must have two tokens, one for each side of the "=" equal sign - // (in other words there must be two subtokens per token) - if (subtokens.size() != 2) { - message.str(""); - message << "expected 2 but received invalid number of tokens (" << subtokens.size() << - ") in FRAME message:"; - for (const auto &subtoken: subtokens) message << " " << subtoken; - this->camera.log_error(function, message.str()); - return ERROR; // We could continue; but if one is bad then we could miss seeing a larger problem - } - - int bufnum = 0; - int value = 0; - uint64_t lvalue = 0; - - // Parse subtokens[1] (value) based on subtoken[0] (keyword) - - // timer is a string - if (subtokens[0] == "TIMER") { - this->frame.timer = subtokens[1]; - } else { - // everything else is going to be a number - // use "try...catch" to catch exceptions converting strings to numbers - try { - // for all "BUFnSOMETHING=VALUE" we want the bufnum "n" - if (subtokens[0].compare(0, 3, "BUF") == 0) { - // extract the "n" here which is 1-based (1,2,3) - bufnum = std::stoi(subtokens[0].substr(3, 1)); - } - - // for "BUFnBASE=xxx" the value is uint64 - if (subtokens[0].substr(4) == "BASE") { - lvalue = std::stol(subtokens[1]); // this value will get assigned to the corresponding parameter - - // for any "xxxTIMESTAMPxxx" the value is uint64 - } else if (subtokens[0].find("TIMESTAMP") != std::string::npos) { - lvalue = std::stol(subtokens[1]); // this value will get assigned to the corresponding parameter - - // everything else is an int - } else { - value = std::stoi(subtokens[1]); // this value will get assigned to the corresponding parameter - } - } catch (std::invalid_argument &) { - message.str(""); - message << "unable to convert buffer: " << subtokens[0] << " or value: " << subtokens[1] << - " from FRAME message to integer. Expected BUFnSOMETHING=nnnn"; - this->camera.log_error(function, message.str()); - return ERROR; - } catch (std::out_of_range &) { - message.str(""); - message << "buffer: " << subtokens[0] << " or value: " << subtokens[1] << - " from FRAME message outside integer range. Expected BUFnSOMETHING=nnnn"; - this->camera.log_error(function, message.str()); - return ERROR; - } - } // end else everything else is a number - - // subtokens value has been parsed based on keyword - - // get currently locked buffers - if (subtokens[0] == "RBUF") this->frame.rbuf = value; // locked for reading - if (subtokens[0] == "WBUF") this->frame.wbuf = value; // locked for writing - - // The next group are BUFnSOMETHING=VALUE - // Extract the "n" which must be a number from 1 to Archon::nbufs - // After getting the buffer number we assign the corresponding value. - // - if (subtokens[0].compare(0, 3, "BUF") == 0) { - bufnum = std::stoi(subtokens[0].substr(3, 1)); - // verify buffer number (1,2, or 3) - if (bufnum < 1 || bufnum > Archon::nbufs) { - message.str(""); - message << "buffer number " << bufnum << " from FRAME message outside range {1:" << Archon::nbufs << - "}"; - this->camera.log_error(function, message.str()); - return ERROR; - } - bufnum--; // subtract 1 because it is 1-based in the message but need 0-based for the indexing - // Assign value to appropriate variable in frame structure - if (subtokens[0].substr(4) == "SAMPLE") this->frame.bufsample[bufnum] = value; - if (subtokens[0].substr(4) == "COMPLETE") this->frame.bufcomplete[bufnum] = value; - if (subtokens[0].substr(4) == "MODE") this->frame.bufmode[bufnum] = value; - if (subtokens[0].substr(4) == "BASE") this->frame.bufbase[bufnum] = lvalue; - if (subtokens[0].substr(4) == "FRAME") this->frame.bufframen[bufnum] = value; - if (subtokens[0].substr(4) == "WIDTH") this->frame.bufwidth[bufnum] = value; - if (subtokens[0].substr(4) == "HEIGHT") this->frame.bufheight[bufnum] = value; - if (subtokens[0].substr(4) == "PIXELS") this->frame.bufpixels[bufnum] = value; - if (subtokens[0].substr(4) == "LINES") this->frame.buflines[bufnum] = value; - if (subtokens[0].substr(4) == "RAWBLOCKS") this->frame.bufrawblocks[bufnum] = value; - if (subtokens[0].substr(4) == "RAWLINES") this->frame.bufrawlines[bufnum] = value; - if (subtokens[0].substr(4) == "RAWOFFSET") this->frame.bufrawoffset[bufnum] = value; - if (subtokens[0].substr(4) == "TIMESTAMP") this->frame.buftimestamp[bufnum] = lvalue; - if (subtokens[0].substr(4) == "RETIMESTAMP") this->frame.bufretimestamp[bufnum] = lvalue; - if (subtokens[0].substr(4) == "FETIMESTAMP") this->frame.buffetimestamp[bufnum] = lvalue; - } // end if token is BUFnSOMETHiNG - } // end loop over all returned tokens from FRAME reply - - // FRAME reply has now been parsed into frame structure variables - // now update frame.frame, frame.index, and frame.next_index - - // Current frame.index - newestbuf = this->frame.index; - - // Is frame.index a legal index? - if (this->frame.index < (int) this->frame.bufframen.size()) { - // get the current frame number - newestframe = this->frame.bufframen[this->frame.index]; - } else { - // frame.index value is illegal - message.str(""); - message << "newest buf " << this->frame.index << " from FRAME message exceeds number of buffers " << this-> - frame.bufframen.size(); - this->camera.log_error(function, message.str()); - return ERROR; - } - - int num_zero = 0; // count zero buffers - - // loop through the buffers - for (int bc = 0; bc < Archon::nbufs; bc++) { - // count number of zero-valued buffers - if (this->frame.bufframen[bc] == 0) num_zero++; - - // Is the latest frame greater than the current one and is it complete? - if ((this->frame.bufframen[bc] > newestframe) && this->frame.bufcomplete[bc]) { - // if so, update frame and buffer numbers - newestframe = this->frame.bufframen[bc]; - newestbuf = bc; - } - } - - // In start-up case, all frame buffers are zero - if (num_zero == Archon::nbufs) { - // initialize frame number and buffer index both to zero - newestframe = 0; - newestbuf = 0; - } - - // save the newest frame number and index of newest buffer. - this->frame.frame = newestframe; - this->frame.index = newestbuf; - - // startup condition for next_frame: - // frame number is 1 and corresponding buffer is not complete - if ((this->frame.bufframen[this->frame.index]) == 1 && this->frame.bufcomplete[this->frame.index] == 0) { - this->frame.next_index = 0; - } else { - // Index of next frame is this->frame.index+1 - this->frame.next_index = this->frame.index + 1; - - // frame.next_index wraps to 0 when it reaches the maximum number of active buffers. - if (this->frame.next_index >= this->camera_info.activebufs) { - this->frame.next_index = 0; - } - } - - return error; + for (cfg_it = this->modemap[mode].configmap.begin(); + cfg_it != this->modemap[mode].configmap.end(); + cfg_it++) { + error = this->write_config_key( cfg_it->first.c_str(), cfg_it->second.value.c_str(), configchanged ); + if (error != NO_ERROR) { + errstr << "ERROR: writing config key:" << cfg_it->first << " value:" << cfg_it->second.value << " for mode " << mode; + break; + } } - /**************** Archon::Interface::get_frame_status ***********************/ - - - /**************** Archon::Interface::print_frame_status *********************/ /** - * @fn print_frame_status - * @brief print the Archon frame buffer status - * @param none - * @return none - * - * Writes the Archon frame buffer status to the log file, - * I.E. information in this->frame structure, obtained from - * the "FRAME" command. See Archon::Interface::get_frame_status() - * + * if no errors from writing config keys, then + * iterate through the parammap, writing each parameter in the map */ - void Interface::print_frame_status() { - std::string function = "Archon::Interface::print_frame_status"; - std::stringstream message; - int bufn; - char statestr[Archon::nbufs][4]; - - // write as log message - // - message.str(""); - message << " buf base rawoff frame ready lines rawlines rblks width height state"; + if (error == NO_ERROR) { + for (param_it = this->modemap[mode].parammap.begin(); + param_it != this->modemap[mode].parammap.end(); + param_it++) { + error = this->write_parameter( param_it->first.c_str(), param_it->second.value.c_str(), paramchanged ); + message.str(""); message << "paramchanged=" << (paramchanged?"true":"false"); logwrite(function, message.str()); - message.str(""); - message << " --- ---------- ---------- ----- ----- ----- -------- ----- ----- ------ -----"; - logwrite(function, message.str()); - message.str(""); - for (bufn = 0; bufn < Archon::nbufs; bufn++) { - memset(statestr[bufn], '\0', 4); - if ((this->frame.rbuf - 1) == bufn) strcat(statestr[bufn], "R"); - if ((this->frame.wbuf - 1) == bufn) strcat(statestr[bufn], "W"); - if (this->frame.bufcomplete[bufn]) strcat(statestr[bufn], "C"); - } - for (bufn = 0; bufn < Archon::nbufs; bufn++) { - message << std::setw(3) << (bufn == this->frame.index ? "-->" : "") << " "; // buf - message << std::setw(3) << bufn + 1 << " "; - message << "0x" << std::uppercase << std::setfill('0') << std::setw(8) << std::hex - << this->frame.bufbase[bufn] << " "; // base - message << "0x" << std::uppercase << std::setfill('0') << std::setw(8) << std::hex - << this->frame.bufrawoffset[bufn] << " "; // rawoff - message << std::setfill(' ') << std::setw(5) << std::dec << this->frame.bufframen[bufn] << " "; // frame - message << std::setw(5) << this->frame.bufcomplete[bufn] << " "; // ready - message << std::setw(5) << this->frame.buflines[bufn] << " "; // lines - message << std::setw(8) << this->frame.bufrawlines[bufn] << " "; // rawlines - message << std::setw(5) << this->frame.bufrawblocks[bufn] << " "; // rblks - message << std::setw(5) << this->frame.bufwidth[bufn] << " "; // width - message << std::setw(6) << this->frame.bufheight[bufn] << " "; // height - message << std::setw(5) << statestr[bufn] << " "; // state - message << std::setw(5) << this->frame.bufpixels[bufn]; - logwrite(function, message.str()); - message.str(""); + if (error != NO_ERROR) { + errstr << "ERROR: writing parameter key:" << param_it->first << " value:" << param_it->second.value << " for mode " << mode; + break; } + } } - /**************** Archon::Interface::print_frame_status *********************/ - - - /**************** Archon::Interface::lock_buffer ****************************/ /** - * @fn lock_buffer - * @brief lock Archon frame buffer - * @param int frame buffer to lock - * @return - * + * apply the new settings to the system here, only if something changed */ - long Interface::lock_buffer(int buffer) { - std::string function = "Archon::Interface::lock_buffer"; - std::stringstream message; - std::stringstream sscmd; - - sscmd.str(""); - sscmd << "LOCK" << buffer; - if (this->archon_cmd(sscmd.str())) { - message.str(""); - message << "ERROR: sending Archon command to lock frame buffer " << buffer; - logwrite(function, message.str()); - return ERROR; - } - return (NO_ERROR); - } - - /**************** Archon::Interface::lock_buffer ****************************/ + if ( (error == NO_ERROR) && paramchanged ) error = this->archon_cmd(LOADPARAMS); + if ( (error == NO_ERROR) && configchanged ) error = this->archon_cmd(APPLYCDS); + if (error == NO_ERROR) { + message.str(""); message << "loaded mode: " << mode; + logwrite(function, message.str()); - /**************** Archon::Interface::get_timer ******************************/ - /** - * @fn get_timer - * @brief read the 64 bit interal timer from the Archon controller - * @param *timer pointer to type unsigned long int - * @return errno on error, 0 if okay. - * - * Sends the "TIMER" command to Archon, reads back the reply, and stores the - * value as (unsigned long int) in the argument variable pointed to by *timer. - * - * This is an internal 64 bit timer/counter. One tick of the counter is 10 ns. - * - */ - long Interface::get_timer(unsigned long int *timer) { - std::string function = "Archon::Interface::get_timer"; - std::string reply; - std::stringstream message, timer_ss; - std::vector tokens; - long error; - - // send TIMER command to get frame buffer status - // - if ((error = this->archon_cmd(TIMER, reply)) != NO_ERROR) { - return error; - } + } else { + logwrite( function, errstr.str() ); + return error; + } - Tokenize(reply, tokens, "="); // Tokenize the reply + // The new mode could contain a ShutterEnable param, + // and if it does then the server needs to know about that. + // + if ( !this->shutenableparam.empty() ) { - // Reponse should be "TIMER=xxxx\n" so there needs - // to be two tokens - // - if (tokens.size() != 2) { - message.str(""); - message << "unrecognized timer response: " << reply << ". Expected TIMER=xxxx"; - this->camera.log_error(function, message.str()); - return ERROR; - } + // first read the parameter + // + std::string lshutten; + if ( read_parameter( this->shutenableparam, lshutten ) != NO_ERROR ) { + message.str(""); message << "ERROR: reading \"" << this->shutenableparam << "\" parameter from Archon"; + logwrite( function, message.str() ); + return ERROR; + } - // Second token must be a hexidecimal string - // - std::string timer_str = tokens[1]; - try { - timer_str.erase(timer_str.find('\n'), 1); - } catch (...) { - } // remove newline - if (!std::all_of(timer_str.begin(), timer_str.end(), ::isxdigit)) { - message.str(""); - message << "unrecognized timer value: " << timer_str << ". Expected hexadecimal string"; - this->camera.log_error(function, message.str()); - return ERROR; - } + // parse the parameter value + // and convert it to a string for the shutter command + // + std::string shuttenstr; + if ( lshutten == "1" ) shuttenstr = "enable"; + else if ( lshutten == "0" ) shuttenstr = "disable"; + else { + message.str(""); message << "ERROR: unrecognized shutter enable parameter value " << lshutten << ": expected {0,1}"; + logwrite( function, message.str() ); + return ERROR; + } - // convert from hex string to integer and save return value - // - timer_ss << std::hex << tokens[1]; - timer_ss >> *timer; - return NO_ERROR; + // Tell the server + // + std::string dontcare; + if ( this->shutter( shuttenstr, dontcare ) != NO_ERROR ) { logwrite( function, "ERROR: setting shutter enable parameter" ); return ERROR; } } - /**************** Archon::Interface::get_timer ******************************/ - - - /**************** Archon::Interface::fetch **********************************/ /** - * @fn fetch - * @brief fetch Archon frame buffer - * @param int frame buffer to lock - * @return - * + * read back some TAPLINE information */ - long Interface::fetch(uint64_t bufaddr, uint32_t bufblocks) { - std::string function = "Archon::Interface::fetch"; - std::stringstream message; - uint32_t maxblocks = (uint32_t) (1.5E9 / this->camera_info.activebufs / 1024); - uint64_t maxoffset = this->frame.bufbase[this->frame.index]; - uint64_t maxaddr = maxoffset + maxblocks; + if (error==NO_ERROR) error = get_configmap_value("TAPLINES", this->taplines); // total number of taps - if (bufaddr > maxaddr) { - message.str(""); - message << "fetch Archon buffer requested address 0x" << std::hex << bufaddr << " exceeds 0x" << maxaddr; - this->camera.log_error(function, message.str()); - return ERROR; - } - if (bufblocks > maxblocks) { - message.str(""); - message << "fetch Archon buffer requested blocks 0x" << std::hex << bufblocks << " exceeds 0x" << maxblocks; - this->camera.log_error(function, message.str()); - return ERROR; - } - - std::stringstream sscmd; - sscmd << "FETCH" - << std::setfill('0') << std::setw(8) << std::hex - << bufaddr - << std::setfill('0') << std::setw(8) << std::hex - << bufblocks; - std::string scmd = sscmd.str(); - try { - std::transform(scmd.begin(), scmd.end(), scmd.begin(), ::toupper); // make uppercase - } catch (...) { - message.str(""); - message << "converting command: " << sscmd.str() << " to uppercase"; - this->camera.log_error(function, message.str()); - return ERROR; - } + std::vector tokens; + std::stringstream tap; + std::string adchan; - if (this->archon_cmd(scmd) == ERROR) { - logwrite(function, "ERROR: sending FETCH command. Aborting read."); - this->archon_cmd(UNLOCK); // unlock all buffers - return ERROR; - } - - message.str(""); - message << "reading " << (this->camera_info.frame_type == Camera::FRAME_RAW ? "raw" : "image") << " with " << - scmd; - logwrite(function, message.str()); - return NO_ERROR; - } - - /**************** Archon::Interface::fetch **********************************/ + // Remove all GAIN* and OFFSET* keywords from the systemkeys database + // because the new mode could have different channels (so simply + // over-writing existing keys might not be sufficient). + // + // The new GAIN* and OFFSET* system keys will be added in the next loop. + // + this->systemkeys.EraseKeys( "GAIN" ); + this->systemkeys.EraseKeys( "OFFSET" ); + // Loop through every tap to get the offset for each + // + for (int tapn=0; tapntaplines; tapn++) { + tap.str(""); + tap << "TAPLINE" << tapn; // used to find the tapline in the configmap - /**************** Archon::Interface::read_frame *****************************/ - /** - * @fn read_frame - * @brief read latest Archon frame buffer - * @param none - * @return ERROR or NO_ERROR - * - * This is function is overloaded. - * - * This version, with no parameter, is the one that is called by the server. - * The decision is made here if the frame to be read is a RAW or an IMAGE - * frame based on this->camera_info.current_observing_mode, then the - * overloaded version of read_frame(frame_type) is called with the appropriate - * frame type of IMAGE or RAW. - * - * This function WILL call write_frame(...) to write data after reading it. - * - */ - long Interface::read_frame() { - std::string function = "Archon::Interface::read_frame"; - std::stringstream message; - long error = NO_ERROR; + // The value of TAPLINEn = ADxx,gain,offset -- + // tokenize by comma to separate out each parameter... + // + Tokenize(this->configmap[tap.str().c_str()].value, tokens, ","); - if (!this->modeselected) { - this->camera.log_error(function, "no mode selected"); - return ERROR; - } + // If all three tokens present (A?xx,gain,offset) then parse it, + // otherwise it's an unused tap, and we can skip it. + // + if (tokens.size() == 3) { // defined tap has three tokens + adchan = tokens[0]; // AD channel is the first (0th) token + char chars[] = "ADMLR"; // characters to remove in order to get just the AD channel number - int rawenable = this->modemap[this->camera_info.current_observing_mode].rawenable; + // Before removing these characters, set the max allowed AD number based on the tapline syntax. + // "ADxx,gain,offset" is for ADC module + // "AMxx,gain,offset" is for ADM module + // + int admax = 0; + if ( adchan.find("AD") != std::string::npos ) admax = MAXADCCHANS; + else if ( adchan.find("AM") != std::string::npos ) admax = MAXADMCHANS; + else { + message.str(""); message << "bad tapline syntax. Expected ADn or AMn but got " << adchan; + this->camera.log_error( function, message.str() ); + return ERROR; + } - if (rawenable == -1) { - this->camera.log_error(function, "RAWENABLE is undefined"); - return ERROR; + // remove AD, AM, L, R from the adchan string, to get just the AD channel number + // + for (unsigned int j = 0; j < strlen(chars); j++) { + adchan.erase(std::remove(adchan.begin(), adchan.end(), chars[j]), adchan.end()); } - // RAW-only + // AD# in TAPLINE is 1-based (numbered 1,2,3,...) + // but convert here to 0-based (numbered 0,1,2,...) and check value before using // - if (this->camera_info.current_observing_mode == "RAW") { - // "RAW" is the only reserved mode name + int adnum; + try { + adnum = std::stoi(adchan) - 1; - // the RAWENABLE parameter must be set in the ACF file, in order to read RAW data - // - if (rawenable == 0) { - this->camera.log_error( - function, "observing mode is RAW but RAWENABLE=0 -- change mode or set RAWENABLE?"); - return ERROR; - } else { - error = this->read_frame(Camera::FRAME_RAW); // read raw frame - if (error != NO_ERROR) { - logwrite(function, "ERROR: reading raw frame"); - return error; - } - error = this->write_frame(); // write raw frame - if (error != NO_ERROR) { - logwrite(function, "ERROR: writing raw frame"); - return error; - } - } - } else { - // IMAGE, or IMAGE+RAW - // datacube was already set = true in the expose function - error = this->read_frame(Camera::FRAME_IMAGE); // read image frame - if (error != NO_ERROR) { - logwrite(function, "ERROR: reading image frame"); - return error; - } - error = this->write_frame(); // write image frame - if (error != NO_ERROR) { - logwrite(function, "ERROR: writing image frame"); - return error; - } + } catch (std::invalid_argument &) { + message.str(""); message << "unable to convert AD number \'" << adchan << "\' to integer"; + this->camera.log_error( function, message.str() ); + return ERROR; - // If mode is not RAW but RAWENABLE=1, then we will first read an image - // frame (just done above) and then a raw frame (below). To do that we - // must switch to raw mode then read the raw frame. Afterward, switch back - // to the original mode, for any subsequent exposures. - // - if (rawenable == 1) { -#ifdef LOGLEVEL_DEBUG - logwrite(function, "[DEBUG] rawenable is set -- IMAGE+RAW file will be saved"); - logwrite(function, "[DEBUG] switching to mode=RAW"); -#endif - std::string orig_mode = this->camera_info.current_observing_mode; - // save the original mode, so we can come back to it - error = this->set_camera_mode("raw"); // switch to raw mode - if (error != NO_ERROR) { - logwrite(function, "ERROR: switching to raw mode"); - return error; - } + } catch (std::out_of_range &) { + this->camera.log_error( function, "AD number out of integer range" ); + return ERROR; + } -#ifdef LOGLEVEL_DEBUG - message.str(""); message << "error=" << error << "[DEBUG] calling read_frame(Camera::FRAME_RAW) if error=0"; logwrite(function, message.str()); -#endif - error = this->read_frame(Camera::FRAME_RAW); // read raw frame - if (error != NO_ERROR) { - logwrite(function, "ERROR: reading raw frame"); - return error; - } -#ifdef LOGLEVEL_DEBUG - message.str(""); message << "error=" << error << "[DEBUG] calling write_frame() for raw data if error=0"; logwrite(function, message.str()); -#endif - error = this->write_frame(); // write raw frame - if (error != NO_ERROR) { - logwrite(function, "ERROR: writing raw frame"); - return error; - } -#ifdef LOGLEVEL_DEBUG - message.str(""); message << "error=" << error << "[DEBUG] switching back to original mode if error=0"; logwrite(function, message.str()); -#endif - error = this->set_camera_mode(orig_mode); // switch back to the original mode - if (error != NO_ERROR) { - logwrite(function, "ERROR: switching back to previous mode"); - return error; - } - } + if ( (adnum < 0) || (adnum > admax) ) { + message.str(""); message << "ADC channel " << adnum << " outside range {0:" << admax << "}"; + this->camera.log_error( function, message.str() ); + return ERROR; } + // Now that adnum is OK, convert next two tokens to gain, offset + // + int gain_try=0, offset_try=0; + try { + gain_try = std::stoi( tokens[1] ); // gain as function of AD channel + offset_try = std::stoi( tokens[2] ); // offset as function of AD channel - return error; + } catch (std::invalid_argument &) { + message.str(""); message << "unable to convert GAIN \"" << tokens[1] << "\" and/or OFFSET \"" << tokens[2] << "\" to integer"; + this->camera.log_error( function, message.str() ); + return ERROR; + + } catch (std::out_of_range &) { + message.str(""); message << "GAIN " << tokens[1] << ", OFFSET " << tokens[2] << " outside integer range"; + this->camera.log_error( function, message.str() ); + return ERROR; + } + // Now assign the gain,offsets to their appropriate position in the vectors + // + try { + this->gain.at( adnum ) = gain_try; // gain as function of AD channel + this->offset.at( adnum ) = offset_try; // offset as function of AD channel + + // Add the gain/offset as system header keywords. + // + std::stringstream keystr; + keystr.str(""); keystr << "GAIN" << std::setfill('0') << std::setw(2) << adnum << "=" << this->gain.at( adnum ) << "// gain for AD chan " << adnum; + this->systemkeys.addkey( keystr.str() ); + keystr.str(""); keystr << "OFFSET" << std::setfill('0') << std::setw(2) << adnum << "=" << this->offset.at( adnum ) << "// offset for AD chan " << adnum; + this->systemkeys.addkey( keystr.str() ); + + } catch ( std::out_of_range & ) { + message.str(""); message << "AD# " << adnum << " outside range {0:" << (this->gain.size() & this->offset.size()) << "}"; + this->camera.log_error( function, message.str() ); + if ( this->gain.empty() || this->offset.empty() ) { + this->camera.log_error( function, "gain/offset vectors are empty: no ADC or ADM board installed?" ); + } + return ERROR; + } + #ifdef LOGLEVEL_DEBUG + message.str(""); message << "[DEBUG] tap #" << tapn << " ad#" << adnum; + logwrite( function, message.str() ); + #endif + } // end if (tokens.size() == 3) + } // end for (int tapn=0; tapntaplines; tapn++) + + return error; + } + /**************** Archon::Interface::load_mode_settings *********************/ + + + /**************** Archon::Interface::get_frame_status ***********************/ + /** + * @fn get_frame_status + * @brief get the current frame buffer status + * @param none + * @return + * + * Sends the "FRAME" command to Archon, reads back the reply, then parses the + * reply and stores parameters into the frame structure (of type frame_data_t). + * + */ + long Interface::get_frame_status() { + std::string function = "Archon::Interface::get_frame_status"; + std::string reply; + std::stringstream message; + int newestframe, newestbuf; + long error=NO_ERROR; + + // send FRAME command to get frame buffer status + // + if ( (error = this->archon_cmd(FRAME, reply)) ) { + if ( error == ERROR ) logwrite( function, "ERROR: sending FRAME command" ); // don't log here if BUSY + return error; } - /**************** Archon::Interface::read_frame *****************************/ + // First Tokenize breaks the single, continuous reply string into vector of individual strings, + // from "TIMER=xxxx RBUF=xxxx " to: + // tokens[0] : TIMER=xxxx + // tokens[1] : RBUF=xxxx + // tokens[2] : etc. + std::vector tokens; + Tokenize(reply, tokens, " "); + + // loop over all tokens in reply + for (const auto & token : tokens) { + + // Second Tokenize separates the parameter from the value + // subtokens[0] = keyword + // subtokens[1] = value + std::vector subtokens; + subtokens.clear(); + Tokenize(token, subtokens, "="); + + // Each entry in the FRAME message must have two tokens, one for each side of the "=" equal sign + // (in other words there must be two subtokens per token) + if (subtokens.size() != 2) { + message.str(""); + message << "expected 2 but received invalid number of tokens (" << subtokens.size() << ") in FRAME message:"; + for (const auto & subtoken : subtokens) message << " " << subtoken; + this->camera.log_error( function, message.str() ); + return ERROR; // We could continue; but if one is bad then we could miss seeing a larger problem + } - /**************** Archon::Interface::hread_frame *****************************/ - /** - * @fn hread_frame - * @brief read latest Archon frame buffer - * @param frame_type - * @return ERROR or NO_ERROR - * - * This is the read_frame function which performs the actual read of the - * selected frame type. - * - * No write takes place here! - * - */ - long Interface::hread_frame() { - std::string function = "Archon::Interface::hread_frame"; - std::stringstream message; - int retval; - int bufready; - char check[5], header[5]; - char *ptr_image; - int bytesread, totalbytesread, toread; - uint64_t bufaddr; - unsigned int block, bufblocks = 0; - long error = ERROR; - int num_detect = this->modemap[this->camera_info.current_observing_mode].geometry.num_detect; + int bufnum=0; + int value=0; + uint64_t lvalue=0; - // Archon buffer number of the last frame read into memory - // Archon frame index is 1 biased so add 1 here - bufready = this->frame.index + 1; + // Parse subtokens[1] (value) based on subtoken[0] (keyword) - if (bufready < 1 || bufready > this->camera_info.activebufs) { - message.str(""); - message << "invalid Archon buffer " << bufready << " requested. Expected {1:" << this->camera_info. - activebufs << "}"; - this->camera.log_error(function, message.str()); - return ERROR; - } + // timer is a string + if (subtokens[0]=="TIMER") { + this->frame.timer = subtokens[1]; - message.str(""); - message << "will read image data from Archon controller buffer " << bufready << " frame " << this->frame.frame; - logwrite(function, message.str()); + } else { + // everything else is going to be a number + // use "try...catch" to catch exceptions converting strings to numbers + try { + // for all "BUFnSOMETHING=VALUE" we want the bufnum "n" + if (subtokens[0].compare(0, 3, "BUF")==0) { + // extract the "n" here which is 1-based (1,2,3) + bufnum = std::stoi( subtokens[0].substr(3, 1) ); + } - // Lock the frame buffer before reading it - // - // if ( this->lock_buffer(bufready) == ERROR) { - // logwrite( function, "ERROR locking frame buffer" ); - // return (ERROR); - // } + // for "BUFnBASE=xxx" the value is uint64 + if (subtokens[0].substr(4)=="BASE" ) { + lvalue = std::stol( subtokens[1] ); // this value will get assigned to the corresponding parameter - // Send the FETCH command to read the memory buffer from the Archon backplane. - // Archon replies with one binary response per requested block. Each response - // has a message header. + // for any "xxxTIMESTAMPxxx" the value is uint64 + } else if (subtokens[0].find("TIMESTAMP") != std::string::npos) { + lvalue = std::stol( subtokens[1] ); // this value will get assigned to the corresponding parameter - // Archon buffer base address - bufaddr = this->frame.bufbase[this->frame.index]; + // everything else is an int + } else { + value = std::stoi( subtokens[1] ); // this value will get assigned to the corresponding parameter + } - // Calculate the number of blocks expected. image_memory is bytes per detector - bufblocks = - (unsigned int) floor(((this->camera_info.image_memory * num_detect) + BLOCK_LEN - 1) / BLOCK_LEN); + } catch (std::invalid_argument &) { + message.str(""); message << "unable to convert buffer: " << subtokens[0] << " or value: " << subtokens[1] << " from FRAME message to integer. Expected BUFnSOMETHING=nnnn"; + this->camera.log_error( function, message.str() ); + return ERROR; - message.str(""); - message << "will read " << std::dec << this->camera_info.image_memory << " bytes " - << "0x" << std::uppercase << std::hex << bufblocks << " blocks from bufaddr=0x" << bufaddr; - logwrite(function, message.str()); + } catch (std::out_of_range &) { + message.str(""); message << "buffer: " << subtokens[0] << " or value: " << subtokens[1] << " from FRAME message outside integer range. Expected BUFnSOMETHING=nnnn"; + this->camera.log_error( function, message.str() ); + return ERROR; + } + } // end else everything else is a number + + // subtokens value has been parsed based on keyword + + // get currently locked buffers + if (subtokens[0]=="RBUF") this->frame.rbuf = value; // locked for reading + if (subtokens[0]=="WBUF") this->frame.wbuf = value; // locked for writing + + // The next group are BUFnSOMETHING=VALUE + // Extract the "n" which must be a number from 1 to Archon::nbufs + // After getting the buffer number we assign the corresponding value. + // + if (subtokens[0].compare(0, 3, "BUF")==0) { + bufnum = std::stoi( subtokens[0].substr(3, 1) ); + // verify buffer number (1,2, or 3) + if (bufnum < 1 || bufnum > Archon::nbufs) { + message.str(""); message << "buffer number " << bufnum << " from FRAME message outside range {1:" << Archon::nbufs << "}"; + this->camera.log_error( function, message.str() ); + return ERROR; + } + bufnum--; // subtract 1 because it is 1-based in the message but need 0-based for the indexing + // Assign value to appropriate variable in frame structure + if (subtokens[0].substr(4) == "SAMPLE") this->frame.bufsample[bufnum] = value; + if (subtokens[0].substr(4) == "COMPLETE") this->frame.bufcomplete[bufnum] = value; + if (subtokens[0].substr(4) == "MODE") this->frame.bufmode[bufnum] = value; + if (subtokens[0].substr(4) == "BASE") this->frame.bufbase[bufnum] = lvalue; + if (subtokens[0].substr(4) == "FRAME") this->frame.bufframen[bufnum] = value; + if (subtokens[0].substr(4) == "WIDTH") this->frame.bufwidth[bufnum] = value; + if (subtokens[0].substr(4) == "HEIGHT") this->frame.bufheight[bufnum] = value; + if (subtokens[0].substr(4) == "PIXELS") this->frame.bufpixels[bufnum] = value; + if (subtokens[0].substr(4) == "LINES") this->frame.buflines[bufnum] = value; + if (subtokens[0].substr(4) == "RAWBLOCKS") this->frame.bufrawblocks[bufnum] = value; + if (subtokens[0].substr(4) == "RAWLINES") this->frame.bufrawlines[bufnum] = value; + if (subtokens[0].substr(4) == "RAWOFFSET") this->frame.bufrawoffset[bufnum] = value; + if (subtokens[0].substr(4) == "TIMESTAMP") this->frame.buftimestamp[bufnum] = lvalue; + if (subtokens[0].substr(4) == "RETIMESTAMP") this->frame.bufretimestamp[bufnum] = lvalue; + if (subtokens[0].substr(4) == "FETIMESTAMP") this->frame.buffetimestamp[bufnum] = lvalue; + } // end if token is BUFnSOMETHiNG + + } // end loop over all returned tokens from FRAME reply + + // FRAME reply has now been parsed into frame structure variables + // now update frame.frame, frame.index, and frame.next_index + + // Current frame.index + newestbuf = this->frame.index; + + // Is frame.index a legal index? + if (this->frame.index < (int)this->frame.bufframen.size()) { + // get the current frame number + newestframe = this->frame.bufframen[this->frame.index]; + + } else { + // frame.index value is illegal + message.str(""); message << "newest buf " << this->frame.index << " from FRAME message exceeds number of buffers " << this->frame.bufframen.size(); + this->camera.log_error( function, message.str() ); + return ERROR; + } - if (!this->is_autofetch) { - // send the FETCH command. - // This will take the archon_busy semaphore, but not release it -- must release in this function! - // - error = this->fetch(bufaddr, bufblocks); - if (error != NO_ERROR) { - logwrite(function, "ERROR: fetching Archon buffer"); - return error; - } - } + int num_zero = 0; // count zero buffers - // Read the data from the connected socket into memory, one block at a time - // - ptr_image = this->image_data; - totalbytesread = 0; - std::cerr << "reading bytes: "; - for (block = 0; block < bufblocks; block++) { - // Are there data to read? - if ((retval = this->archon.Poll()) <= 0) { - if (retval == 0) { - message.str(""); - message << "Poll timeout waiting for Archon frame data"; - error = ERROR; - } // TODO should error=TIMEOUT? + // loop through the buffers + for (int bc=0; bcframe.bufframen[bc] == 0 ) num_zero++; - if (error != NO_ERROR) this->camera.log_error(function, message.str()); - break; // breaks out of for loop - } + // Is the latest frame greater than the current one and is it complete? + if ( (this->frame.bufframen[bc] > newestframe) && this->frame.bufcomplete[bc] ) { + // if so, update frame and buffer numbers + newestframe = this->frame.bufframen[bc]; + newestbuf = bc; + } + } - // Wait for a block+header Bytes to be available - // (but don't wait more than 1 second -- this should be tens of microseconds or less) - // - auto start = std::chrono::steady_clock::now(); // start a timer now + // In start-up case, all frame buffers are zero + if (num_zero == Archon::nbufs) { + // initialize frame number and buffer index both to zero + newestframe = 0; + newestbuf = 0; + } - 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 - std::cerr << "\n"; - this->camera.log_error(function, "timeout waiting for data from Archon"); - error = ERROR; - break; // breaks out of while loop - } - } - if (error != NO_ERROR) break; // needed to also break out of for loop on error + // save the newest frame number and index of newest buffer. + this->frame.frame = newestframe; + this->frame.index = newestbuf; - // Check message header - // - SNPRINTF(check, "<%02X:", this->msgref) - if ((retval = this->archon.Read(header, 4)) != 4) { - message.str(""); - message << "code " << retval << " reading Archon frame header"; - this->camera.log_error(function, message.str()); - error = ERROR; - break; // break out of for loop - } - if (header[0] == '?') { - // Archon retured an error - message.str(""); - message << "Archon returned \'?\' reading image data"; - this->camera.log_error(function, message.str()); - this->fetchlog(); // check the Archon log for error messages - error = ERROR; - break; // break out of for loop - } else if (strncmp(header, check, 4) != 0) { - message.str(""); - message << "Archon command-reply mismatch reading image data. header=" << header << " check=" << check; - this->camera.log_error(function, message.str()); - error = ERROR; - break; // break out of for loop - } + // startup condition for next_frame: + // frame number is 1 and corresponding buffer is not complete + if ( ( this->frame.bufframen[ this->frame.index ] ) == 1 && this->frame.bufcomplete[ this->frame.index ] == 0 ) { + this->frame.next_index = 0; - // Read the frame contents - // - bytesread = 0; - do { - toread = BLOCK_LEN - bytesread; - if ((retval = this->archon.Read(ptr_image, (size_t) toread)) > 0) { - bytesread += retval; // this will get zeroed after each block - totalbytesread += retval; // this won't (used only for info purposes) - std::cerr << std::setw(10) << totalbytesread << "\b\b\b\b\b\b\b\b\b\b"; - ptr_image += retval; // advance pointer - } - } while (bytesread < BLOCK_LEN); - } // end of loop: for (block=0; blockframe.index+1 + this->frame.next_index = this->frame.index + 1; - // give back the archon_busy semaphore to allow other threads to access the Archon now - // - const std::unique_lock lock(this->archon_mutex); - this->archon_busy = false; - this->archon_mutex.unlock(); + // frame.next_index wraps to 0 when it reaches the maximum number of active buffers. + if (this->frame.next_index >= this->camera_info.activebufs) { + this->frame.next_index = 0; + } + } - std::cerr << std::setw(10) << totalbytesread << " complete\n"; // display progress on same line of std err + return error; + } + /**************** Archon::Interface::get_frame_status ***********************/ + + + /**************** Archon::Interface::print_frame_status *********************/ + /** + * @fn print_frame_status + * @brief print the Archon frame buffer status + * @param none + * @return none + * + * Writes the Archon frame buffer status to the log file, + * I.E. information in this->frame structure, obtained from + * the "FRAME" command. See Archon::Interface::get_frame_status() + * + */ + void Interface::print_frame_status() { + std::string function = "Archon::Interface::print_frame_status"; + std::stringstream message; + int bufn; + char statestr[Archon::nbufs][4]; + + // write as log message + // + message.str(""); message << " buf base rawoff frame ready lines rawlines rblks width height state"; + logwrite(function, message.str()); + message.str(""); message << " --- ---------- ---------- ----- ----- ----- -------- ----- ----- ------ -----"; + logwrite(function, message.str()); + message.str(""); + for (bufn=0; bufn < Archon::nbufs; bufn++) { + memset(statestr[bufn], '\0', 4); + if ( (this->frame.rbuf-1) == bufn) strcat(statestr[bufn], "R"); + if ( (this->frame.wbuf-1) == bufn) strcat(statestr[bufn], "W"); + if ( this->frame.bufcomplete[bufn] ) strcat(statestr[bufn], "C"); + } + for (bufn=0; bufn < Archon::nbufs; bufn++) { + message << std::setw(3) << (bufn==this->frame.index ? "-->" : "") << " "; // buf + message << std::setw(3) << bufn+1 << " "; + message << "0x" << std::uppercase << std::setfill('0') << std::setw(8) << std::hex + << this->frame.bufbase[bufn] << " "; // base + message << "0x" << std::uppercase << std::setfill('0') << std::setw(8) << std::hex + << this->frame.bufrawoffset[bufn] << " "; // rawoff + message << std::setfill(' ') << std::setw(5) << std::dec << this->frame.bufframen[bufn] << " "; // frame + message << std::setw(5) << this->frame.bufcomplete[bufn] << " "; // ready + message << std::setw(5) << this->frame.buflines[bufn] << " "; // lines + message << std::setw(8) << this->frame.bufrawlines[bufn] << " "; // rawlines + message << std::setw(5) << this->frame.bufrawblocks[bufn] << " "; // rblks + message << std::setw(5) << this->frame.bufwidth[bufn] << " "; // width + message << std::setw(6) << this->frame.bufheight[bufn] << " "; // height + message << std::setw(5) << statestr[bufn] << " "; // state + message << std::setw(5) << this->frame.bufpixels[bufn]; + logwrite(function, message.str()); + message.str(""); + } + } + /**************** Archon::Interface::print_frame_status *********************/ + + + /**************** Archon::Interface::lock_buffer ****************************/ + /** + * @fn lock_buffer + * @brief lock Archon frame buffer + * @param int frame buffer to lock + * @return + * + */ + long Interface::lock_buffer(int buffer) { + std::string function = "Archon::Interface::lock_buffer"; + std::stringstream message; + std::stringstream sscmd; + + sscmd.str(""); + sscmd << "LOCK" << buffer; + if ( this->archon_cmd(sscmd.str()) ) { + message.str(""); message << "ERROR: sending Archon command to lock frame buffer " << buffer; + logwrite( function, message.str() ); + return ERROR; + } + return (NO_ERROR); + } + /**************** Archon::Interface::lock_buffer ****************************/ + + + /**************** Archon::Interface::get_timer ******************************/ + /** + * @fn get_timer + * @brief read the 64 bit interal timer from the Archon controller + * @param *timer pointer to type unsigned long int + * @return errno on error, 0 if okay. + * + * Sends the "TIMER" command to Archon, reads back the reply, and stores the + * value as (unsigned long int) in the argument variable pointed to by *timer. + * + * This is an internal 64 bit timer/counter. One tick of the counter is 10 ns. + * + */ + long Interface::get_timer(unsigned long int *timer) { + std::string function = "Archon::Interface::get_timer"; + std::string reply; + std::stringstream message, timer_ss; + std::vector tokens; + long error; + + // send TIMER command to get frame buffer status + // + if ( (error = this->archon_cmd(TIMER, reply)) != NO_ERROR ) { + return error; + } - // If we broke out of the for loop for an error then report incomplete read - // - if (error == ERROR || block < bufblocks) { - message.str(""); - message << "incomplete frame read " << std::dec - << totalbytesread << " bytes: " << block << " of " << bufblocks << " 1024-byte blocks"; - logwrite(function, message.str()); - } + Tokenize(reply, tokens, "="); // Tokenize the reply - // Unlock the frame buffer - // - // if (error == NO_ERROR) error = this->archon_cmd(UNLOCK); + // Reponse should be "TIMER=xxxx\n" so there needs + // to be two tokens + // + if (tokens.size() != 2) { + message.str(""); message << "unrecognized timer response: " << reply << ". Expected TIMER=xxxx"; + this->camera.log_error( function, message.str() ); + return ERROR; + } - // On success, write the value to the log and return - // - if (error == NO_ERROR) { - message.str(""); - message << "successfully read " << std::dec << totalbytesread - << " image bytes (0x" << std::uppercase << std::hex << bufblocks << - " blocks) from Archon controller"; - logwrite(function, message.str()); - } else { - // Throw an error for any other errors - logwrite(function, "ERROR: reading Archon camera data to memory!"); - } - return error; + // Second token must be a hexidecimal string + // + std::string timer_str = tokens[1]; + try { + timer_str.erase(timer_str.find('\n'), 1); + } catch(...) { } // remove newline + if (!std::all_of(timer_str.begin(), timer_str.end(), ::isxdigit)) { + message.str(""); message << "unrecognized timer value: " << timer_str << ". Expected hexadecimal string"; + this->camera.log_error( function, message.str() ); + return ERROR; } - /**************** Archon::Interface::hread_frame *****************************/ + // convert from hex string to integer and save return value + // + timer_ss << std::hex << tokens[1]; + timer_ss >> *timer; + return NO_ERROR; + } + /**************** Archon::Interface::get_timer ******************************/ + + + /**************** Archon::Interface::fetch **********************************/ + /** + * @fn fetch + * @brief fetch Archon frame buffer + * @param int frame buffer to lock + * @return + * + */ + long Interface::fetch(uint64_t bufaddr, uint32_t bufblocks) { + std::string function = "Archon::Interface::fetch"; + std::stringstream message; + uint32_t maxblocks = (uint32_t)(1.5E9 / this->camera_info.activebufs / 1024 ); + uint64_t maxoffset = this->frame.bufbase[this->frame.index]; + uint64_t maxaddr = maxoffset + maxblocks; + + if ( bufaddr > maxaddr ) { + message.str(""); message << "fetch Archon buffer requested address 0x" << std::hex << bufaddr << " exceeds 0x" << maxaddr; + this->camera.log_error( function, message.str() ); + return ERROR; + } + if ( bufblocks > maxblocks ) { + message.str(""); message << "fetch Archon buffer requested blocks 0x" << std::hex << bufblocks << " exceeds 0x" << maxblocks; + this->camera.log_error( function, message.str() ); + return ERROR; + } - /**************** Archon::Interface::read_frame *****************************/ - /** - * @fn read_frame - * @brief read latest Archon frame buffer - * @param frame_type - * @return ERROR or NO_ERROR - * - * This is the overloaded read_frame function which accepts the frame_type argument. - * This is called only by this->read_frame() to perform the actual read of the - * selected frame type. - * - * No write takes place here! - * - */ - long Interface::read_frame(Camera::frame_type_t frame_type) { - std::string function = "Archon::Interface::read_frame"; + std::stringstream sscmd; + sscmd << "FETCH" + << std::setfill('0') << std::setw(8) << std::hex + << bufaddr + << std::setfill('0') << std::setw(8) << std::hex + << bufblocks; + std::string scmd = sscmd.str(); + try { + std::transform( scmd.begin(), scmd.end(), scmd.begin(), ::toupper ); // make uppercase + + } catch (...) { + message.str(""); message << "converting command: " << sscmd.str() << " to uppercase"; + this->camera.log_error( function, message.str() ); + return ERROR; + } + + if (this->archon_cmd(scmd) == ERROR) { + logwrite( function, "ERROR: sending FETCH command. Aborting read." ); + this->archon_cmd(UNLOCK); // unlock all buffers + return ERROR; + } + + message.str(""); message << "reading " << (this->camera_info.frame_type==Camera::FRAME_RAW?"raw":"image") << " with " << scmd; + logwrite(function, message.str()); + return NO_ERROR; + } + /**************** Archon::Interface::fetch **********************************/ + + + /**************** Archon::Interface::read_frame *****************************/ + /** + * @fn read_frame + * @brief read latest Archon frame buffer + * @param none + * @return ERROR or NO_ERROR + * + * This is function is overloaded. + * + * This version, with no parameter, is the one that is called by the server. + * The decision is made here if the frame to be read is a RAW or an IMAGE + * frame based on this->camera_info.current_observing_mode, then the + * overloaded version of read_frame(frame_type) is called with the appropriate + * frame type of IMAGE or RAW. + * + * This function WILL call write_frame(...) to write data after reading it. + * + */ + long Interface::read_frame() { + std::string function = "Archon::Interface::read_frame"; + std::stringstream message; + long error = NO_ERROR; + + if ( ! this->modeselected ) { + this->camera.log_error( function, "no mode selected" ); + return ERROR; + } + + int rawenable = this->modemap[this->camera_info.current_observing_mode].rawenable; + + if (rawenable == -1) { + this->camera.log_error( function, "RAWENABLE is undefined" ); + return ERROR; + } + + // RAW-only + // + if (this->camera_info.current_observing_mode == "RAW") { // "RAW" is the only reserved mode name + + // the RAWENABLE parameter must be set in the ACF file, in order to read RAW data + // + if (rawenable==0) { + this->camera.log_error( function, "observing mode is RAW but RAWENABLE=0 -- change mode or set RAWENABLE?" ); + return ERROR; + + } else { + error = this->read_frame(Camera::FRAME_RAW); // read raw frame + if ( error != NO_ERROR ) { logwrite( function, "ERROR: reading raw frame" ); return error; } + error = this->write_frame(); // write raw frame + if ( error != NO_ERROR ) { logwrite( function, "ERROR: writing raw frame" ); return error; } + } + + } else { + // IMAGE, or IMAGE+RAW + // datacube was already set = true in the expose function + error = this->read_frame(Camera::FRAME_IMAGE); // read image frame + if ( error != NO_ERROR ) { logwrite( function, "ERROR: reading image frame" ); return error; } + error = this->write_frame(); // write image frame + if ( error != NO_ERROR ) { logwrite( function, "ERROR: writing image frame" ); return error; } + + // If mode is not RAW but RAWENABLE=1, then we will first read an image + // frame (just done above) and then a raw frame (below). To do that we + // must switch to raw mode then read the raw frame. Afterward, switch back + // to the original mode, for any subsequent exposures. + // + if (rawenable == 1) { + #ifdef LOGLEVEL_DEBUG + logwrite(function, "[DEBUG] rawenable is set -- IMAGE+RAW file will be saved"); + logwrite(function, "[DEBUG] switching to mode=RAW"); + #endif + std::string orig_mode = this->camera_info.current_observing_mode; // save the original mode, so we can come back to it + error = this->set_camera_mode("raw"); // switch to raw mode + if ( error != NO_ERROR ) { logwrite( function, "ERROR: switching to raw mode" ); return error; } + + #ifdef LOGLEVEL_DEBUG + message.str(""); message << "error=" << error << "[DEBUG] calling read_frame(Camera::FRAME_RAW) if error=0"; logwrite(function, message.str()); + #endif + error = this->read_frame(Camera::FRAME_RAW); // read raw frame + if ( error != NO_ERROR ) { logwrite( function, "ERROR: reading raw frame" ); return error; } + #ifdef LOGLEVEL_DEBUG + message.str(""); message << "error=" << error << "[DEBUG] calling write_frame() for raw data if error=0"; logwrite(function, message.str()); + #endif + error = this->write_frame(); // write raw frame + if ( error != NO_ERROR ) { logwrite( function, "ERROR: writing raw frame" ); return error; } + #ifdef LOGLEVEL_DEBUG + message.str(""); message << "error=" << error << "[DEBUG] switching back to original mode if error=0"; logwrite(function, message.str()); + #endif + error = this->set_camera_mode(orig_mode); // switch back to the original mode + if ( error != NO_ERROR ) { logwrite( function, "ERROR: switching back to previous mode" ); return error; } + } + } + + return error; + } + /**************** Archon::Interface::read_frame *****************************/ + + /**************** Archon::Interface::hread_frame *****************************/ + /** + * @fn hread_frame + * @brief read latest Archon frame buffer + * @param frame_type + * @return ERROR or NO_ERROR + * + * This is the read_frame function which performs the actual read of the + * selected frame type. + * + * No write takes place here! + * + */ + long Interface::hread_frame() { + std::string function = "Archon::Interface::hread_frame"; std::stringstream message; int retval; int bufready; @@ -2985,113 +2627,54 @@ namespace Archon { char *ptr_image; int bytesread, totalbytesread, toread; uint64_t bufaddr; - unsigned int block, bufblocks = 0; + unsigned int block, bufblocks=0; long error = ERROR; int num_detect = this->modemap[this->camera_info.current_observing_mode].geometry.num_detect; - this->camera_info.frame_type = frame_type; - - /*** - // Check that image buffer is prepared //TODO should I call prepare_image_buffer() here, automatically? - // - if ( (this->image_data == nullptr) || - (this->image_data_bytes == 0) ) { - this->camera.log_error( function, "image buffer not ready" ); - // return ERROR; - } - - if ( this->image_data_allocated != this->image_data_bytes ) { - message.str(""); message << "incorrect image buffer size: " - << this->image_data_allocated << " bytes allocated but " << this->image_data_bytes << " needed"; - this->camera.log_error( function, message.str() ); - // return ERROR; - } - ***/ - - error = this->prepare_image_buffer(); - if (error == ERROR) { - logwrite(function, "ERROR: unable to allocate an image buffer"); - return ERROR; - } - - // TODO removed 2021-Jun-09 - // This shouldn't be needed since wait_for_readout() was called previously. - // // Get the current frame buffer status - // // - // error = this->get_frame_status(); - // - // if (error != NO_ERROR) { - // this->camera.log_error( function, "unable to get frame status"); - // return error; - // } - // Archon buffer number of the last frame read into memory - // + // Archon frame index is 1 biased so add 1 here bufready = this->frame.index + 1; if (bufready < 1 || bufready > this->camera_info.activebufs) { - message.str(""); - message << "invalid Archon buffer " << bufready << " requested. Expected {1:" << this->camera_info. - activebufs << "}"; - this->camera.log_error(function, message.str()); + message.str(""); message << "invalid Archon buffer " << bufready << " requested. Expected {1:" << this->camera_info.activebufs << "}"; + this->camera.log_error( function, message.str() ); return ERROR; } - message.str(""); - message << "will read " << (frame_type == Camera::FRAME_RAW ? "raw" : "image") - << " data from Archon controller buffer " << bufready << " frame " << this->frame.frame; + message.str(""); message << "will read image data from Archon controller buffer " << bufready << " frame " << this->frame.frame; logwrite(function, message.str()); // Lock the frame buffer before reading it // - if (this->lock_buffer(bufready) == ERROR) { - logwrite(function, "ERROR locking frame buffer"); - return (ERROR); - } + // if ( this->lock_buffer(bufready) == ERROR) { + // logwrite( function, "ERROR locking frame buffer" ); + // return (ERROR); + // } // Send the FETCH command to read the memory buffer from the Archon backplane. // Archon replies with one binary response per requested block. Each response - // has a message header. - // - switch (frame_type) { - case Camera::FRAME_RAW: - // Archon buffer base address - bufaddr = this->frame.bufbase[this->frame.index] + this->frame.bufrawoffset[this->frame.index]; - - // Calculate the number of blocks expected. image_memory is bytes per detector - bufblocks = (unsigned int) floor((this->camera_info.image_memory + BLOCK_LEN - 1) / BLOCK_LEN); - break; - - case Camera::FRAME_IMAGE: - // Archon buffer base address - bufaddr = this->frame.bufbase[this->frame.index]; - - // Calculate the number of blocks expected. image_memory is bytes per detector - bufblocks = - (unsigned int) floor( - ((this->camera_info.image_memory * num_detect) + BLOCK_LEN - 1) / BLOCK_LEN); - break; - - default: // should be impossible - message.str(""); - message << "unknown frame type specified: " << frame_type << ": expected FRAME_RAW | FRAME_IMAGE"; - this->camera.log_error(function, message.str()); - return ERROR; - break; - } + // has a message header. - message.str(""); - message << "will read " << std::dec << this->camera_info.image_memory << " bytes " - << "0x" << std::uppercase << std::hex << bufblocks << " blocks from bufaddr=0x" << bufaddr; + // Archon buffer base address + bufaddr = this->frame.bufbase[this->frame.index]; + + // Calculate the number of blocks expected. image_memory is bytes per detector + bufblocks = + (unsigned int) floor( ((this->camera_info.image_memory * num_detect) + BLOCK_LEN - 1 ) / BLOCK_LEN ); + + message.str(""); message << "will read " << std::dec << this->camera_info.image_memory << " bytes " + << "0x" << std::uppercase << std::hex << bufblocks << " blocks from bufaddr=0x" << bufaddr; logwrite(function, message.str()); - // send the FETCH command. - // This will take the archon_busy semaphore, but not release it -- must release in this function! - // - error = this->fetch(bufaddr, bufblocks); - if (error != NO_ERROR) { - logwrite(function, "ERROR: fetching Archon buffer"); - return error; + if (!this->is_autofetch) { + // send the FETCH command. + // This will take the archon_busy semaphore, but not release it -- must release in this function! + // + error = this->fetch(bufaddr, bufblocks); + if (error != NO_ERROR) { + logwrite(function, "ERROR: fetching Archon buffer"); + return error; + } } // Read the data from the connected socket into memory, one block at a time @@ -3099,71 +2682,64 @@ namespace Archon { ptr_image = this->image_data; totalbytesread = 0; std::cerr << "reading bytes: "; - for (block = 0; block < bufblocks; block++) { + for (block=0; blockarchon.Poll()) <= 0) { - if (retval == 0) { + if ( (retval=this->archon.Poll()) <= 0) { + if (retval==0) { message.str(""); message << "Poll timeout waiting for Archon frame data"; error = ERROR; - } // TODO should error=TIMEOUT? + } // TODO should error=TIMEOUT? - if (retval < 0) { + if (retval<0) { message.str(""); message << "Poll error waiting for Archon frame data"; error = ERROR; } - if (error != NO_ERROR) this->camera.log_error(function, message.str()); - break; // breaks out of for loop + if ( error != NO_ERROR ) this->camera.log_error( function, message.str() ); + break; // breaks out of for loop } // Wait for a block+header Bytes to be available // (but don't wait more than 1 second -- this should be tens of microseconds or less) // - auto start = std::chrono::steady_clock::now(); // start a timer now + auto start = std::chrono::steady_clock::now(); // start a timer now - 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 + 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 std::cerr << "\n"; - this->camera.log_error(function, "timeout waiting for data from Archon"); + this->camera.log_error( function, "timeout waiting for data from Archon" ); error = ERROR; - break; // breaks out of while loop + break; // breaks out of while loop } } - if (error != NO_ERROR) break; // needed to also break out of for loop on error + if ( error != NO_ERROR ) break; // needed to also break out of for loop on error // Check message header // SNPRINTF(check, "<%02X:", this->msgref) - if ((retval = this->archon.Read(header, 4)) != 4) { - message.str(""); - message << "code " << retval << " reading Archon frame header"; - this->camera.log_error(function, message.str()); + if ( (retval=this->archon.Read(header, 4)) != 4 ) { + message.str(""); message << "code " << retval << " reading Archon frame header"; + this->camera.log_error( function, message.str() ); error = ERROR; - break; // break out of for loop + break; // break out of for loop } - if (header[0] == '?') { - // Archon retured an error - message.str(""); - message << "Archon returned \'?\' reading " << (frame_type == Camera::FRAME_RAW ? "raw " : "image ") << - " data"; - this->camera.log_error(function, message.str()); - this->fetchlog(); // check the Archon log for error messages + if (header[0] == '?') { // Archon retured an error + message.str(""); message << "Archon returned \'?\' reading image data"; + this->camera.log_error( function, message.str() ); + this->fetchlog(); // check the Archon log for error messages error = ERROR; - break; // break out of for loop + break; // break out of for loop + } else if (strncmp(header, check, 4) != 0) { - message.str(""); - message << "Archon command-reply mismatch reading " << (frame_type == Camera::FRAME_RAW - ? "raw " - : "image ") - << " data. header=" << header << " check=" << check; - this->camera.log_error(function, message.str()); + message.str(""); message << "Archon command-reply mismatch reading image data. header=" << header << " check=" << check; + this->camera.log_error( function, message.str() ); error = ERROR; - break; // break out of for loop + break; // break out of for loop } // Read the frame contents @@ -3171,13 +2747,14 @@ namespace Archon { bytesread = 0; do { toread = BLOCK_LEN - bytesread; - if ((retval = this->archon.Read(ptr_image, (size_t) toread)) > 0) { - bytesread += retval; // this will get zeroed after each block - totalbytesread += retval; // this won't (used only for info purposes) + if ( (retval=this->archon.Read(ptr_image, (size_t)toread)) > 0 ) { + bytesread += retval; // this will get zeroed after each block + totalbytesread += retval; // this won't (used only for info purposes) std::cerr << std::setw(10) << totalbytesread << "\b\b\b\b\b\b\b\b\b\b"; - ptr_image += retval; // advance pointer + ptr_image += retval; // advance pointer } } while (bytesread < BLOCK_LEN); + } // end of loop: for (block=0; blockarchon_busy = false; this->archon_mutex.unlock(); - std::cerr << std::setw(10) << totalbytesread << " complete\n"; // display progress on same line of std err + std::cerr << std::setw(10) << totalbytesread << " complete\n"; // display progress on same line of std err // If we broke out of the for loop for an error then report incomplete read // - if (error == ERROR || block < bufblocks) { - message.str(""); - message << "incomplete frame read " << std::dec - << totalbytesread << " bytes: " << block << " of " << bufblocks << " 1024-byte blocks"; - logwrite(function, message.str()); + if ( error==ERROR || block < bufblocks) { + message.str(""); message << "incomplete frame read " << std::dec + << totalbytesread << " bytes: " << block << " of " << bufblocks << " 1024-byte blocks"; + logwrite( function, message.str() ); } // Unlock the frame buffer // - if (error == NO_ERROR) error = this->archon_cmd(UNLOCK); + // if (error == NO_ERROR) error = this->archon_cmd(UNLOCK); // On success, write the value to the log and return // if (error == NO_ERROR) { - message.str(""); - message << "successfully read " << std::dec << totalbytesread << (frame_type == Camera::FRAME_RAW - ? " raw" - : " image") - << " bytes (0x" << std::uppercase << std::hex << bufblocks << " blocks) from Archon controller"; + message.str(""); message << "successfully read " << std::dec << totalbytesread + << " image bytes (0x" << std::uppercase << std::hex << bufblocks << " blocks) from Archon controller"; logwrite(function, message.str()); + } else { // Throw an error for any other errors - logwrite(function, "ERROR: reading Archon camera data to memory!"); + logwrite( function, "ERROR: reading Archon camera data to memory!" ); } return error; } + /**************** Archon::Interface::hread_frame *****************************/ - /**************** Archon::Interface::read_frame *****************************/ + /**************** Archon::Interface::read_frame *****************************/ + /** + * @fn read_frame + * @brief read latest Archon frame buffer + * @param frame_type + * @return ERROR or NO_ERROR + * + * This is the overloaded read_frame function which accepts the frame_type argument. + * This is called only by this->read_frame() to perform the actual read of the + * selected frame type. + * + * No write takes place here! + * + */ + long Interface::read_frame(Camera::frame_type_t frame_type) { + std::string function = "Archon::Interface::read_frame"; + std::stringstream message; + int retval; + int bufready; + char check[5], header[5]; + char *ptr_image; + int bytesread, totalbytesread, toread; + uint64_t bufaddr; + unsigned int block, bufblocks=0; + long error = ERROR; + int num_detect = this->modemap[this->camera_info.current_observing_mode].geometry.num_detect; + + this->camera_info.frame_type = frame_type; + +/*** + // Check that image buffer is prepared //TODO should I call prepare_image_buffer() here, automatically? + // + if ( (this->image_data == nullptr) || + (this->image_data_bytes == 0) ) { + this->camera.log_error( function, "image buffer not ready" ); +// return ERROR; + } + if ( this->image_data_allocated != this->image_data_bytes ) { + message.str(""); message << "incorrect image buffer size: " + << this->image_data_allocated << " bytes allocated but " << this->image_data_bytes << " needed"; + this->camera.log_error( function, message.str() ); +// return ERROR; + } +***/ - /**************** Archon::Interface::write_frame ****************************/ - /** - * @fn write_frame - * @brief creates a FITS_file object to write the image_data buffer to disk - * @param none - * @return ERROR or NO_ERROR - * - * A FITS_file object is created here to write the data. This object MUST remain - * valid while any (all) threads are writing data, so the write_data function - * will keep track of threads so that it doesn't terminate until all of its - * threads terminate. - * - * The camera_info class was copied into fits_info when the exposure was started, //TODO I've un-done this. - * so use fits_info from here on out. //TODO Don't use fits_info right now. - * //TODO Only using camera_info - */ - long Interface::write_frame() { - std::string function = "Archon::Interface::write_frame"; - std::stringstream message; - uint32_t *cbuf32; //!< used to cast char buf into 32 bit int - uint16_t *cbuf16; //!< used to cast char buf into 16 bit int - int16_t *cbuf16s; //!< used to cast char buf into 16 bit int - long error = NO_ERROR; + error = this->prepare_image_buffer(); + if (error == ERROR) { + logwrite( function, "ERROR: unable to allocate an image buffer" ); + return ERROR; + } - if (!this->modeselected) { - this->camera.log_error(function, "no mode selected"); - return ERROR; - } +// TODO removed 2021-Jun-09 +// This shouldn't be needed since wait_for_readout() was called previously. +// // Get the current frame buffer status +// // +// error = this->get_frame_status(); +// +// if (error != NO_ERROR) { +// this->camera.log_error( function, "unable to get frame status"); +// return error; +// } + + // Archon buffer number of the last frame read into memory + // + bufready = this->frame.index + 1; - // message.str(""); message << "writing " << this->fits_info.bitpix << "-bit data from memory to disk"; //TODO - message.str(""); - message << "writing " << this->camera_info.bitpix << "-bit data from memory to disk"; - logwrite(function, message.str()); + if (bufready < 1 || bufready > this->camera_info.activebufs) { + message.str(""); message << "invalid Archon buffer " << bufready << " requested. Expected {1:" << this->camera_info.activebufs << "}"; + this->camera.log_error( function, message.str() ); + return ERROR; + } - // The Archon sends four 8-bit numbers per pixel. To convert this into something usable, - // cast the image buffer into integers. Handled differently depending on bits per pixel. - // - switch (this->camera_info.bitpix) { - // convert four 8-bit values into a 32-bit value and scale by 2^16 - // - case 32: { - cbuf32 = (uint32_t *) this->image_data; // cast here to 32b - - // Write each amplifier as a separate extension - // - if (this->camera.cubeamps()) { - float *fext = nullptr; - - for (int ext = 0; ext < (int) this->camera_info.amp_section.size(); ext++) { - try { - // get the coordinates of this amplifier extension - // - long x1 = this->camera_info.amp_section.at(ext).at(0); - long x2 = this->camera_info.amp_section.at(ext).at(1); - long y1 = this->camera_info.amp_section.at(ext).at(2); - long y2 = this->camera_info.amp_section.at(ext).at(3); - - // assign this amplifier section as the region of interest - // - this->camera_info.region_of_interest[0] = x1; - this->camera_info.region_of_interest[1] = x2; - this->camera_info.region_of_interest[2] = y1; - this->camera_info.region_of_interest[3] = y2; - - // This call to set_axes() is to set the axis_pixels, axes, and section_size, - // needed for the FITS writer - // - error = this->camera_info.set_axes(); + message.str(""); message << "will read " << (frame_type == Camera::FRAME_RAW ? "raw" : "image") + << " data from Archon controller buffer " << bufready << " frame " << this->frame.frame; + logwrite(function, message.str()); -#ifdef LOGLEVEL_DEBUG - message.str(""); message << "[DEBUG] x1=" << x1 << " x2=" << x2 << " y1=" << y1 << " y2=" << y2; - logwrite( function, message.str() ); - message.str(""); message << "[DEBUG] axes[0]=" << this->camera_info.axes[0] << " axes[1]=" << this->camera_info.axes[1]; - logwrite( function, message.str() ); -#endif + // Lock the frame buffer before reading it + // + if ( this->lock_buffer(bufready) == ERROR) { + logwrite( function, "ERROR locking frame buffer" ); + return (ERROR); + } - // allocate the number of pixels needed for this amplifier extension - // - long ext_size = (x2 - x1 + 1) * (y2 - y1 + 1); - fext = new float[ext_size]; + // Send the FETCH command to read the memory buffer from the Archon backplane. + // Archon replies with one binary response per requested block. Each response + // has a message header. + // + switch (frame_type) { + case Camera::FRAME_RAW: + // Archon buffer base address + bufaddr = this->frame.bufbase[this->frame.index] + this->frame.bufrawoffset[this->frame.index]; -#ifdef LOGLEVEL_DEBUG - message.str(""); message << "[DEBUG] allocated " << ext_size << " pixels for extension " << this->camera_info.extension+1; - logwrite( function, message.str() ); -#endif + // Calculate the number of blocks expected. image_memory is bytes per detector + bufblocks = (unsigned int) floor( (this->camera_info.image_memory + BLOCK_LEN - 1 ) / BLOCK_LEN ); + break; - // copy this amplifier from the main cbuf32, - // at the same time right-shift the requested number of bits - // - long pix = 0; - long ncols = this->camera_info.detector_pixels[0]; // PIXELCOUNT - for (long row = y1 - 1; row < y2; row++) { - for (long col = x1 - 1; col < x2; col++) { - fext[pix++] = (float) (cbuf32[row * ncols + col] >> this->n_hdrshift); - } - } + case Camera::FRAME_IMAGE: + // Archon buffer base address + bufaddr = this->frame.bufbase[this->frame.index]; -#ifdef LOGLEVEL_DEBUG - message.str(""); message << "[DEBUG] calling fits_file.write_image( ) for extension " << this->camera_info.extension+1; - logwrite( function, message.str() ); -#endif + // Calculate the number of blocks expected. image_memory is bytes per detector + bufblocks = + (unsigned int) floor( ((this->camera_info.image_memory * num_detect) + BLOCK_LEN - 1 ) / BLOCK_LEN ); + break; + + default: // should be impossible + message.str(""); message << "unknown frame type specified: " << frame_type << ": expected FRAME_RAW | FRAME_IMAGE"; + this->camera.log_error( function, message.str() ); + return ERROR; + break; + } - error = this->fits_file.write_image(fext, this->camera_info); // write the image to disk - this->camera_info.extension++; // increment extension for cubes - if (fext != nullptr) { - delete [] fext; - fext = nullptr; - } // dynamic object not automatic so must be destroyed - } catch (std::out_of_range &) { - message.str(""); - message << "ERROR: " << ext << " is a bad extension number"; - logwrite(function, message.str()); - if (fext != nullptr) { - delete [] fext; - fext = nullptr; - } // dynamic object not automatic so must be destroyed - return ERROR; - } - } - // end if this->camera.cubeamps() - } else { - // Write all amplifiers to the same extension - // - float *fbuf = nullptr; - // fbuf = new float[ this->fits_info.section_size ]; // allocate a float buffer of same number of pixels for scaling //TODO - fbuf = new float[this->camera_info.section_size]; - // allocate a float buffer of same number of pixels for scaling - - // for (long pix=0; pix < this->fits_info.section_size; pix++) //TODO - for (long pix = 0; pix < this->camera_info.section_size; pix++) { - fbuf[pix] = (float) (cbuf32[pix] >> this->n_hdrshift); - // right shift the requested number of bits - } + message.str(""); message << "will read " << std::dec << this->camera_info.image_memory << " bytes " + << "0x" << std::uppercase << std::hex << bufblocks << " blocks from bufaddr=0x" << bufaddr; + logwrite(function, message.str()); - // error = fits_file.write_image(fbuf, this->fits_info); // write the image to disk //TODO - error = this->fits_file.write_image(fbuf, this->camera_info); // write the image to disk - if (error != NO_ERROR) { this->camera.log_error(function, "writing 32-bit image to disk"); } - delete [] fbuf; - } - break; - } + // send the FETCH command. + // This will take the archon_busy semaphore, but not release it -- must release in this function! + // + error = this->fetch(bufaddr, bufblocks); + if ( error != NO_ERROR ) { + logwrite( function, "ERROR: fetching Archon buffer" ); + return error; + } - // convert four 8-bit values into 16 bit values - // - case 16: { - if (this->camera_info.datatype == USHORT_IMG) { - // raw - cbuf16 = (uint16_t *) this->image_data; // cast to 16b unsigned int - // error = fits_file.write_image(cbuf16, this->fits_info); // write the image to disk //TODO - error = this->fits_file.write_image(cbuf16, this->camera_info); // write the image to disk - if (error != NO_ERROR) { this->camera.log_error(function, "writing 16-bit raw image to disk"); } - } else if (this->camera_info.datatype == SHORT_IMG) { - cbuf16s = (int16_t *) this->image_data; // cast to 16b signed int - int16_t *ibuf = nullptr; - ibuf = new int16_t[this->camera_info.section_size]; - for (long pix = 0; pix < this->camera_info.section_size; pix++) { - ibuf[pix] = cbuf16s[pix] - 32768; // subtract 2^15 from every pixel - } - error = this->fits_file.write_image(ibuf, this->camera_info); // write the image to disk - if (error != NO_ERROR) { this->camera.log_error(function, "writing 16-bit image to disk"); } - delete [] ibuf; - } else { - message.str(""); - message << "unsupported 16 bit datatype " << this->camera_info.datatype; - this->camera.log_error(function, message.str()); - error = ERROR; - } - break; - } + // Read the data from the connected socket into memory, one block at a time + // + ptr_image = this->image_data; + totalbytesread = 0; + std::cerr << "reading bytes: "; + for (block=0; blockarchon.Poll()) <= 0) { + if (retval==0) { + message.str(""); + message << "Poll timeout waiting for Archon frame data"; + error = ERROR; + } // TODO should error=TIMEOUT? - // shouldn't happen - // - default: - // message.str(""); message << "unrecognized bits per pixel: " << this->fits_info.bitpix; //TODO - message.str(""); - message << "unrecognized bits per pixel: " << this->camera_info.bitpix; - this->camera.log_error(function, message.str()); - error = ERROR; - break; + if (retval<0) { + message.str(""); + message << "Poll error waiting for Archon frame data"; + error = ERROR; } - // Things to do after successful write - // - if (error == NO_ERROR) { - if (this->camera.datacube()) { - this->camera_info.extension++; // increment extension for cubes - message.str(""); - message << "DATACUBE:" << this->camera_info.extension << " " << (error == NO_ERROR - ? "COMPLETE" - : "ERROR"); - this->camera.async.enqueue(message.str()); - error == NO_ERROR ? logwrite(function, message.str()) : this->camera.log_error(function, message.str()); - } - logwrite(function, "frame write complete"); - } else { - logwrite(function, "ERROR: writing image"); + if ( error != NO_ERROR ) this->camera.log_error( function, message.str() ); + break; // breaks out of for loop + } + + // Wait for a block+header Bytes to be available + // (but don't wait more than 1 second -- this should be tens of microseconds or less) + // + auto start = std::chrono::steady_clock::now(); // start a timer now + + 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 + std::cerr << "\n"; + this->camera.log_error( function, "timeout waiting for data from Archon" ); + error = ERROR; + break; // breaks out of while loop + } + } + if ( error != NO_ERROR ) break; // needed to also break out of for loop on error + + // Check message header + // + SNPRINTF(check, "<%02X:", this->msgref) + if ( (retval=this->archon.Read(header, 4)) != 4 ) { + message.str(""); message << "code " << retval << " reading Archon frame header"; + this->camera.log_error( function, message.str() ); + error = ERROR; + break; // break out of for loop + } + if (header[0] == '?') { // Archon retured an error + message.str(""); message << "Archon returned \'?\' reading " << (frame_type==Camera::FRAME_RAW?"raw ":"image ") << " data"; + this->camera.log_error( function, message.str() ); + this->fetchlog(); // check the Archon log for error messages + error = ERROR; + break; // break out of for loop + + } else if (strncmp(header, check, 4) != 0) { + message.str(""); message << "Archon command-reply mismatch reading " << (frame_type==Camera::FRAME_RAW?"raw ":"image ") + << " data. header=" << header << " check=" << check; + this->camera.log_error( function, message.str() ); + error = ERROR; + break; // break out of for loop + } + + // Read the frame contents + // + bytesread = 0; + do { + toread = BLOCK_LEN - bytesread; + if ( (retval=this->archon.Read(ptr_image, (size_t)toread)) > 0 ) { + bytesread += retval; // this will get zeroed after each block + totalbytesread += retval; // this won't (used only for info purposes) + std::cerr << std::setw(10) << totalbytesread << "\b\b\b\b\b\b\b\b\b\b"; + ptr_image += retval; // advance pointer } + } while (bytesread < BLOCK_LEN); - return error; + } // end of loop: for (block=0; block lock(this->archon_mutex); + this->archon_busy = false; + this->archon_mutex.unlock(); + + std::cerr << std::setw(10) << totalbytesread << " complete\n"; // display progress on same line of std err + + // If we broke out of the for loop for an error then report incomplete read + // + if ( error==ERROR || block < bufblocks) { + message.str(""); message << "incomplete frame read " << std::dec + << totalbytesread << " bytes: " << block << " of " << bufblocks << " 1024-byte blocks"; + logwrite( function, message.str() ); } - /**************** Archon::Interface::write_frame ****************************/ + // Unlock the frame buffer + // + if (error == NO_ERROR) error = this->archon_cmd(UNLOCK); + // On success, write the value to the log and return + // + if (error == NO_ERROR) { + message.str(""); message << "successfully read " << std::dec << totalbytesread << (frame_type==Camera::FRAME_RAW?" raw":" image") + << " bytes (0x" << std::uppercase << std::hex << bufblocks << " blocks) from Archon controller"; + logwrite(function, message.str()); - /**************** Archon::Interface::write_raw ******************************/ - /** - * @fn write_raw - * @brief write raw 16 bit data to a FITS file - * @param none - * @return ERROR or NO_ERROR - * - */ - long Interface::write_raw() { - std::string function = "Archon::Interface::write_raw"; - std::stringstream message; + } else { + // Throw an error for any other errors + logwrite( function, "ERROR: reading Archon camera data to memory!" ); + } + return error; + } + /**************** Archon::Interface::read_frame *****************************/ + + + /**************** Archon::Interface::write_frame ****************************/ + /** + * @fn write_frame + * @brief creates a FITS_file object to write the image_data buffer to disk + * @param none + * @return ERROR or NO_ERROR + * + * A FITS_file object is created here to write the data. This object MUST remain + * valid while any (all) threads are writing data, so the write_data function + * will keep track of threads so that it doesn't terminate until all of its + * threads terminate. + * + * The camera_info class was copied into fits_info when the exposure was started, //TODO I've un-done this. + * so use fits_info from here on out. //TODO Don't use fits_info right now. + * //TODO Only using camera_info + */ + long Interface::write_frame() { + std::string function = "Archon::Interface::write_frame"; + std::stringstream message; + uint32_t *cbuf32; //!< used to cast char buf into 32 bit int + uint16_t *cbuf16; //!< used to cast char buf into 16 bit int + int16_t *cbuf16s; //!< used to cast char buf into 16 bit int + long error=NO_ERROR; + + if ( ! this->modeselected ) { + this->camera.log_error( function, "no mode selected" ); + return ERROR; + } - unsigned short *cbuf16; //!< used to cast char buf into 16 bit int +// message.str(""); message << "writing " << this->fits_info.bitpix << "-bit data from memory to disk"; //TODO + message.str(""); message << "writing " << this->camera_info.bitpix << "-bit data from memory to disk"; + logwrite(function, message.str()); + + // The Archon sends four 8-bit numbers per pixel. To convert this into something usable, + // cast the image buffer into integers. Handled differently depending on bits per pixel. + // + switch (this->camera_info.bitpix) { + + // convert four 8-bit values into a 32-bit value and scale by 2^16 + // + case 32: { + cbuf32 = (uint32_t *)this->image_data; // cast here to 32b - // Cast the image buffer of chars into integers to convert four 8-bit values - // into a 16-bit value + // Write each amplifier as a separate extension // - cbuf16 = (unsigned short *) this->image_data; + if ( this->camera.cubeamps() ) { + float *fext = nullptr; - fitsfile *FP = nullptr; - int status = 0; - int naxes = 2; - long axes[2]; - long firstele = 1; - long nelements; + for ( int ext=0; ext < (int)this->camera_info.amp_section.size(); ext++ ) { + try { + // get the coordinates of this amplifier extension + // + long x1 = this->camera_info.amp_section.at(ext).at(0); + long x2 = this->camera_info.amp_section.at(ext).at(1); + long y1 = this->camera_info.amp_section.at(ext).at(2); + long y2 = this->camera_info.amp_section.at(ext).at(3); + + // assign this amplifier section as the region of interest + // + this->camera_info.region_of_interest[0] = x1; + this->camera_info.region_of_interest[1] = x2; + this->camera_info.region_of_interest[2] = y1; + this->camera_info.region_of_interest[3] = y2; + + // This call to set_axes() is to set the axis_pixels, axes, and section_size, + // needed for the FITS writer + // + error = this->camera_info.set_axes(); + + #ifdef LOGLEVEL_DEBUG + message.str(""); message << "[DEBUG] x1=" << x1 << " x2=" << x2 << " y1=" << y1 << " y2=" << y2; + logwrite( function, message.str() ); + message.str(""); message << "[DEBUG] axes[0]=" << this->camera_info.axes[0] << " axes[1]=" << this->camera_info.axes[1]; + logwrite( function, message.str() ); + #endif - axes[0] = this->camera_info.axes[0]; - axes[1] = this->camera_info.axes[1]; + // allocate the number of pixels needed for this amplifier extension + // + long ext_size = (x2-x1+1) * (y2-y1+1); + fext = new float[ ext_size ]; - nelements = axes[0] * axes[1]; + #ifdef LOGLEVEL_DEBUG + message.str(""); message << "[DEBUG] allocated " << ext_size << " pixels for extension " << this->camera_info.extension+1; + logwrite( function, message.str() ); + #endif + + // copy this amplifier from the main cbuf32, + // at the same time right-shift the requested number of bits + // + long pix=0; + long ncols=this->camera_info.detector_pixels[0]; // PIXELCOUNT + for ( long row=y1-1; row> this->n_hdrshift ); + } + } - // create fits file - // - if (this->camera_info.extension == 0) { -#ifdef LOGLEVEL_DEBUG - logwrite(function, "[DEBUG] creating fits file with cfitsio"); -#endif - if (fits_create_file(&FP, this->camera_info.fits_name.c_str(), &status)) { - message.str(""); - message << "cfitsio error " << status << " creating FITS file " << this->camera_info.fits_name; - this->camera.log_error(function, message.str()); - return ERROR; + #ifdef LOGLEVEL_DEBUG + message.str(""); message << "[DEBUG] calling fits_file.write_image( ) for extension " << this->camera_info.extension+1; + logwrite( function, message.str() ); + #endif + + error = this->fits_file.write_image(fext, this->camera_info); // write the image to disk + this->camera_info.extension++; // increment extension for cubes + if ( fext != nullptr ) { delete [] fext; fext=nullptr; } // dynamic object not automatic so must be destroyed + + } catch( std::out_of_range & ) { + message.str(""); message << "ERROR: " << ext << " is a bad extension number"; + logwrite( function, message.str() ); + if ( fext != nullptr ) { delete [] fext; fext=nullptr; } // dynamic object not automatic so must be destroyed + return ERROR; } + } + // end if this->camera.cubeamps() + } else { -#ifdef LOGLEVEL_DEBUG - logwrite(function, "[DEBUG] opening fits file with cfitsio"); - message.str(""); message << "[DEBUG] file=" << this->camera_info.fits_name << " extension=" << this->camera_info.extension - << " bitpix=" << this->camera_info.bitpix; - logwrite(function, message.str()); -#endif - if (fits_open_file(&FP, this->camera_info.fits_name.c_str(), READWRITE, &status)) { - message.str(""); - message << "cfitsio error " << status << " opening FITS file " << this->camera_info.fits_name; - this->camera.log_error(function, message.str()); - return ERROR; + // Write all amplifiers to the same extension + // + float *fbuf = nullptr; +// fbuf = new float[ this->fits_info.section_size ]; // allocate a float buffer of same number of pixels for scaling //TODO + fbuf = new float[ this->camera_info.section_size ]; // allocate a float buffer of same number of pixels for scaling + +// for (long pix=0; pix < this->fits_info.section_size; pix++) //TODO + for (long pix=0; pix < this->camera_info.section_size; pix++) { + fbuf[pix] = (float) ( cbuf32[pix] >> this->n_hdrshift ); // right shift the requested number of bits } - } - // create image - // - logwrite(function, "create image"); - message.str(""); - message << "axes=" << axes[0] << " " << axes[1]; - logwrite(function, message.str()); - if (fits_create_img(FP, USHORT_IMG, naxes, axes, &status)) { - message.str(""); - message << "fitsio error " << status << " creating FITS image for " << this->camera_info.fits_name; - this->camera.log_error(function, message.str()); - return ERROR; +// error = fits_file.write_image(fbuf, this->fits_info); // write the image to disk //TODO + error = this->fits_file.write_image(fbuf, this->camera_info); // write the image to disk + if ( error != NO_ERROR ) { this->camera.log_error( function, "writing 32-bit image to disk" ); } + delete [] fbuf; } + break; + } - // supplemental header keywords - // - fits_write_key(FP, TSTRING, "MODE", &this->camera_info.current_observing_mode, "observing mode", &status); - - // write HDU - // - logwrite(function, "write HDU"); - if (fits_write_img(FP, TUSHORT, firstele, nelements, cbuf16, &status)) { - message.str(""); - message << "fitsio error " << status << " writing FITS image HDU to " << this->camera_info.fits_name; - this->camera.log_error(function, message.str()); - return ERROR; - } + // convert four 8-bit values into 16 bit values + // + case 16: { + if (this->camera_info.datatype == USHORT_IMG) { // raw + cbuf16 = (uint16_t *)this->image_data; // cast to 16b unsigned int +// error = fits_file.write_image(cbuf16, this->fits_info); // write the image to disk //TODO + error = this->fits_file.write_image(cbuf16, this->camera_info); // write the image to disk + if ( error != NO_ERROR ) { this->camera.log_error( function, "writing 16-bit raw image to disk" ); } + + } else if (this->camera_info.datatype == SHORT_IMG) { + cbuf16s = (int16_t *)this->image_data; // cast to 16b signed int + int16_t *ibuf = nullptr; + ibuf = new int16_t[ this->camera_info.section_size ]; + for (long pix=0; pix < this->camera_info.section_size; pix++) { + ibuf[pix] = cbuf16s[pix] - 32768; // subtract 2^15 from every pixel + } + error = this->fits_file.write_image(ibuf, this->camera_info); // write the image to disk + if ( error != NO_ERROR ) { this->camera.log_error( function, "writing 16-bit image to disk" ); } + delete [] ibuf; - // close file - // - logwrite(function, "close file"); - if (fits_close_file(FP, &status)) { - message.str(""); - message << "fitsio error " << status << " closing fits file " << this->camera_info.fits_name; - this->camera.log_error(function, message.str()); - return ERROR; + } else { + message.str(""); message << "unsupported 16 bit datatype " << this->camera_info.datatype; + this->camera.log_error( function, message.str() ); + error = ERROR; } + break; + } - return NO_ERROR; + // shouldn't happen + // + default: +// message.str(""); message << "unrecognized bits per pixel: " << this->fits_info.bitpix; //TODO + message.str(""); message << "unrecognized bits per pixel: " << this->camera_info.bitpix; + this->camera.log_error( function, message.str() ); + error = ERROR; + break; } - /**************** Archon::Interface::write_raw ******************************/ + // Things to do after successful write + // + if ( error == NO_ERROR ) { + if ( this->camera.datacube() ) { + this->camera_info.extension++; // increment extension for cubes + message.str(""); message << "DATACUBE:" << this->camera_info.extension << " " << ( error==NO_ERROR ? "COMPLETE" : "ERROR" ); + this->camera.async.enqueue( message.str() ); + error == NO_ERROR ? logwrite( function, message.str() ) : this->camera.log_error( function, message.str() ); + } + logwrite(function, "frame write complete"); + } else { + logwrite( function, "ERROR: writing image" ); + } - /**************** Archon::Interface::write_config_key ***********************/ - /** - * @fn write_config_key - * @brief write a configuration KEY=VALUE pair to the Archon controller - * @param key - * @param newvalue - * @return ERROR or NO_ERROR - * - */ - long Interface::write_config_key(const char *key, const char *newvalue, bool &changed) { - std::string function = "Archon::Interface::write_config_key"; - std::stringstream message, sscmd; - long error = NO_ERROR; + return error; + } + /**************** Archon::Interface::write_frame ****************************/ - if (key == nullptr || newvalue == nullptr) { - error = ERROR; - this->camera.log_error(function, "key|value cannot have NULL"); - } else if (this->configmap.find(key) == this->configmap.end()) { - error = ERROR; - message.str(""); - message << "requested key " << key << " not found in configmap"; - this->camera.log_error(function, message.str()); - } else if (this->configmap[key].value == newvalue) { - // If no change in value then don't send the command - error = NO_ERROR; - message.str(""); - message << "config key " << key << "=" << newvalue << " not written: no change in value"; - logwrite(function, message.str()); - } else { - /** - * Format and send the Archon WCONFIG command - * to write the KEY=VALUE pair to controller memory - */ - sscmd << "WCONFIG" - << std::uppercase << std::setfill('0') << std::setw(4) << std::hex - << this->configmap[key].line - << key - << "=" - << newvalue; - message.str(""); - message << "sending: archon_cmd(" << sscmd.str() << ")"; - logwrite(function, message.str()); - error = this->archon_cmd((char *) sscmd.str().c_str()); // send the WCONFIG command here - if (error == NO_ERROR) { - this->configmap[key].value = newvalue; // save newvalue in the STL map - changed = true; - } else { - message.str(""); - message << "ERROR: config key=value: " << key << "=" << newvalue << " not written"; - logwrite(function, message.str()); - } - } - return error; - } - long Interface::write_config_key(const char *key, int newvalue, bool &changed) { - std::stringstream newvaluestr; - newvaluestr << newvalue; - return (write_config_key(key, newvaluestr.str().c_str(), changed)); - } + /**************** Archon::Interface::write_raw ******************************/ + /** + * @fn write_raw + * @brief write raw 16 bit data to a FITS file + * @param none + * @return ERROR or NO_ERROR + * + */ + long Interface::write_raw() { + std::string function = "Archon::Interface::write_raw"; + std::stringstream message; - /**************** Archon::Interface::write_config_key ***********************/ + unsigned short *cbuf16; //!< used to cast char buf into 16 bit int + // Cast the image buffer of chars into integers to convert four 8-bit values + // into a 16-bit value + // + cbuf16 = (unsigned short *)this->image_data; - /**************** Archon::Interface::write_parameter ************************/ - /** - * @fn write_parameter - * @brief write a parameter to the Archon configuration memory - * @param paramname - * @param newvalue - * @return NO_ERROR or ERROR - * - * After writing a parameter, requires an APPLYALL or LOADPARAMS command - * - */ - long Interface::write_parameter(const char *paramname, const char *newvalue, bool &changed) { - std::string function = "Archon::Interface::write_parameter"; - std::stringstream message, sscmd; - long error = NO_ERROR; + fitsfile *FP = nullptr; + int status = 0; + int naxes = 2; + long axes[2]; + long firstele = 1; + long nelements; -#ifdef LOGLEVEL_DEBUG - message.str(""); message << "[DEBUG] paramname=" << paramname << " value=" << newvalue; - logwrite( function, message.str() ); -#endif + axes[0] = this->camera_info.axes[0]; + axes[1] = this->camera_info.axes[1]; - if (paramname == nullptr || newvalue == nullptr) { - error = ERROR; - this->camera.log_error(function, "paramname|value cannot have NULL"); - } else if (this->parammap.find(paramname) == this->parammap.end()) { - error = ERROR; - message.str(""); - message << "parameter \"" << paramname << "\" not found in parammap"; - this->camera.log_error(function, message.str()); - } + nelements = axes[0] * axes[1]; - /** - * If no change in value then don't send the command - */ - if (error == NO_ERROR && this->parammap[paramname].value == newvalue) { - error = NO_ERROR; - message.str(""); - message << "parameter " << paramname << "=" << newvalue << " not written: no change in value"; - logwrite(function, message.str()); - } else if (error == NO_ERROR) { - /** - * Format and send the Archon command WCONFIGxxxxttt...ttt - * which writes the text ttt...ttt to configuration line xxx (hex) - * to controller memory. - */ - sscmd << "WCONFIG" - << std::uppercase << std::setfill('0') << std::setw(4) << std::hex - << this->parammap[paramname].line - << this->parammap[paramname].key - << "=" - << this->parammap[paramname].name - << "=" - << newvalue; - message.str(""); - message << "sending archon_cmd(" << sscmd.str() << ")"; - logwrite(function, message.str()); - error = this->archon_cmd((char *) sscmd.str().c_str()); // send the WCONFIG command here - if (error == NO_ERROR) { - this->parammap[paramname].value = newvalue; // save newvalue in the STL map - changed = true; - } else logwrite(function, "ERROR: sending WCONFIG command"); - } + // create fits file + // + if (this->camera_info.extension == 0) { + #ifdef LOGLEVEL_DEBUG + logwrite(function, "[DEBUG] creating fits file with cfitsio"); + #endif + if (fits_create_file( &FP, this->camera_info.fits_name.c_str(), &status ) ) { + message.str(""); + message << "cfitsio error " << status << " creating FITS file " << this->camera_info.fits_name; + this->camera.log_error( function, message.str() ); + return ERROR; + } - return error; + } else { + #ifdef LOGLEVEL_DEBUG + logwrite(function, "[DEBUG] opening fits file with cfitsio"); + message.str(""); message << "[DEBUG] file=" << this->camera_info.fits_name << " extension=" << this->camera_info.extension + << " bitpix=" << this->camera_info.bitpix; + logwrite(function, message.str()); + #endif + if (fits_open_file( &FP, this->camera_info.fits_name.c_str(), READWRITE, &status ) ) { + message.str(""); + message << "cfitsio error " << status << " opening FITS file " << this->camera_info.fits_name; + this->camera.log_error( function, message.str() ); + return ERROR; + } } - long Interface::write_parameter(const char *paramname, int newvalue, bool &changed) { - std::stringstream newvaluestr; - newvaluestr << newvalue; - return (write_parameter(paramname, newvaluestr.str().c_str(), changed)); + // create image + // + logwrite(function, "create image"); + message.str(""); message << "axes=" << axes[0] << " " << axes[1]; + logwrite(function, message.str()); + if ( fits_create_img( FP, USHORT_IMG, naxes, axes, &status) ) { + message.str(""); + message << "fitsio error " << status << " creating FITS image for " << this->camera_info.fits_name; + this->camera.log_error( function, message.str() ); + return ERROR; } - long Interface::write_parameter(const char *paramname, const char *newvalue) { - bool dontcare = false; - return (write_parameter(paramname, newvalue, dontcare)); - } + // supplemental header keywords + // + fits_write_key( FP, TSTRING, "MODE", &this->camera_info.current_observing_mode, "observing mode", &status ); - long Interface::write_parameter(const char *paramname, int newvalue) { - bool dontcare = false; - std::stringstream newvaluestr; - newvaluestr << newvalue; - return (write_parameter(paramname, newvaluestr.str().c_str(), dontcare)); + // write HDU + // + logwrite(function, "write HDU"); + if ( fits_write_img( FP, TUSHORT, firstele, nelements, cbuf16, &status) ) { + message.str(""); + message << "fitsio error " << status << " writing FITS image HDU to " << this->camera_info.fits_name; + this->camera.log_error( function, message.str() ); + return ERROR; } - /**************** Archon::Interface::write_parameter ************************/ + // close file + // + logwrite(function, "close file"); + if ( fits_close_file( FP, &status ) ) { + message.str(""); + message << "fitsio error " << status << " closing fits file " << this->camera_info.fits_name; + this->camera.log_error( function, message.str() ); + return ERROR; + } + return NO_ERROR; + } + /**************** Archon::Interface::write_raw ******************************/ + + + /**************** Archon::Interface::write_config_key ***********************/ + /** + * @fn write_config_key + * @brief write a configuration KEY=VALUE pair to the Archon controller + * @param key + * @param newvalue + * @return ERROR or NO_ERROR + * + */ + long Interface::write_config_key( const char *key, const char *newvalue, bool &changed ) { + std::string function = "Archon::Interface::write_config_key"; + std::stringstream message, sscmd; + long error=NO_ERROR; + + if ( key==nullptr || newvalue==nullptr ) { + error = ERROR; + this->camera.log_error( function, "key|value cannot have NULL" ); + + } else if ( this->configmap.find(key) == this->configmap.end() ) { + error = ERROR; + message.str(""); message << "requested key " << key << " not found in configmap"; + this->camera.log_error( function, message.str() ); + + } else if ( this->configmap[key].value == newvalue ) { + // If no change in value then don't send the command + error = NO_ERROR; + message.str(""); message << "config key " << key << "=" << newvalue << " not written: no change in value"; + logwrite(function, message.str()); - /**************** Archon::Interface::get_configmap_value ********************/ - /** - * @fn get_configmap_value - * @brief get the VALUE from configmap for a givenn KEY and assign to a variable - * @param string key_in is the KEY - * @param T& value_out reference to variable to contain the VALUE - * @return ERROR or NO_ERROR - * - * This is a template class function so the &value_out reference can be any type. - * If the key_in KEY is not found then an error message is logged and ERROR is - * returned, otherwise the VALUE associated with key_in is assigned to &value_out, - * and NO_ERROR is returned. - * + } else { + /** + * Format and send the Archon WCONFIG command + * to write the KEY=VALUE pair to controller memory */ - template - long Interface::get_configmap_value(std::string key_in, T &value_out) { - std::string function = "Archon::Interface::get_configmap_value"; - std::stringstream message; - - if (this->configmap.find(key_in) != this->configmap.end()) { - std::istringstream(this->configmap[key_in].value) >> value_out; -#ifdef LOGLEVEL_DEBUG - message.str(""); message << "[DEBUG] key=" << key_in << " value=" << value_out << " line=" << this->configmap[key_in].line; + sscmd << "WCONFIG" + << std::uppercase << std::setfill('0') << std::setw(4) << std::hex + << this->configmap[key].line + << key + << "=" + << newvalue; + message.str(""); message << "sending: archon_cmd(" << sscmd.str() << ")"; logwrite(function, message.str()); -#endif - return NO_ERROR; - } else { - message.str(""); - message << "requested key: " << key_in << " not found in configmap"; - this->camera.log_error(function, message.str()); - return ERROR; - } + error = this->archon_cmd((char *)sscmd.str().c_str()); // send the WCONFIG command here + if (error==NO_ERROR) { + this->configmap[key].value = newvalue; // save newvalue in the STL map + changed = true; + + } else { + message.str(""); message << "ERROR: config key=value: " << key << "=" << newvalue << " not written"; + logwrite( function, message.str() ); + } } + return error; + } + + long Interface::write_config_key( const char *key, int newvalue, bool &changed ) { + std::stringstream newvaluestr; + newvaluestr << newvalue; + return ( write_config_key(key, newvaluestr.str().c_str(), changed) ); + } + /**************** Archon::Interface::write_config_key ***********************/ + + + /**************** Archon::Interface::write_parameter ************************/ + /** + * @fn write_parameter + * @brief write a parameter to the Archon configuration memory + * @param paramname + * @param newvalue + * @return NO_ERROR or ERROR + * + * After writing a parameter, requires an APPLYALL or LOADPARAMS command + * + */ + long Interface::write_parameter( const char *paramname, const char *newvalue, bool &changed ) { + std::string function = "Archon::Interface::write_parameter"; + std::stringstream message, sscmd; + long error=NO_ERROR; + + #ifdef LOGLEVEL_DEBUG + message.str(""); message << "[DEBUG] paramname=" << paramname << " value=" << newvalue; + logwrite( function, message.str() ); + #endif - /**************** Archon::Interface::get_configmap_value ********************/ + if ( paramname==nullptr || newvalue==nullptr ) { + error = ERROR; + this->camera.log_error( function, "paramname|value cannot have NULL" ); + } else if ( this->parammap.find(paramname) == this->parammap.end() ) { + error = ERROR; + message.str(""); message << "parameter \"" << paramname << "\" not found in parammap"; + this->camera.log_error( function, message.str() ); + } - /**************** Archon::Interface::add_filename_key ***********************/ /** - * @fn add_filename_key - * @brief adds the current filename to the systemkeys database - * @param none - * @return none - * + * If no change in value then don't send the command */ - void Interface::add_filename_key() { - std::stringstream keystr; - int loc = this->camera_info.fits_name.find_last_of('/'); - std::string filename; - filename = this->camera_info.fits_name.substr(loc + 1); - keystr << "FILENAME=" << filename << "// this filename"; - this->systemkeys.addkey(keystr.str()); + if ( error==NO_ERROR && this->parammap[paramname].value == newvalue ) { + error = NO_ERROR; + message.str(""); message << "parameter " << paramname << "=" << newvalue << " not written: no change in value"; + logwrite(function, message.str()); + } else if (error==NO_ERROR) { + /** + * Format and send the Archon command WCONFIGxxxxttt...ttt + * which writes the text ttt...ttt to configuration line xxx (hex) + * to controller memory. + */ + sscmd << "WCONFIG" + << std::uppercase << std::setfill('0') << std::setw(4) << std::hex + << this->parammap[paramname].line + << this->parammap[paramname].key + << "=" + << this->parammap[paramname].name + << "=" + << newvalue; + message.str(""); message << "sending archon_cmd(" << sscmd.str() << ")"; + logwrite(function, message.str()); + error=this->archon_cmd((char *)sscmd.str().c_str()); // send the WCONFIG command here + if ( error == NO_ERROR ) { + this->parammap[paramname].value = newvalue; // save newvalue in the STL map + changed = true; + + } else logwrite( function, "ERROR: sending WCONFIG command" ); + } + + return error; + } + + long Interface::write_parameter( const char *paramname, int newvalue, bool &changed ) { + std::stringstream newvaluestr; + newvaluestr << newvalue; + return( write_parameter(paramname, newvaluestr.str().c_str(), changed) ); + } + + long Interface::write_parameter( const char *paramname, const char *newvalue ) { + bool dontcare = false; + return( write_parameter(paramname, newvalue, dontcare) ); + } + + long Interface::write_parameter( const char *paramname, int newvalue ) { + bool dontcare = false; + std::stringstream newvaluestr; + newvaluestr << newvalue; + return( write_parameter(paramname, newvaluestr.str().c_str(), dontcare) ); + } + /**************** Archon::Interface::write_parameter ************************/ + + + /**************** Archon::Interface::get_configmap_value ********************/ + /** + * @fn get_configmap_value + * @brief get the VALUE from configmap for a givenn KEY and assign to a variable + * @param string key_in is the KEY + * @param T& value_out reference to variable to contain the VALUE + * @return ERROR or NO_ERROR + * + * This is a template class function so the &value_out reference can be any type. + * If the key_in KEY is not found then an error message is logged and ERROR is + * returned, otherwise the VALUE associated with key_in is assigned to &value_out, + * and NO_ERROR is returned. + * + */ + template + long Interface::get_configmap_value(std::string key_in, T& value_out) { + std::string function = "Archon::Interface::get_configmap_value"; + std::stringstream message; + + if ( this->configmap.find(key_in) != this->configmap.end() ) { + std::istringstream( this->configmap[key_in].value ) >> value_out; + #ifdef LOGLEVEL_DEBUG + message.str(""); message << "[DEBUG] key=" << key_in << " value=" << value_out << " line=" << this->configmap[key_in].line; + logwrite(function, message.str()); + #endif + return NO_ERROR; + + } else { + message.str(""); + message << "requested key: " << key_in << " not found in configmap"; + this->camera.log_error( function, message.str() ); + return ERROR; + } + } + /**************** Archon::Interface::get_configmap_value ********************/ + + + /**************** Archon::Interface::add_filename_key ***********************/ + /** + * @fn add_filename_key + * @brief adds the current filename to the systemkeys database + * @param none + * @return none + * + */ + void Interface::add_filename_key() { + std::stringstream keystr; + int loc = this->camera_info.fits_name.find_last_of( '/' ); + std::string filename; + filename = this->camera_info.fits_name.substr( loc+1 ); + keystr << "FILENAME=" << filename << "// this filename"; + this->systemkeys.addkey( keystr.str() ); + } + /**************** Archon::Interface::add_filename_key ***********************/ + + + /**************** Archon::Interface::expose *********************************/ + /** + * @fn expose + * @brief initiate an exposure + * @param nseq_in string, if set becomes the number of sequences + * @return ERROR or NO_ERROR + * + * This function does the following before returning successful completion: + * 1) trigger an Archon exposure by setting the EXPOSE parameter = nseq_in + * 2) wait for exposure delay + * 3) wait for readout into Archon frame buffer + * 4) read frame buffer from Archon to host + * 5) write frame to disk + * + * Note that this assumes that the Archon ACF has been programmed to automatically + * read out the detector into the frame buffer after an exposure. + * + */ + long Interface::expose(std::string nseq_in) { + std::string function = "Archon::Interface::expose"; + std::stringstream message; + long error = NO_ERROR; + std::string nseqstr; + int nseq; + + std::string mode = this->camera_info.current_observing_mode; // local copy for convenience + + if ( ! this->modeselected ) { + this->camera.log_error( function, "no mode selected" ); + return ERROR; } - /**************** Archon::Interface::add_filename_key ***********************/ - + // When switching from cubeamps=true to cubeamps=false, + // simply reset the mode to the current mode in order to + // reset the image size. + // + // This will need to be revisited once ROI is implemented. // TODO + // + if ( !this->camera.cubeamps() && ( this->lastcubeamps != this->camera.cubeamps() ) ) { + message.str(""); message << "detected change in cubeamps -- resetting camera mode to " << mode; + logwrite( function, message.str() ); + this->set_camera_mode( mode ); + } - /**************** Archon::Interface::expose *********************************/ - /** - * @fn expose - * @brief initiate an exposure - * @param nseq_in string, if set becomes the number of sequences - * @return ERROR or NO_ERROR - * - * This function does the following before returning successful completion: - * 1) trigger an Archon exposure by setting the EXPOSE parameter = nseq_in - * 2) wait for exposure delay - * 3) wait for readout into Archon frame buffer - * 4) read frame buffer from Archon to host - * 5) write frame to disk - * - * Note that this assumes that the Archon ACF has been programmed to automatically - * read out the detector into the frame buffer after an exposure. - * - */ - long Interface::expose(std::string nseq_in) { - std::string function = "Archon::Interface::expose"; - std::stringstream message; - long error = NO_ERROR; - std::string nseqstr; - int nseq; + // exposeparam is set by the configuration file + // check to make sure it was set, or else expose won't work + // + if (this->exposeparam.empty()) { + message.str(""); message << "EXPOSE_PARAM not defined in configuration file " << this->config.filename; + this->camera.log_error( function, message.str() ); + return ERROR; + } - std::string mode = this->camera_info.current_observing_mode; // local copy for convenience + // If the exposure time or longexposure mode were never set then read them from the Archon. + // This ensures that, if the client doesn't set these values then the server will have the + // same default values that the ACF has, rather than hope that the ACF programmer picks + // their defaults to match mine. + // + if ( this->camera_info.exposure_time == -1 ) { + logwrite( function, "NOTICE:exptime has not been set--will read from Archon" ); + this->camera.async.enqueue( "NOTICE:exptime has not been set--will read from Archon" ); + + // read the Archon configuration memory + // + std::string etime; + if ( read_parameter( "exptime", etime ) != NO_ERROR ) { logwrite( function, "ERROR: reading \"exptime\" parameter from Archon" ); return ERROR; } + + // Tell the server these values + // + std::string retval; + if ( this->exptime( etime, retval ) != NO_ERROR ) { logwrite( function, "ERROR: setting exptime" ); return ERROR; } + } + if ( this->camera_info.exposure_factor == -1 || + this->camera_info.exposure_unit.empty() ) { + logwrite( function, "NOTICE:longexposure has not been set--will read from Archon" ); + this->camera.async.enqueue( "NOTICE:longexposure has not been set--will read from Archon" ); + + // read the Archon configuration memory + // + std::string lexp; + if ( read_parameter( "longexposure", lexp ) != NO_ERROR ) { logwrite( function, "ERROR: reading \"longexposure\" parameter from Archon" ); return ERROR; } + + // Tell the server these values + // + std::string retval; + if ( this->longexposure( lexp, retval ) != NO_ERROR ) { logwrite( function, "ERROR: setting longexposure" ); return ERROR; } + } - if (!this->modeselected) { - this->camera.log_error(function, "no mode selected"); - return ERROR; - } + // If nseq_in is not supplied then set nseq to 1. + // Add any pre-exposures onto the number of sequences. + // + if ( nseq_in.empty() ) { + nseq = 1 + this->camera_info.num_pre_exposures; + nseqstr = std::to_string( nseq ); + + } else { // sequence argument passed in + try { + nseq = std::stoi( nseq_in ) + this->camera_info.num_pre_exposures; // test that nseq_in is an integer + nseqstr = std::to_string( nseq ); // before trying to use it + + } catch (std::invalid_argument &) { + message.str(""); message << "unable to convert sequences: " << nseq_in << " to integer"; + this->camera.log_error( function, message.str() ); + return ERROR; + + } catch (std::out_of_range &) { + message.str(""); message << "sequences " << nseq_in << " outside integer range"; + this->camera.log_error( function, message.str() ); + return ERROR; + } + } - // When switching from cubeamps=true to cubeamps=false, - // simply reset the mode to the current mode in order to - // reset the image size. - // - // This will need to be revisited once ROI is implemented. // TODO - // - if (!this->camera.cubeamps() && (this->lastcubeamps != this->camera.cubeamps())) { - message.str(""); - message << "detected change in cubeamps -- resetting camera mode to " << mode; - logwrite(function, message.str()); - this->set_camera_mode(mode); - } + // Always initialize the extension number because someone could + // set datacube true and then send "expose" without a number. + // + this->camera_info.extension = 0; - // exposeparam is set by the configuration file - // check to make sure it was set, or else expose won't work - // - if (this->exposeparam.empty()) { - message.str(""); - message << "EXPOSE_PARAM not defined in configuration file " << this->config.filename; - this->camera.log_error(function, message.str()); - return ERROR; - } + error = this->get_frame_status(); // TODO is this needed here? - // If the exposure time or longexposure mode were never set then read them from the Archon. - // This ensures that, if the client doesn't set these values then the server will have the - // same default values that the ACF has, rather than hope that the ACF programmer picks - // their defaults to match mine. - // - if (this->camera_info.exposure_time == -1) { - logwrite(function, "NOTICE:exptime has not been set--will read from Archon"); - this->camera.async.enqueue("NOTICE:exptime has not been set--will read from Archon"); + if (error != NO_ERROR) { + logwrite( function, "ERROR: unable to get frame status" ); + return ERROR; + } + this->lastframe = this->frame.bufframen[this->frame.index]; // save the last frame number acquired (wait_for_readout will need this) - // read the Archon configuration memory - // - std::string etime; - if (read_parameter("exptime", etime) != NO_ERROR) { - logwrite(function, "ERROR: reading \"exptime\" parameter from Archon"); - return ERROR; - } + // initiate the exposure here + // + error = this->prep_parameter(this->exposeparam, nseqstr); + if (error == NO_ERROR) error = this->load_parameter(this->exposeparam, nseqstr); + if ( error != NO_ERROR ) { + logwrite( function, "ERROR: could not initiate exposure" ); + return error; + } - // Tell the server these values - // - std::string retval; - if (this->exptime(etime, retval) != NO_ERROR) { - logwrite(function, "ERROR: setting exptime"); - return ERROR; - } - } - if (this->camera_info.exposure_factor == -1 || - this->camera_info.exposure_unit.empty()) { - logwrite(function, "NOTICE:longexposure has not been set--will read from Archon"); - this->camera.async.enqueue("NOTICE:longexposure has not been set--will read from Archon"); + // get system time and Archon's timer after exposure starts + // start_timer is used to determine when the exposure has ended, in wait_for_exposure() + // + this->camera_info.start_time = get_timestamp(); // current system time formatted as YYYY-MM-DDTHH:MM:SS.sss + if ( this->get_timer(&this->start_timer) != NO_ERROR ) { // Archon internal timer (one tick=10 nsec) + logwrite( function, "ERROR: could not get start time" ); + return ERROR; + } + this->camera.set_fitstime(this->camera_info.start_time); // sets camera.fitstime (YYYYMMDDHHMMSS) used for filename + error=this->camera.get_fitsname(this->camera_info.fits_name); // assemble the FITS filename + if ( error != NO_ERROR ) { + logwrite( function, "ERROR: couldn't validate fits filename" ); + return error; + } - // read the Archon configuration memory - // - std::string lexp; - if (read_parameter("longexposure", lexp) != NO_ERROR) { - logwrite(function, "ERROR: reading \"longexposure\" parameter from Archon"); - return ERROR; - } + this->add_filename_key(); // add filename to system keys database - // Tell the server these values - // - std::string retval; - if (this->longexposure(lexp, retval) != NO_ERROR) { - logwrite(function, "ERROR: setting longexposure"); - return ERROR; - } - } + logwrite(function, "exposure started"); - // If nseq_in is not supplied then set nseq to 1. - // Add any pre-exposures onto the number of sequences. - // - if (nseq_in.empty()) { - nseq = 1 + this->camera_info.num_pre_exposures; - nseqstr = std::to_string(nseq); - } else { - // sequence argument passed in - try { - nseq = std::stoi(nseq_in) + this->camera_info.num_pre_exposures; // test that nseq_in is an integer - nseqstr = std::to_string(nseq); // before trying to use it - } catch (std::invalid_argument &) { - message.str(""); - message << "unable to convert sequences: " << nseq_in << " to integer"; - this->camera.log_error(function, message.str()); - return ERROR; - } catch (std::out_of_range &) { - message.str(""); - message << "sequences " << nseq_in << " outside integer range"; - this->camera.log_error(function, message.str()); - return ERROR; - } - } + this->camera_info.systemkeys.keydb = this->systemkeys.keydb; // copy the systemkeys database object into camera_info - // Always initialize the extension number because someone could - // set datacube true and then send "expose" without a number. - // - this->camera_info.extension = 0; + if (this->camera.writekeys_when=="before") this->copy_keydb(); // copy the ACF and userkeys database into camera_info - error = this->get_frame_status(); // TODO is this needed here? + // If mode is not "RAW" but RAWENABLE is set then we're going to require a multi-extension data cube, + // one extension for the image and a separate extension for raw data. + // + if ( (mode != "RAW") && (this->modemap[mode].rawenable) ) { + if ( !this->camera.datacube() ) { // if datacube not already set then it must be overridden here + this->camera.async.enqueue( "NOTICE:override datacube true" ); // let everyone know + logwrite( function, "NOTICE:override datacube true" ); + this->camera.datacube(true); + } + this->camera_info.extension = 0; + } - if (error != NO_ERROR) { - logwrite(function, "ERROR: unable to get frame status"); - return ERROR; - } - this->lastframe = this->frame.bufframen[this->frame.index]; - // save the last frame number acquired (wait_for_readout will need this) + // Save the datacube state in camera_info so that the FITS writer can know about it + // + this->camera_info.iscube = this->camera.datacube(); - // initiate the exposure here - // - error = this->prep_parameter(this->exposeparam, nseqstr); - if (error == NO_ERROR) error = this->load_parameter(this->exposeparam, nseqstr); - if (error != NO_ERROR) { - logwrite(function, "ERROR: could not initiate exposure"); - return error; - } + // Open the FITS file now for cubes + // + if ( this->camera.datacube() && !this->camera.cubeamps() ) { + #ifdef LOGLEVEL_DEBUG + logwrite( function, "[DEBUG] opening fits file for multi-exposure sequence data cube" ); + #endif + error = this->fits_file.open_file(this->camera.writekeys_when == "before", this->camera_info ); + if ( error != NO_ERROR ) { + this->camera.log_error( function, "couldn't open fits file" ); + return error; + } + } - // get system time and Archon's timer after exposure starts - // start_timer is used to determine when the exposure has ended, in wait_for_exposure() - // - this->camera_info.start_time = get_timestamp(); // current system time formatted as YYYY-MM-DDTHH:MM:SS.sss - if (this->get_timer(&this->start_timer) != NO_ERROR) { - // Archon internal timer (one tick=10 nsec) - logwrite(function, "ERROR: could not get start time"); - return ERROR; - } - this->camera.set_fitstime(this->camera_info.start_time); - // sets camera.fitstime (YYYYMMDDHHMMSS) used for filename - error = this->camera.get_fitsname(this->camera_info.fits_name); // assemble the FITS filename - if (error != NO_ERROR) { - logwrite(function, "ERROR: couldn't validate fits filename"); - return error; - } +// //TODO only use camera_info -- don't use fits_info -- is this OK? TO BE CONFIRMED +// this->fits_info = this->camera_info; // copy the camera_info class, to be given to fits writer //TODO - this->add_filename_key(); // add filename to system keys database +// this->lastframe = this->frame.bufframen[this->frame.index]; // save the last frame number acquired (wait_for_readout will need this) - logwrite(function, "exposure started"); + if (nseq > 1) { + message.str(""); message << "starting sequence of " << nseq << " frames. lastframe=" << this->lastframe; + logwrite(function, message.str()); + } - this->camera_info.systemkeys.keydb = this->systemkeys.keydb; - // copy the systemkeys database object into camera_info + // If not RAW mode then wait for Archon frame buffer to be ready, + // then read the latest ready frame buffer to the host. If this + // is a squence, then loop over all expected frames. + // + if ( mode != "RAW" ) { // If not raw mode then + int expcount = 0; // counter used only for tracking pre-exposures - if (this->camera.writekeys_when == "before") this->copy_keydb(); - // copy the ACF and userkeys database into camera_info + // + // -- MAIN SEQUENCE LOOP -- + // + while (nseq-- > 0) { - // If mode is not "RAW" but RAWENABLE is set then we're going to require a multi-extension data cube, - // one extension for the image and a separate extension for raw data. + // Wait for any pre-exposures, first the exposure delay then the readout, + // but then continue to the next because pre-exposures are not read from + // the Archon's buffer. // - if ((mode != "RAW") && (this->modemap[mode].rawenable)) { - if (!this->camera.datacube()) { - // if datacube not already set then it must be overridden here - this->camera.async.enqueue("NOTICE:override datacube true"); // let everyone know - logwrite(function, "NOTICE:override datacube true"); - this->camera.datacube(true); - } - this->camera_info.extension = 0; - } + if ( ++expcount <= this->camera_info.num_pre_exposures ) { - // Save the datacube state in camera_info so that the FITS writer can know about it - // - this->camera_info.iscube = this->camera.datacube(); + message.str(""); message << "pre-exposure " << expcount << " of " << this->camera_info.num_pre_exposures; + logwrite( function, message.str() ); - // Open the FITS file now for cubes - // - if (this->camera.datacube() && !this->camera.cubeamps()) { -#ifdef LOGLEVEL_DEBUG - logwrite( function, "[DEBUG] opening fits file for multi-exposure sequence data cube" ); -#endif - error = this->fits_file.open_file(this->camera.writekeys_when == "before", this->camera_info); - if (error != NO_ERROR) { - this->camera.log_error(function, "couldn't open fits file"); - return error; + if ( this->camera_info.exposure_time != 0 ) { // wait for the exposure delay to complete (if there is one) + error = this->wait_for_exposure(); + if ( error != NO_ERROR ) { + logwrite( function, "ERROR: waiting for pre-exposure" ); + return error; } - } - - // //TODO only use camera_info -- don't use fits_info -- is this OK? TO BE CONFIRMED - // this->fits_info = this->camera_info; // copy the camera_info class, to be given to fits writer //TODO + } - // this->lastframe = this->frame.bufframen[this->frame.index]; // save the last frame number acquired (wait_for_readout will need this) + error = this->wait_for_readout(); // Wait for the readout into frame buffer, + if ( error != NO_ERROR ) { + logwrite( function, "ERROR: waiting for pre-exposure readout" ); + return error; + } - if (nseq > 1) { - message.str(""); - message << "starting sequence of " << nseq << " frames. lastframe=" << this->lastframe; - logwrite(function, message.str()); + continue; } - // If not RAW mode then wait for Archon frame buffer to be ready, - // then read the latest ready frame buffer to the host. If this - // is a squence, then loop over all expected frames. + // Open a new FITS file for each frame when not using datacubes // - if (mode != "RAW") { - // If not raw mode then - int expcount = 0; // counter used only for tracking pre-exposures - - // - // -- MAIN SEQUENCE LOOP -- - // - while (nseq-- > 0) { - // Wait for any pre-exposures, first the exposure delay then the readout, - // but then continue to the next because pre-exposures are not read from - // the Archon's buffer. - // - if (++expcount <= this->camera_info.num_pre_exposures) { - message.str(""); - message << "pre-exposure " << expcount << " of " << this->camera_info.num_pre_exposures; - logwrite(function, message.str()); - - if (this->camera_info.exposure_time != 0) { - // wait for the exposure delay to complete (if there is one) - error = this->wait_for_exposure(); - if (error != NO_ERROR) { - logwrite(function, "ERROR: waiting for pre-exposure"); - return error; - } - } - - error = this->wait_for_readout(); // Wait for the readout into frame buffer, - if (error != NO_ERROR) { - logwrite(function, "ERROR: waiting for pre-exposure readout"); - return error; - } - - continue; - } - - // Open a new FITS file for each frame when not using datacubes - // -#ifdef LOGLEVEL_DEBUG + #ifdef LOGLEVEL_DEBUG message.str(""); message << "[DEBUG] datacube=" << this->camera.datacube() << " cubeamps=" << this->camera.cubeamps(); logwrite( function, message.str() ); -#endif - if (!this->camera.datacube() || this->camera.cubeamps()) { - this->camera_info.start_time = get_timestamp(); - // current system time formatted as YYYY-MM-DDTHH:MM:SS.sss - if (this->get_timer(&this->start_timer) != NO_ERROR) { - // Archon internal timer (one tick=10 nsec) - logwrite(function, "ERROR: could not get start time"); - return ERROR; - } - this->camera.set_fitstime(this->camera_info.start_time); - // sets camera.fitstime (YYYYMMDDHHMMSS) used for filename - error = this->camera.get_fitsname(this->camera_info.fits_name); // Assemble the FITS filename - if (error != NO_ERROR) { - logwrite(function, "ERROR: couldn't validate fits filename"); - return error; - } - this->add_filename_key(); // add filename to system keys database + #endif + if ( !this->camera.datacube() || this->camera.cubeamps() ) { + this->camera_info.start_time = get_timestamp(); // current system time formatted as YYYY-MM-DDTHH:MM:SS.sss + if ( this->get_timer(&this->start_timer) != NO_ERROR ) { // Archon internal timer (one tick=10 nsec) + logwrite( function, "ERROR: could not get start time" ); + return ERROR; + } + this->camera.set_fitstime(this->camera_info.start_time); // sets camera.fitstime (YYYYMMDDHHMMSS) used for filename + error=this->camera.get_fitsname(this->camera_info.fits_name); // Assemble the FITS filename + if ( error != NO_ERROR ) { + logwrite( function, "ERROR: couldn't validate fits filename" ); + return error; + } + this->add_filename_key(); // add filename to system keys database -#ifdef LOGLEVEL_DEBUG + #ifdef LOGLEVEL_DEBUG logwrite( function, "[DEBUG] reset extension=0 and opening new fits file" ); -#endif - // reset the extension number and open the fits file - // - this->camera_info.extension = 0; - error = this->fits_file.open_file( - this->camera.writekeys_when == "before", this->camera_info); - if (error != NO_ERROR) { - this->camera.log_error(function, "couldn't open fits file"); - return error; - } - } + #endif + // reset the extension number and open the fits file + // + this->camera_info.extension = 0; + error = this->fits_file.open_file( + this->camera.writekeys_when == "before", this->camera_info ); + if ( error != NO_ERROR ) { + this->camera.log_error( function, "couldn't open fits file" ); + return error; + } + } - if (this->camera_info.exposure_time != 0) { - // wait for the exposure delay to complete (if there is one) - error = this->wait_for_exposure(); - if (error != NO_ERROR) { - logwrite(function, "ERROR: waiting for exposure"); - return error; - } - } + if ( this->camera_info.exposure_time != 0 ) { // wait for the exposure delay to complete (if there is one) + error = this->wait_for_exposure(); + if ( error != NO_ERROR ) { + logwrite( function, "ERROR: waiting for exposure" ); + return error; + } + } - if (this->camera.writekeys_when == "after") this->copy_keydb(); - // copy the ACF and userkeys database into camera_info + if (this->camera.writekeys_when=="after") this->copy_keydb(); // copy the ACF and userkeys database into camera_info - error = this->wait_for_readout(); // Wait for the readout into frame buffer, + error = this->wait_for_readout(); // Wait for the readout into frame buffer, - if (error != NO_ERROR) { - logwrite(function, "ERROR: waiting for readout"); - this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info); - return error; - } + if ( error != NO_ERROR ) { + logwrite( function, "ERROR: waiting for readout" ); + this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info ); + return error; + } - error = read_frame(); // then read the frame buffer to host (and write file) when frame ready. - if (error != NO_ERROR) { - logwrite(function, "ERROR: reading frame buffer"); - this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info); - return error; - } + error = read_frame(); // then read the frame buffer to host (and write file) when frame ready. + if ( error != NO_ERROR ) { + logwrite( function, "ERROR: reading frame buffer" ); + this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info ); + return error; + } - // For non-sequence multiple exposures, including cubeamps, close the fits file here - // - if (!this->camera.datacube() || this->camera.cubeamps()) { - // Error or not, close the file. -#ifdef LOGLEVEL_DEBUG + // For non-sequence multiple exposures, including cubeamps, close the fits file here + // + if ( !this->camera.datacube() || this->camera.cubeamps() ) { // Error or not, close the file. + #ifdef LOGLEVEL_DEBUG logwrite( function, "[DEBUG] closing fits file (1)" ); -#endif - this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info); - // close the file when not using datacubes - this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" - - // ASYNC status message on completion of each file - // - message.str(""); - message << "FILE:" << this->camera_info.fits_name << " COMPLETE"; - this->camera.async.enqueue(message.str()); - logwrite(function, message.str()); - } - - if (error != NO_ERROR) break; - // should be impossible but don't try additional sequences if there were errors - } // end of sequence loop, while (nseq-- > 0) - } else if (mode == "RAW") { - error = this->get_frame_status(); // Get the current frame buffer status - if (error != NO_ERROR) { - logwrite(function, "ERROR: unable to get frame status"); - return ERROR; - } - error = this->camera.get_fitsname(this->camera_info.fits_name); // Assemble the FITS filename - if (error != NO_ERROR) { - logwrite(function, "ERROR: couldn't validate fits filename"); - return error; - } - this->add_filename_key(); // add filename to system keys database + #endif + this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info ); // close the file when not using datacubes + this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" - this->camera_info.systemkeys.keydb = this->systemkeys.keydb; - // copy the systemkeys database into camera_info + // ASYNC status message on completion of each file + // + message.str(""); message << "FILE:" << this->camera_info.fits_name << " COMPLETE"; + this->camera.async.enqueue( message.str() ); + logwrite( function, message.str() ); + } - this->copy_keydb(); // copy the ACF and userkeys databases into camera_info + if (error != NO_ERROR) break; // should be impossible but don't try additional sequences if there were errors - error = this->fits_file.open_file(this->camera.writekeys_when == "before", this->camera_info); - if (error != NO_ERROR) { - this->camera.log_error(function, "couldn't open fits file"); - return error; - } - error = read_frame(); // For raw mode just read immediately - this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info); - this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" - } + } // end of sequence loop, while (nseq-- > 0) - // for multi-exposure (non-cubeamp) cubes, close the FITS file now that they've all been written - // - if (this->camera.datacube() && !this->camera.cubeamps()) { -#ifdef LOGLEVEL_DEBUG - logwrite( function, "[DEBUG] closing fits file (2)" ); -#endif - this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info); - this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" + } else if ( mode == "RAW") { + error = this->get_frame_status(); // Get the current frame buffer status + if (error != NO_ERROR) { + logwrite( function, "ERROR: unable to get frame status" ); + return ERROR; + } + error = this->camera.get_fitsname( this->camera_info.fits_name ); // Assemble the FITS filename + if ( error != NO_ERROR ) { + logwrite( function, "ERROR: couldn't validate fits filename" ); + return error; + } + this->add_filename_key(); // add filename to system keys database - // ASYNC status message on completion of each file - // - message.str(""); - message << "FILE:" << this->camera_info.fits_name << " " << (error == NO_ERROR ? "COMPLETE" : "ERROR"); - this->camera.async.enqueue(message.str()); - error == NO_ERROR ? logwrite(function, message.str()) : this->camera.log_error(function, message.str()); - } + this->camera_info.systemkeys.keydb = this->systemkeys.keydb; // copy the systemkeys database into camera_info - // remember the cubeamps setting used for the last completed exposure - // TODO revisit once region-of-interest is implemented - // - this->lastcubeamps = this->camera.cubeamps(); + this->copy_keydb(); // copy the ACF and userkeys databases into camera_info - return (error); + error = this->fits_file.open_file(this->camera.writekeys_when == "before", this->camera_info ); + if ( error != NO_ERROR ) { + this->camera.log_error( function, "couldn't open fits file" ); + return error; + } + error = read_frame(); // For raw mode just read immediately + this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info ); + this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" } - /**************** Archon::Interface::expose *********************************/ - + // for multi-exposure (non-cubeamp) cubes, close the FITS file now that they've all been written + // + if ( this->camera.datacube() && !this->camera.cubeamps() ) { + #ifdef LOGLEVEL_DEBUG + logwrite( function, "[DEBUG] closing fits file (2)" ); + #endif + this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info ); + this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" + + // ASYNC status message on completion of each file + // + message.str(""); message << "FILE:" << this->camera_info.fits_name << " " << ( error==NO_ERROR ? "COMPLETE" : "ERROR" ); + this->camera.async.enqueue( message.str() ); + error == NO_ERROR ? logwrite( function, message.str() ) : this->camera.log_error( function, message.str() ); + } - /**************** Archon::Interface::hsetup ********************************/ - /** - * @fn hsetup - * @brief setup archon for h2rg - * @param NONE - * @return ERROR or NO_ERROR - * - * NOTE: this assumes LVDS is module 10 - * This function does the following: - * 1) Pulse low on MainResetB - * 2) sets output to Pad B and HIGHOHM - * - */ + // remember the cubeamps setting used for the last completed exposure + // TODO revisit once region-of-interest is implemented + // + this->lastcubeamps = this->camera.cubeamps(); + + return (error); + } + /**************** Archon::Interface::expose *********************************/ + + + /**************** Archon::Interface::hsetup ********************************/ + /** + * @fn hsetup + * @brief setup archon for h2rg + * @param NONE + * @return ERROR or NO_ERROR + * + * NOTE: this assumes LVDS is module 10 + * This function does the following: + * 1) Pulse low on MainResetB + * 2) sets output to Pad B and HIGHOHM + * + */ long Interface::hsetup() { std::string function = "Archon::Interface::hsetup"; std::stringstream message; @@ -4153,25 +3920,23 @@ namespace Archon { // H2RG manual says to pull this value low for 100 ns // however, currently it is pulled low for ~1000 usec this->set_parameter("H2RGMainReset", 1); - usleep(1500); // to be sure we are done with the reset + usleep(1500); // to be sure we are done with the reset this->set_parameter("H2RGMainReset", 0); - usleep(1000); // to be sure we are done with the reset + usleep(1000); // to be sure we are done with the reset // Enable output to Pad B and HIGHOHM - error = this->inreg("10 1 16402"); // 0100 000000010010 + error = this->inreg("10 1 16402"); // 0100 000000010010 if (error == NO_ERROR) error = this->inreg("10 0 1"); // send to detector if (error == NO_ERROR) error = this->inreg("10 0 0"); // reset to 0 if (error != NO_ERROR) { - message.str(""); - message << "enabling output to Pad B and HIGHOHM"; - this->camera.log_error(function, message.str()); + message.str(""); message << "enabling output to Pad B and HIGHOHM"; + this->camera.log_error( function, message.str() ); return ERROR; } return (error); } - /**************** Archon::Interface::hsetup *******************************/ /**************** Archon::Interface::hroi ******************************/ @@ -4197,73 +3962,65 @@ namespace Archon { // If geom_in is not supplied then set geometry to full frame. // - if (!geom_in.empty()) { - // geometry arguments passed in + if ( !geom_in.empty() ) { // geometry arguments passed in Tokenize(geom_in, tokens, " "); if (tokens.size() != 4) { - message.str(""); - message << "param expected 4 arguments (vstart, vstop, hstart, hstop) but got " << tokens.size(); - this->camera.log_error(function, message.str()); + message.str(""); message << "param expected 4 arguments (vstart, vstop, hstart, hstop) but got " << tokens.size(); + this->camera.log_error( function, message.str() ); return ERROR; } try { - vstart = std::stoi(tokens[0]); // test that inputs are integers - vstop = std::stoi(tokens[1]); - hstart = std::stoi(tokens[2]); - hstop = std::stoi(tokens[3]); + vstart = std::stoi( tokens[0] ); // test that inputs are integers + vstop = std::stoi( tokens[1] ); + hstart = std::stoi( tokens[2] ); + hstop = std::stoi( tokens[3]); + } catch (std::invalid_argument &) { - message.str(""); - message << "unable to convert geometry values: " << geom_in << " to integer"; - this->camera.log_error(function, message.str()); + message.str(""); message << "unable to convert geometry values: " << geom_in << " to integer"; + this->camera.log_error( function, message.str() ); return ERROR; + } catch (std::out_of_range &) { - message.str(""); - message << "geometry values " << geom_in << " outside integer range"; - this->camera.log_error(function, message.str()); + message.str(""); message << "geometry values " << geom_in << " outside integer range"; + this->camera.log_error( function, message.str() ); return ERROR; } // Validate values are within detector - if (vstart < 0 || vstop > 2047 || hstart < 0 || hstop > 2047) { - message.str(""); - message << "geometry values " << geom_in << " outside pixel range"; - this->camera.log_error(function, message.str()); + if ( vstart < 0 || vstop > 2047 || hstart < 0 || hstop > 2047) { + message.str(""); message << "geometry values " << geom_in << " outside pixel range"; + this->camera.log_error( function, message.str()); return ERROR; } // Validate values have proper ordering if (vstart >= vstop || hstart >= hstop) { - message.str(""); - message << "geometry values " << geom_in << " are not correctly ordered"; - this->camera.log_error(function, message.str()); + message.str(""); message << "geometry values " << geom_in << " are not correctly ordered"; + this->camera.log_error( function, message.str()); return ERROR; } // Set detector registers and record limits // vstart 1000 000000000000 = 32768 - cmd.str(""); - cmd << "10 1 " << (32768 + vstart); + cmd.str("") ; cmd << "10 1 " << (32768 + vstart); error = this->inreg(cmd.str()); if (error == NO_ERROR) error = this->inreg("10 0 1"); // send to detector if (error == NO_ERROR) error = this->inreg("10 0 0"); // reset to 0 if (error == NO_ERROR) this->win_vstart = vstart; // set y lo lim // vstop 1001 000000000000 = 36864 - cmd.str(""); - cmd << "10 1 " << (36864 + vstop); + cmd.str("") ; cmd << "10 1 " << (36864 + vstop); if (error == NO_ERROR) error = this->inreg(cmd.str()); if (error == NO_ERROR) error = this->inreg("10 0 1"); // send to detector if (error == NO_ERROR) error = this->inreg("10 0 0"); // reset to 0 if (error == NO_ERROR) this->win_vstop = vstop; // set y hi lim // hstart 1010 000000000000 = 40960 - cmd.str(""); - cmd << "10 1 " << (40960 + hstart); + cmd.str("") ; cmd << "10 1 " << (40960 + hstart); if (error == NO_ERROR) error = this->inreg(cmd.str()); if (error == NO_ERROR) error = this->inreg("10 0 1"); // send to detector if (error == NO_ERROR) error = this->inreg("10 0 0"); // reset to 0 if (error == NO_ERROR) this->win_hstart = hstart; // set x lo lim // hstop 1011 000000000000 = 45056 - cmd.str(""); - cmd << "10 1 " << (45056 + hstop); + cmd.str("") ; cmd << "10 1 " << (45056 + hstop); if (error == NO_ERROR) error = this->inreg(cmd.str()); if (error == NO_ERROR) error = this->inreg("10 0 1"); // send to detector if (error == NO_ERROR) error = this->inreg("10 0 0"); // reset to 0 @@ -4307,24 +4064,22 @@ namespace Archon { this->camera_info.set_axes(); } - } // end if geom passed in + + } // end if geom passed in // prepare the return value // - message.str(""); - message << this->win_vstart << " " << this->win_vstop << " " << this->win_hstart << " " << this->win_hstop; + message.str(""); message << this->win_vstart << " " << this->win_vstop << " " << this->win_hstart << " " << this->win_hstop; retstring = message.str(); if (error != NO_ERROR) { - message.str(""); - message << "setting window geometry to " << retstring; - this->camera.log_error(function, message.str()); + message.str(""); message << "setting window geometry to " << retstring; + this->camera.log_error( function, message.str() ); return ERROR; } return (error); } - /**************** Archon::Interface::hroi *********************************/ /**************** Archon::Interface::hwindow ******************************/ @@ -4351,12 +4106,11 @@ namespace Archon { // If something is passed then try to use it to set the window state // - if (!state_in.empty()) { + if ( !state_in.empty() ) { try { - std::transform(state_in.begin(), state_in.end(), state_in.begin(), ::toupper); // make uppercase + std::transform( state_in.begin(), state_in.end(), state_in.begin(), ::toupper ); // make uppercase - if (state_in == "FALSE" || state_in == "0") { - // leave window mode + if ( state_in == "FALSE" || state_in == "0" ) { // leave window mode this->is_window = false; // Set detector out of window mode error = this->inreg("10 1 28684"); // 0111 000000001100 @@ -4378,19 +4132,19 @@ namespace Archon { // Now set CDS cmd.str(""); cmd << "PIXELCOUNT " << this->modemap[nowin_mode].geometry.pixelcount; - error = this->cds(cmd.str(), dontcare); + error = this->cds( cmd.str(), dontcare ); cmd.str(""); cmd << "LINECOUNT " << this->modemap[nowin_mode].geometry.linecount; - error = this->cds(cmd.str(), dontcare); + error = this->cds( cmd.str(), dontcare ); // Issue Abort to complete window mode exit cmd.str(""); if (error == NO_ERROR) { cmd << "Abort 1 "; - error = this->set_parameter(cmd.str()); + error = this->set_parameter( cmd.str() ); } - } else if (state_in == "TRUE" || state_in == "1") { - // enter window mode + + } else if ( state_in == "TRUE" || state_in == "1" ) { // enter window mode this->is_window = true; // Set detector into window mode error = this->inreg("10 1 28687"); // 0111 000000001111 @@ -4417,21 +4171,21 @@ namespace Archon { int cols = (this->win_hstop - this->win_hstart) + 1; if (error == NO_ERROR) { cmd << "H2RG_win_columns " << cols; - error = this->set_parameter(cmd.str()); + error = this->set_parameter( cmd.str() ); } cmd.str(""); if (error == NO_ERROR) { cmd << "H2RG_win_rows " << rows; - error = this->set_parameter(cmd.str()); + error = this->set_parameter( cmd.str() ); } // Now set CDS cmd.str(""); cmd << "PIXELCOUNT " << cols; - error = this->cds(cmd.str(), dontcare); + error = this->cds( cmd.str(), dontcare ); cmd.str(""); cmd << "LINECOUNT " << rows; - error = this->cds(cmd.str(), dontcare); + error = this->cds( cmd.str(), dontcare ); // update modemap, in case someone asks again std::string mode = this->camera_info.current_observing_mode; @@ -4447,32 +4201,30 @@ namespace Archon { this->camera_info.detector_pixels[1] = rows; this->camera_info.set_axes(); + } else { - message.str(""); - message << "window state " << state_in << " is invalid. Expecting {true,false,0,1}"; - this->camera.log_error(function, message.str()); + message.str(""); message << "window state " << state_in << " is invalid. Expecting {true,false,0,1}"; + this->camera.log_error( function, message.str() ); return ERROR; } + } catch (...) { - message.str(""); - message << "unknown exception converting window state " << state_in << " to uppercase"; - this->camera.log_error(function, message.str()); + message.str(""); message << "unknown exception converting window state " << state_in << " to uppercase"; + this->camera.log_error( function, message.str() ); return ERROR; } } - state_out = (this->is_window ? "true" : "false"); + state_out = ( this->is_window ? "true" : "false" ); if (error != NO_ERROR) { - message.str(""); - message << "setting window state to " << state_in; - this->camera.log_error(function, message.str()); + message.str(""); message << "setting window state to " << state_in; + this->camera.log_error( function, message.str() ); return ERROR; } return (error); } - /**************** Archon::Interface::hwindow *******************************/ /**************** Archon::Interface::autofetch ******************************/ @@ -4499,12 +4251,11 @@ namespace Archon { // If something is passed then try to use it to set the autofetch state // - if (!state_in.empty()) { + if ( !state_in.empty() ) { try { - std::transform(state_in.begin(), state_in.end(), state_in.begin(), ::toupper); // make uppercase + std::transform( state_in.begin(), state_in.end(), state_in.begin(), ::toupper ); // make uppercase - if (state_in == "FALSE" || state_in == "0") { - // leave autofetch mode + if ( state_in == "FALSE" || state_in == "0" ) { // leave autofetch mode this->is_autofetch = false; // Set detector out of autofetch mode // Now send the AUTOFETCHx command @@ -4521,8 +4272,8 @@ namespace Archon { } logwrite(function, message.str()); - } else if (state_in == "TRUE" || state_in == "1") { - // enter window mode + + } else if ( state_in == "TRUE" || state_in == "1" ) { // enter window mode this->is_autofetch = true; // Set detector into autofetch mode // Now send the AUTOFETCHx command @@ -4540,31 +4291,28 @@ namespace Archon { logwrite(function, message.str()); } else { - message.str(""); - message << "autofetch state " << state_in << " is invalid. Expecting {true,false,0,1}"; - this->camera.log_error(function, message.str()); + message.str(""); message << "autofetch state " << state_in << " is invalid. Expecting {true,false,0,1}"; + this->camera.log_error( function, message.str() ); return ERROR; } + } catch (...) { - message.str(""); - message << "unknown exception converting autofetch state " << state_in << " to uppercase"; - this->camera.log_error(function, message.str()); + message.str(""); message << "unknown exception converting autofetch state " << state_in << " to uppercase"; + this->camera.log_error( function, message.str() ); return ERROR; } } - state_out = (this->is_autofetch ? "true" : "false"); + state_out = ( this->is_autofetch ? "true" : "false" ); if (error != NO_ERROR) { - message.str(""); - message << "setting autofetch state to " << state_in; - this->camera.log_error(function, message.str()); + message.str(""); message << "setting autofetch state to " << state_in; + this->camera.log_error( function, message.str() ); return ERROR; } return (error); } - /**************** Archon::Interface::autofetch *******************************/ /**************** Archon::Interface::hexpose ******************************/ @@ -4592,19 +4340,18 @@ namespace Archon { std::string nseqstr; int nseq, finalframe, nread, currentindex; - std::string mode = this->camera_info.current_observing_mode; // local copy for convenience + std::string mode = this->camera_info.current_observing_mode; // local copy for convenience - if (!this->modeselected) { - this->camera.log_error(function, "no mode selected"); + if ( ! this->modeselected ) { + this->camera.log_error( function, "no mode selected" ); return ERROR; } // exposeparam is set by the configuration file // check to make sure it was set, or else expose won't work if (this->exposeparam.empty()) { - message.str(""); - message << "EXPOSE_PARAM not defined in configuration file " << this->config.filename; - this->camera.log_error(function, message.str()); + message.str(""); message << "EXPOSE_PARAM not defined in configuration file " << this->config.filename; + this->camera.log_error( function, message.str() ); return ERROR; } @@ -4612,66 +4359,54 @@ namespace Archon { // This ensures that, if the client doesn't set these values then the server will have the // same default values that the ACF has, rather than hope that the ACF programmer picks // their defaults to match mine. - if (this->camera_info.exposure_time == -1) { - logwrite(function, "NOTICE:exptime has not been set--will read from Archon"); - this->camera.async.enqueue("NOTICE:exptime has not been set--will read from Archon"); + if ( this->camera_info.exposure_time == -1 ) { + logwrite( function, "NOTICE:exptime has not been set--will read from Archon" ); + this->camera.async.enqueue( "NOTICE:exptime has not been set--will read from Archon" ); // read the Archon configuration memory // std::string etime; - if (read_parameter("exptime", etime) != NO_ERROR) { - logwrite(function, "ERROR: reading \"exptime\" parameter from Archon"); - return ERROR; - } + if ( read_parameter( "exptime", etime ) != NO_ERROR ) { logwrite( function, "ERROR: reading \"exptime\" parameter from Archon" ); return ERROR; } // Tell the server these values // std::string retval; - if (this->exptime(etime, retval) != NO_ERROR) { - logwrite(function, "ERROR: setting exptime"); - return ERROR; - } + if ( this->exptime( etime, retval ) != NO_ERROR ) { logwrite( function, "ERROR: setting exptime" ); return ERROR; } } - if (this->camera_info.exposure_factor == -1 || - this->camera_info.exposure_unit.empty()) { - logwrite(function, "NOTICE:longexposure has not been set--will read from Archon"); - this->camera.async.enqueue("NOTICE:longexposure has not been set--will read from Archon"); + if ( this->camera_info.exposure_factor == -1 || + this->camera_info.exposure_unit.empty() ) { + logwrite( function, "NOTICE:longexposure has not been set--will read from Archon" ); + this->camera.async.enqueue( "NOTICE:longexposure has not been set--will read from Archon" ); // read the Archon configuration memory // std::string lexp; - if (read_parameter("longexposure", lexp) != NO_ERROR) { - logwrite(function, "ERROR: reading \"longexposure\" parameter from Archon"); - return ERROR; - } + if ( read_parameter( "longexposure", lexp ) != NO_ERROR ) { logwrite( function, "ERROR: reading \"longexposure\" parameter from Archon" ); return ERROR; } // Tell the server these values // std::string retval; - if (this->longexposure(lexp, retval) != NO_ERROR) { - logwrite(function, "ERROR: setting longexposure"); - return ERROR; - } + if ( this->longexposure( lexp, retval ) != NO_ERROR ) { logwrite( function, "ERROR: setting longexposure" ); return ERROR; } } // If nseq_in is not supplied then set nseq to 1. - if (nseq_in.empty()) { + if ( nseq_in.empty() ) { nseq = 1; - nseqstr = std::to_string(nseq); - } else { - // sequence argument passed in + nseqstr = std::to_string( nseq ); + + } else { // sequence argument passed in try { - nseq = std::stoi(nseq_in) + this->camera_info.num_pre_exposures; // test that nseq_in is an integer - nseqstr = std::to_string(nseq); // before trying to use it + nseq = std::stoi( nseq_in ) + this->camera_info.num_pre_exposures; // test that nseq_in is an integer + nseqstr = std::to_string( nseq ); // before trying to use it + } catch (std::invalid_argument &) { - message.str(""); - message << "unable to convert sequences: " << nseq_in << " to integer"; - this->camera.log_error(function, message.str()); + message.str(""); message << "unable to convert sequences: " << nseq_in << " to integer"; + this->camera.log_error( function, message.str() ); return ERROR; + } catch (std::out_of_range &) { - message.str(""); - message << "sequences " << nseq_in << " outside integer range"; - this->camera.log_error(function, message.str()); + message.str(""); message << "sequences " << nseq_in << " outside integer range"; + this->camera.log_error( function, message.str() ); return ERROR; } } @@ -4685,7 +4420,7 @@ namespace Archon { currentindex = this->frame.index; if (error != NO_ERROR) { - logwrite(function, "ERROR: unable to get frame status"); + logwrite( function, "ERROR: unable to get frame status" ); return ERROR; } // save the last frame number acquired (wait_for_readout will need this) @@ -4695,9 +4430,7 @@ namespace Archon { finalframe = this->lastframe + nseq; if (nseq > 1) { - message.str(""); - message << "starting sequence of " << nseq << " frames. lastframe=" << this->lastframe << " last buffer=" << - currentindex + 1; + message.str(""); message << "starting sequence of " << nseq << " frames. lastframe=" << this->lastframe << " last buffer=" << currentindex+1; logwrite(function, message.str()); } @@ -4705,7 +4438,7 @@ namespace Archon { this->camera_info.frame_type = Camera::FRAME_IMAGE; error = this->prepare_image_buffer(); if (error == ERROR) { - logwrite(function, "ERROR: unable to allocate an image buffer"); + logwrite( function, "ERROR: unable to allocate an image buffer" ); return ERROR; } @@ -4713,8 +4446,8 @@ namespace Archon { logwrite(function, "exposure starting"); error = this->prep_parameter(this->exposeparam, nseqstr); if (error == NO_ERROR) error = this->load_parameter(this->exposeparam, nseqstr); - if (error != NO_ERROR) { - logwrite(function, "ERROR: could not initiate exposure"); + if ( error != NO_ERROR ) { + logwrite( function, "ERROR: could not initiate exposure" ); return error; } @@ -4733,3654 +4466,3454 @@ namespace Archon { // // -- MAIN SEQUENCE LOOP -- - nread = 0; // Keep track of how many we actually read - int ns = nseq; // Iterate with ns, to preserve original request + nread = 0; // Keep track of how many we actually read + int ns = nseq; // Iterate with ns, to preserve original request while (ns-- > 0 && this->lastframe < finalframe) { + // if ( !this->camera.datacube() || this->camera.cubeamps() ) { // this->camera_info.start_time = get_timestamp(); // current system time formatted as YYYY-MM-DDTHH:MM:SS.sss // if ( this->get_timer(&this->start_timer) != NO_ERROR ) { // Archon internal timer (one tick=10 nsec) // logwrite( function, "ERROR: could not get start time" ); // return ERROR; // } - // this->add_filename_key(); // add filename to system keys database + // this->add_filename_key(); // add filename to system keys database // } // wait for the exposure delay to complete (if there is one) - if (this->camera_info.exposure_time != 0) { + if ( this->camera_info.exposure_time != 0 ) { error = this->wait_for_exposure(); - if (error != NO_ERROR) { - logwrite(function, "ERROR: waiting for exposure"); + if ( error != NO_ERROR ) { + logwrite( function, "ERROR: waiting for exposure" ); return error; } } // Wait for the readout into frame buffer, error = this->hwait_for_readout(); - if (error != NO_ERROR) { - logwrite(function, "ERROR: waiting for readout"); + if ( error != NO_ERROR ) { + logwrite( function, "ERROR: waiting for readout" ); return error; } // then read the frame buffer to host (and write file) when frame ready. error = hread_frame(); - if (error != NO_ERROR) { - logwrite(function, "ERROR: reading frame buffer"); + if ( error != NO_ERROR ) { + logwrite( function, "ERROR: reading frame buffer" ); return error; } // ASYNC status message on completion of each readout nread++; - message.str(""); - message << "READOUT COMPLETE (" << nread << " of " << nseq << " read)"; - this->camera.async.enqueue(message.str()); - logwrite(function, message.str()); + message.str(""); message << "READOUT COMPLETE (" << nread << " of " << nseq << " read)"; + this->camera.async.enqueue( message.str() ); + logwrite( function, message.str() ); + + if (error != NO_ERROR) break; // should be impossible but don't try additional sequences if there were errors - if (error != NO_ERROR) break; - // should be impossible but don't try additional sequences if there were errors - } // end of sequence loop, while (ns-- > 0 && this->lastframe < finalframe) + } // end of sequence loop, while (ns-- > 0 && this->lastframe < finalframe) // ASYNC status message on completion of each sequence - message.str(""); - message << "READOUT SEQUENCE " << (error == NO_ERROR ? "COMPLETE" : "ERROR") << " (" << nread << " of " << nseq - << " read)"; - this->camera.async.enqueue(message.str()); - error == NO_ERROR ? logwrite(function, message.str()) : this->camera.log_error(function, message.str()); + message.str(""); message << "READOUT SEQUENCE " << ( error==NO_ERROR ? "COMPLETE" : "ERROR" ) << " (" << nread << " of " << nseq << " read)"; + this->camera.async.enqueue( message.str() ); + error == NO_ERROR ? logwrite( function, message.str() ) : this->camera.log_error( function, message.str() ); error = get_frame_status(); - if (error != NO_ERROR) { - logwrite(function, "ERROR: getting final frame status"); + if ( error != NO_ERROR ) { + logwrite( function, "ERROR: getting final frame status" ); return error; } - message.str(""); - message << "Last frame read " << this->frame.frame << " from buffer " << this->frame.index + 1; - logwrite(function, message.str()); + message.str(""); message << "Last frame read " << this->frame.frame << " from buffer " << this->frame.index + 1; + logwrite( function, message.str()); return (error); } - /**************** Archon::Interface::hexpose *********************************/ - /**************** Archon::Interface::video *********************************/ - /** - * @fn video - * @brief initiate a video exposure - * @param nseq_in string, if set becomes the number of sequences - * @return ERROR or NO_ERROR - * - * This function does the following before returning successful completion: - * 1) trigger an Archon exposure by setting the EXPOSE parameter = nseq_in - * 2) wait for exposure delay - * 3) wait for readout into Archon frame buffer - * 4) read frame buffer from Archon to host - * 5) do NOT write frame to disk - * - * Note that this assumes that the Archon ACF has been programmed to automatically - * read out the detector into the frame buffer after an exposure. - * - */ - - long Interface::video() { - std::string function = "Archon::Interface::video"; - std::stringstream message; - long error = NO_ERROR; - std::string nseqstr; - int nseq; - - std::string mode = this->camera_info.current_observing_mode; // local copy for convenience - - if (!this->modeselected) { - this->camera.log_error(function, "no mode selected"); - return ERROR; - } - - // When switching from cubeamps=true to cubeamps=false, - // simply reset the mode to the current mode in order to - // reset the image size. - // - // This will need to be revisited once ROI is implemented. // TODO - // - if (!this->camera.cubeamps() && (this->lastcubeamps != this->camera.cubeamps())) { - message.str(""); - message << "detected change in cubeamps -- resetting camera mode to " << mode; - logwrite(function, message.str()); - this->set_camera_mode(mode); - } - - // exposeparam is set by the configuration file - // check to make sure it was set, or else expose won't work - // - if (this->exposeparam.empty()) { - message.str(""); - message << "EXPOSE_PARAM not defined in configuration file " << this->config.filename; - this->camera.log_error(function, message.str()); - return ERROR; - } - - // If the exposure time or longexposure mode were never set then read them from the Archon. - // This ensures that, if the client doesn't set these values then the server will have the - // same default values that the ACF has, rather than hope that the ACF programmer picks - // their defaults to match mine. - // - if (this->camera_info.exposure_time == -1) { - logwrite(function, "NOTICE:exptime has not been set--will read from Archon"); - this->camera.async.enqueue("NOTICE:exptime has not been set--will read from Archon"); - - // read the Archon configuration memory - // - std::string etime; - if (read_parameter("exptime", etime) != NO_ERROR) { - logwrite(function, "ERROR: reading \"exptime\" parameter from Archon"); - return ERROR; - } - - // Tell the server these values - // - std::string retval; - if (this->exptime(etime, retval) != NO_ERROR) { - logwrite(function, "ERROR: setting exptime"); - return ERROR; - } - } - if (this->camera_info.exposure_factor == -1 || - this->camera_info.exposure_unit.empty()) { - logwrite(function, "NOTICE:longexposure has not been set--will read from Archon"); - this->camera.async.enqueue("NOTICE:longexposure has not been set--will read from Archon"); - - // read the Archon configuration memory - // - std::string lexp; - if (read_parameter("longexposure", lexp) != NO_ERROR) { - logwrite(function, "ERROR: reading \"longexposure\" parameter from Archon"); - return ERROR; - } - - // Tell the server these values - // - std::string retval; - if (this->longexposure(lexp, retval) != NO_ERROR) { - logwrite(function, "ERROR: setting longexposure"); - return ERROR; - } - } - - // If nseq_in is not supplied then set nseq to 1. - // Add any pre-exposures onto the number of sequences. - // - - nseq = 1 + this->camera_info.num_pre_exposures; - - // Always initialize the extension number because someone could - // set datacube true and then send "expose" without a number. - // - this->camera_info.extension = 0; - - error = this->get_frame_status(); // TODO is this needed here? - - if (error != NO_ERROR) { - logwrite(function, "ERROR: unable to get frame status"); - return ERROR; - } - this->lastframe = this->frame.bufframen[this->frame.index]; - // save the last frame number acquired (wait_for_readout will need this) - - // initiate the exposure here - // - error = this->prep_parameter(this->exposeparam, nseqstr); - if (error == NO_ERROR) error = this->load_parameter(this->exposeparam, nseqstr); - if (error != NO_ERROR) { - logwrite(function, "ERROR: could not initiate exposure"); - return error; - } - - // get system time and Archon's timer after exposure starts - // start_timer is used to determine when the exposure has ended, in wait_for_exposure() - // - this->camera_info.start_time = get_timestamp(); // current system time formatted as YYYY-MM-DDTHH:MM:SS.sss - if (this->get_timer(&this->start_timer) != NO_ERROR) { - // Archon internal timer (one tick=10 nsec) - logwrite(function, "ERROR: could not get start time"); - return ERROR; - } - this->camera.set_fitstime(this->camera_info.start_time); - // sets camera.fitstime (YYYYMMDDHHMMSS) used for filename - error = this->camera.get_fitsname(this->camera_info.fits_name); // assemble the FITS filename - if (error != NO_ERROR) { - logwrite(function, "ERROR: couldn't validate fits filename"); - return error; - } - - this->add_filename_key(); // add filename to system keys database - - logwrite(function, "exposure started"); - - this->camera_info.systemkeys.keydb = this->systemkeys.keydb; - // copy the systemkeys database object into camera_info - - if (this->camera.writekeys_when == "before") this->copy_keydb(); - // copy the ACF and userkeys database into camera_info - - // If mode is not "RAW" but RAWENABLE is set then we're going to require a multi-extension data cube, - // one extension for the image and a separate extension for raw data. - // - if ((mode != "RAW") && (this->modemap[mode].rawenable)) { - if (!this->camera.datacube()) { - // if datacube not already set then it must be overridden here - this->camera.async.enqueue("NOTICE:override datacube true"); // let everyone know - logwrite(function, "NOTICE:override datacube true"); - this->camera.datacube(true); - } - this->camera_info.extension = 0; - } - - // Save the datacube state in camera_info so that the FITS writer can know about it - // - this->camera_info.iscube = this->camera.datacube(); - - // Open the FITS file now for cubes - // - if (this->camera.datacube() && !this->camera.cubeamps()) { -#ifdef LOGLEVEL_DEBUG - logwrite( function, "[DEBUG] opening fits file for multi-exposure sequence data cube" ); -#endif - error = this->fits_file.open_file( - this->camera.writekeys_when == "before", this->camera_info); - if (error != NO_ERROR) { - this->camera.log_error(function, "couldn't open fits file"); - return error; - } - } - - // //TODO only use camera_info -- don't use fits_info -- is this OK? TO BE CONFIRMED - // this->fits_info = this->camera_info; // copy the camera_info class, to be given to fits writer //TODO - - // this->lastframe = this->frame.bufframen[this->frame.index]; // save the last frame number acquired (wait_for_readout will need this) - - if (nseq > 1) { - message.str(""); - message << "starting sequence of " << nseq << " frames. lastframe=" << this->lastframe; - logwrite(function, message.str()); - } - - // If not RAW mode then wait for Archon frame buffer to be ready, - // then read the latest ready frame buffer to the host. If this - // is a squence, then loop over all expected frames. - // - if (mode != "RAW") { - // If not raw mode then - int expcount = 0; // counter used only for tracking pre-exposures - - // - // -- MAIN SEQUENCE LOOP -- - // - while (nseq-- > 0) { - // Wait for any pre-exposures, first the exposure delay then the readout, - // but then continue to the next because pre-exposures are not read from - // the Archon's buffer. - // - if (++expcount <= this->camera_info.num_pre_exposures) { - message.str(""); - message << "pre-exposure " << expcount << " of " << this->camera_info.num_pre_exposures; - logwrite(function, message.str()); - - if (this->camera_info.exposure_time != 0) { - // wait for the exposure delay to complete (if there is one) - error = this->wait_for_exposure(); - if (error != NO_ERROR) { - logwrite(function, "ERROR: waiting for pre-exposure"); - return error; - } - } - - error = this->wait_for_readout(); // Wait for the readout into frame buffer, - if (error != NO_ERROR) { - logwrite(function, "ERROR: waiting for pre-exposure readout"); - return error; - } - - continue; - } - - // Open a new FITS file for each frame when not using datacubes - // -#ifdef LOGLEVEL_DEBUG - message.str(""); message << "[DEBUG] datacube=" << this->camera.datacube() << " cubeamps=" << this->camera.cubeamps(); - logwrite( function, message.str() ); -#endif - if (!this->camera.datacube() || this->camera.cubeamps()) { - this->camera_info.start_time = get_timestamp(); - // current system time formatted as YYYY-MM-DDTHH:MM:SS.sss - if (this->get_timer(&this->start_timer) != NO_ERROR) { - // Archon internal timer (one tick=10 nsec) - logwrite(function, "ERROR: could not get start time"); - return ERROR; - } - this->camera.set_fitstime(this->camera_info.start_time); - // sets camera.fitstime (YYYYMMDDHHMMSS) used for filename - error = this->camera.get_fitsname(this->camera_info.fits_name); // Assemble the FITS filename - if (error != NO_ERROR) { - logwrite(function, "ERROR: couldn't validate fits filename"); - return error; - } - this->add_filename_key(); // add filename to system keys database - -#ifdef LOGLEVEL_DEBUG - logwrite( function, "[DEBUG] reset extension=0 and opening new fits file" ); -#endif - // reset the extension number and open the fits file - // - this->camera_info.extension = 0; - error = this->fits_file.open_file( - this->camera.writekeys_when == "before", this->camera_info); - if (error != NO_ERROR) { - this->camera.log_error(function, "couldn't open fits file"); - return error; - } - } - - if (this->camera_info.exposure_time != 0) { - // wait for the exposure delay to complete (if there is one) - error = this->wait_for_exposure(); - if (error != NO_ERROR) { - logwrite(function, "ERROR: waiting for exposure"); - return error; - } - } - - if (this->camera.writekeys_when == "after") this->copy_keydb(); - // copy the ACF and userkeys database into camera_info - - error = this->wait_for_readout(); // Wait for the readout into frame buffer, - - if (error != NO_ERROR) { - logwrite(function, "ERROR: waiting for readout"); - this->fits_file.close_file( - this->camera.writekeys_when == "after", this->camera_info); - return error; - } - - error = read_frame(); // then read the frame buffer to host (and write file) when frame ready. - if (error != NO_ERROR) { - logwrite(function, "ERROR: reading frame buffer"); - this->fits_file.close_file( - this->camera.writekeys_when == "after", this->camera_info); - return error; - } - - // For non-sequence multiple exposures, including cubeamps, close the fits file here - // - if (!this->camera.datacube() || this->camera.cubeamps()) { - // Error or not, close the file. -#ifdef LOGLEVEL_DEBUG - logwrite( function, "[DEBUG] closing fits file (1)" ); -#endif - this->fits_file.close_file( - this->camera.writekeys_when == "after", - this->camera_info); // close the file when not using datacubes - this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" - - // ASYNC status message on completion of each file - // - message.str(""); - message << "FILE:" << this->camera_info.fits_name << " COMPLETE"; - this->camera.async.enqueue(message.str()); - logwrite(function, message.str()); - } - - if (error != NO_ERROR) break; - // should be impossible but don't try additional sequences if there were errors - } // end of sequence loop, while (nseq-- > 0) - } else if (mode == "RAW") { - error = this->get_frame_status(); // Get the current frame buffer status - if (error != NO_ERROR) { - logwrite(function, "ERROR: unable to get frame status"); - return ERROR; - } - error = this->camera.get_fitsname(this->camera_info.fits_name); // Assemble the FITS filename - if (error != NO_ERROR) { - logwrite(function, "ERROR: couldn't validate fits filename"); - return error; - } - this->add_filename_key(); // add filename to system keys database - - this->camera_info.systemkeys.keydb = this->systemkeys.keydb; - // copy the systemkeys database into camera_info - - this->copy_keydb(); // copy the ACF and userkeys databases into camera_info - - error = this->fits_file.open_file( - this->camera.writekeys_when == "before", this->camera_info); - if (error != NO_ERROR) { - this->camera.log_error(function, "couldn't open fits file"); - return error; - } - error = read_frame(); // For raw mode just read immediately - this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info); - this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" - } - - // for multi-exposure (non-cubeamp) cubes, close the FITS file now that they've all been written - // - if (this->camera.datacube() && !this->camera.cubeamps()) { -#ifdef LOGLEVEL_DEBUG - logwrite( function, "[DEBUG] closing fits file (2)" ); -#endif - this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info); - this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" - - // ASYNC status message on completion of each file - // - message.str(""); - message << "FILE:" << this->camera_info.fits_name << " " << (error == NO_ERROR ? "COMPLETE" : "ERROR"); - this->camera.async.enqueue(message.str()); - error == NO_ERROR ? logwrite(function, message.str()) : this->camera.log_error(function, message.str()); - } - - // remember the cubeamps setting used for the last completed exposure - // TODO revisit once region-of-interest is implemented - // - this->lastcubeamps = this->camera.cubeamps(); - - return (error); - } - - /**************** Archon::Interface::video *********************************/ - - - /**************** Archon::Interface::wait_for_exposure **********************/ + /**************** Archon::Interface::video *********************************/ /** - * @fn wait_for_exposure - * @brief creates a wait until the exposure delay has completed - * @param none + * @fn video + * @brief initiate a video exposure + * @param nseq_in string, if set becomes the number of sequences * @return ERROR or NO_ERROR * - * This is not the actual exposure delay, nor does it accurately time the exposure - * delay. This function merely creates a reasonably accurate wait on the host to - * allow time for the Archon to complete its exposure delay. This is done by using - * the exposure time given to the Archon and by using the Archon's internal timer, - * which is queried here. There is no sense in polling the Archon's timer for the - * entire exposure time, so this function waits internally for about 90% of the - * exposure time, then only starts polling the Archon for the remaining time. + * This function does the following before returning successful completion: + * 1) trigger an Archon exposure by setting the EXPOSE parameter = nseq_in + * 2) wait for exposure delay + * 3) wait for readout into Archon frame buffer + * 4) read frame buffer from Archon to host + * 5) do NOT write frame to disk * - * A prediction is made of what the Archon's timer will be at the end, in order - * to provide an estimate of completion. + * Note that this assumes that the Archon ACF has been programmed to automatically + * read out the detector into the frame buffer after an exposure. * */ - long Interface::wait_for_exposure() { - std::string function = "Archon::Interface::wait_for_exposure"; - std::stringstream message; - long error = NO_ERROR; - int exposure_timeout_time; // Time to wait for the exposure delay to time out - unsigned long int timer, increment = 0; + long Interface::video() { + std::string function = "Archon::Interface::video"; + std::stringstream message; + long error = NO_ERROR; + std::string nseqstr; + int nseq; - // For long exposures, waittime is 1 second less than the exposure time. - // For short exposures, waittime is an integral number of msec below 90% of the exposure time - // and will be used to keep track of elapsed time, for timeout errors. - // - double waittime; - if (this->is_longexposure) { - waittime = this->camera_info.exposure_time - 1; - } else { - waittime = floor(0.9 * this->camera_info.exposure_time / this->camera_info.exposure_factor); - // in units of this->camera_info.exposure_unit - } + std::string mode = this->camera_info.current_observing_mode; // local copy for convenience - // Wait, (don't sleep) for the above waittime. - // This is a period that could be aborted by setting the this->abort flag. //TODO not yet implemented? - // All that is happening here is a wait -- there is no Archon polling going on here. - // - double start_time = get_clock_time(); // get the current clock time from host (in seconds) - double now = start_time; + if ( ! this->modeselected ) { + this->camera.log_error( function, "no mode selected" ); + return ERROR; + } - // Prediction is the predicted finish_timer, used to compute exposure time progress, - // and is computed as start_time + exposure_time in Archon ticks. - // Each Archon tick is 10 nsec (1e8 sec). Divide by exposure_factor (=1 for sec, =1000 for msec). - // - unsigned long int prediction = this->start_timer + this->camera_info.exposure_time * 1e8 / this->camera_info. - exposure_factor; - - std::cerr << "exposure progress: "; - while ((now - (waittime + start_time) < 0) && !this->abort) { - // sleep 10 msec = 1e6 Archon ticks - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - increment += 1000000; - now = get_clock_time(); - this->camera_info.exposure_progress = (double) increment / (double) (prediction - this->start_timer); - if (this->camera_info.exposure_progress < 0 || this->camera_info.exposure_progress > 1) - this->camera_info.exposure_progress = 1; - std::cerr << std::setw(3) << (int) (this->camera_info.exposure_progress * 100) << "\b\b\b"; - - // ASYNC status message reports the elapsed time in the chosen unit - // - message.str(""); - message << "EXPOSURE:" << (int) (this->camera_info.exposure_time - ( - this->camera_info.exposure_progress * this->camera_info. - exposure_time)); - this->camera.async.enqueue(message.str()); - } + // When switching from cubeamps=true to cubeamps=false, + // simply reset the mode to the current mode in order to + // reset the image size. + // + // This will need to be revisited once ROI is implemented. // TODO + // + if ( !this->camera.cubeamps() && ( this->lastcubeamps != this->camera.cubeamps() ) ) { + message.str(""); message << "detected change in cubeamps -- resetting camera mode to " << mode; + logwrite( function, message.str() ); + this->set_camera_mode( mode ); + } - if (this->abort) { - std::cerr << "\n"; - logwrite(function, "exposure aborted"); - return NO_ERROR; - } + // exposeparam is set by the configuration file + // check to make sure it was set, or else expose won't work + // + if (this->exposeparam.empty()) { + message.str(""); message << "EXPOSE_PARAM not defined in configuration file " << this->config.filename; + this->camera.log_error( function, message.str() ); + return ERROR; + } - // Set the time-out value in ms. If the exposure time is less than a second, set - // the timeout to 1 second. Otherwise, set it to the exposure time plus - // 1 second. - // - if (this->camera_info.exposure_time / this->camera_info.exposure_factor < 1) { - exposure_timeout_time = 1000; //ms - } else { - exposure_timeout_time = (this->camera_info.exposure_time / this->camera_info.exposure_factor) * 1000 + 1000; - } + // If the exposure time or longexposure mode were never set then read them from the Archon. + // This ensures that, if the client doesn't set these values then the server will have the + // same default values that the ACF has, rather than hope that the ACF programmer picks + // their defaults to match mine. + // + if ( this->camera_info.exposure_time == -1 ) { + logwrite( function, "NOTICE:exptime has not been set--will read from Archon" ); + this->camera.async.enqueue( "NOTICE:exptime has not been set--will read from Archon" ); + + // read the Archon configuration memory + // + std::string etime; + if ( read_parameter( "exptime", etime ) != NO_ERROR ) { logwrite( function, "ERROR: reading \"exptime\" parameter from Archon" ); return ERROR; } + + // Tell the server these values + // + std::string retval; + if ( this->exptime( etime, retval ) != NO_ERROR ) { logwrite( function, "ERROR: setting exptime" ); return ERROR; } + } + if ( this->camera_info.exposure_factor == -1 || + this->camera_info.exposure_unit.empty() ) { + logwrite( function, "NOTICE:longexposure has not been set--will read from Archon" ); + this->camera.async.enqueue( "NOTICE:longexposure has not been set--will read from Archon" ); + + // read the Archon configuration memory + // + std::string lexp; + if ( read_parameter( "longexposure", lexp ) != NO_ERROR ) { logwrite( function, "ERROR: reading \"longexposure\" parameter from Archon" ); return ERROR; } + + // Tell the server these values + // + std::string retval; + if ( this->longexposure( lexp, retval ) != NO_ERROR ) { logwrite( function, "ERROR: setting longexposure" ); return ERROR; } + } - // Now start polling the Archon for the last remaining portion of the exposure delay - // - bool done = false; - while (!done && !this->abort) { - // Poll Archon's internal timer - // - if ((error = this->get_timer(&timer)) == ERROR) { - std::cerr << "\n"; - logwrite(function, "ERROR: could not get Archon timer"); - break; - } + // If nseq_in is not supplied then set nseq to 1. + // Add any pre-exposures onto the number of sequences. + // - // update progress - // - this->camera_info.exposure_progress = - (double) (timer - this->start_timer) / (double) (prediction - this->start_timer); - if (this->camera_info.exposure_progress < 0 || this->camera_info.exposure_progress > 1) - this->camera_info.exposure_progress = 1; + nseq = 1 + this->camera_info.num_pre_exposures; - // ASYNC status message reports the elapsed time in the chosen unit - // - message.str(""); - message << "EXPOSURE:" << (int) (this->camera_info.exposure_time - ( - this->camera_info.exposure_progress * this->camera_info. - exposure_time)); - this->camera.async.enqueue(message.str()); + // Always initialize the extension number because someone could + // set datacube true and then send "expose" without a number. + // + this->camera_info.extension = 0; - std::cerr << std::setw(3) << (int) (this->camera_info.exposure_progress * 100) << "\b\b\b"; - // send to stderr in case anyone is watching + error = this->get_frame_status(); // TODO is this needed here? - // Archon timer ticks are in 10 nsec (1e-8) so when comparing to exposure_time, - // multiply exposure_time by 1e8/exposure_factor, where exposure_factor=1 or =1000 for exposure_unit sec or msec. - // - if ((timer - this->start_timer) >= (this->camera_info.exposure_time * 1e8 / this->camera_info. - exposure_factor)) { - this->finish_timer = timer; - done = true; - break; - } + if (error != NO_ERROR) { + logwrite( function, "ERROR: unable to get frame status" ); + return ERROR; + } + this->lastframe = this->frame.bufframen[this->frame.index]; // save the last frame number acquired (wait_for_readout will need this) + + // initiate the exposure here + // + error = this->prep_parameter(this->exposeparam, nseqstr); + if (error == NO_ERROR) error = this->load_parameter(this->exposeparam, nseqstr); + if ( error != NO_ERROR ) { + logwrite( function, "ERROR: could not initiate exposure" ); + return error; + } - // a little pause to slow down the requests to Archon - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - // Added protection against infinite loops, probably will never be invoked - // because an Archon error getting the timer would exit the loop. - // exposure_timeout_time is in msec, and it's a little more than 1 msec to get - // through this loop. If this is decremented each time through then it should - // never hit zero before the exposure is finished, unless there is a serious - // problem. - // - if (--exposure_timeout_time < 0) { - std::cerr << "\n"; - error = ERROR; - this->camera.log_error(function, "timeout waiting for exposure"); - break; - } - } // end while (done == false && this->abort == false) + // get system time and Archon's timer after exposure starts + // start_timer is used to determine when the exposure has ended, in wait_for_exposure() + // + this->camera_info.start_time = get_timestamp(); // current system time formatted as YYYY-MM-DDTHH:MM:SS.sss + if ( this->get_timer(&this->start_timer) != NO_ERROR ) { // Archon internal timer (one tick=10 nsec) + logwrite( function, "ERROR: could not get start time" ); + return ERROR; + } + this->camera.set_fitstime(this->camera_info.start_time); // sets camera.fitstime (YYYYMMDDHHMMSS) used for filename + error=this->camera.get_fitsname(this->camera_info.fits_name); // assemble the FITS filename + if ( error != NO_ERROR ) { + logwrite( function, "ERROR: couldn't validate fits filename" ); + return error; + } - std::cerr << "\n"; + this->add_filename_key(); // add filename to system keys database - if (this->abort) { - logwrite(function, "exposure aborted"); - } + logwrite(function, "exposure started"); - return error; - } + this->camera_info.systemkeys.keydb = this->systemkeys.keydb; // copy the systemkeys database object into camera_info - /**************** Archon::Interface::wait_for_exposure **********************/ + if (this->camera.writekeys_when=="before") this->copy_keydb(); // copy the ACF and userkeys database into camera_info + // If mode is not "RAW" but RAWENABLE is set then we're going to require a multi-extension data cube, + // one extension for the image and a separate extension for raw data. + // + if ( (mode != "RAW") && (this->modemap[mode].rawenable) ) { + if ( !this->camera.datacube() ) { // if datacube not already set then it must be overridden here + this->camera.async.enqueue( "NOTICE:override datacube true" ); // let everyone know + logwrite( function, "NOTICE:override datacube true" ); + this->camera.datacube(true); + } + this->camera_info.extension = 0; + } - /**************** Archon::Interface::wait_for_readout ***********************/ - /** - * @fn wait_for_readout - * @brief creates a wait until the next frame buffer is ready - * @param none - * @return ERROR or NO_ERROR - * - * This function polls the Archon frame status until a new frame is ready. - * - */ - long Interface::wait_for_readout() { - std::string function = "Archon::Interface::wait_for_readout"; - std::stringstream message; - long error = NO_ERROR; - int currentframe = this->lastframe; - int busycount = 0; - bool done = false; + // Save the datacube state in camera_info so that the FITS writer can know about it + // + this->camera_info.iscube = this->camera.datacube(); - message.str(""); - message << "waiting for new frame: current frame=" << this->lastframe << " current buffer=" << this->frame.index - + 1; - logwrite(function, message.str()); + // Open the FITS file now for cubes + // + if ( this->camera.datacube() && !this->camera.cubeamps() ) { + #ifdef LOGLEVEL_DEBUG + logwrite( function, "[DEBUG] opening fits file for multi-exposure sequence data cube" ); + #endif + error = this->fits_file.open_file( + this->camera.writekeys_when == "before", this->camera_info ); + if ( error != NO_ERROR ) { + this->camera.log_error( function, "couldn't open fits file" ); + return error; + } + } - // waittime is 10% over the specified readout time - // and will be used to keep track of timeout errors - // - double waittime; - try { - waittime = this->camera.readout_time.at(0) * 1.1; // this is in msec - } catch (std::out_of_range &) { - message.str(""); - message << "readout time for Archon not found from config file"; - this->camera.log_error(function, message.str()); - return ERROR; - } + // //TODO only use camera_info -- don't use fits_info -- is this OK? TO BE CONFIRMED + // this->fits_info = this->camera_info; // copy the camera_info class, to be given to fits writer //TODO - double clock_now = get_clock_time(); // get_clock_time returns seconds - double clock_timeout = clock_now + waittime / 1000.; // must receive frame by this time + // this->lastframe = this->frame.bufframen[this->frame.index]; // save the last frame number acquired (wait_for_readout will need this) - // Poll frame status until current frame is not the last frame and the buffer is ready to read. - // The last frame was recorded before the readout was triggered in get_frame(). - // - while (!done && !this->abort) { - if (this->is_longexposure) usleep(10000); // reduces polling frequency - error = this->get_frame_status(); + if (nseq > 1) { + message.str(""); message << "starting sequence of " << nseq << " frames. lastframe=" << this->lastframe; + logwrite(function, message.str()); + } - // If Archon is busy then ignore it, keep trying for up to ~ 3 second - // (300 attempts, ~10000us between attempts) - // - if (error == BUSY) { - if (++busycount > 300) { - done = true; - this->camera.log_error( - function, "received BUSY from Archon too many times trying to get frame status"); - break; - } else continue; - } else busycount = 0; - - if (error == ERROR) { - done = true; - logwrite(function, "ERROR: unable to get frame status"); - break; - } + // If not RAW mode then wait for Archon frame buffer to be ready, + // then read the latest ready frame buffer to the host. If this + // is a squence, then loop over all expected frames. + // + if ( mode != "RAW" ) { // If not raw mode then + int expcount = 0; // counter used only for tracking pre-exposures + + // + // -- MAIN SEQUENCE LOOP -- + // + while (nseq-- > 0) { + + // Wait for any pre-exposures, first the exposure delay then the readout, + // but then continue to the next because pre-exposures are not read from + // the Archon's buffer. + // + if ( ++expcount <= this->camera_info.num_pre_exposures ) { + + message.str(""); message << "pre-exposure " << expcount << " of " << this->camera_info.num_pre_exposures; + logwrite( function, message.str() ); + + if ( this->camera_info.exposure_time != 0 ) { // wait for the exposure delay to complete (if there is one) + error = this->wait_for_exposure(); + if ( error != NO_ERROR ) { + logwrite( function, "ERROR: waiting for pre-exposure" ); + return error; + } + } + + error = this->wait_for_readout(); // Wait for the readout into frame buffer, + if ( error != NO_ERROR ) { + logwrite( function, "ERROR: waiting for pre-exposure readout" ); + return error; + } + + continue; + } - // get current frame number and check the status of its buffer - currentframe = this->frame.bufframen[this->frame.index]; - if ((currentframe != this->lastframe) && (this->frame.bufcomplete[this->frame.index] == 1)) { - done = true; - error = NO_ERROR; - break; - } + // Open a new FITS file for each frame when not using datacubes + // + #ifdef LOGLEVEL_DEBUG + message.str(""); message << "[DEBUG] datacube=" << this->camera.datacube() << " cubeamps=" << this->camera.cubeamps(); + logwrite( function, message.str() ); + #endif + if ( !this->camera.datacube() || this->camera.cubeamps() ) { + this->camera_info.start_time = get_timestamp(); // current system time formatted as YYYY-MM-DDTHH:MM:SS.sss + if ( this->get_timer(&this->start_timer) != NO_ERROR ) { // Archon internal timer (one tick=10 nsec) + logwrite( function, "ERROR: could not get start time" ); + return ERROR; + } + this->camera.set_fitstime(this->camera_info.start_time); // sets camera.fitstime (YYYYMMDDHHMMSS) used for filename + error=this->camera.get_fitsname(this->camera_info.fits_name); // Assemble the FITS filename + if ( error != NO_ERROR ) { + logwrite( function, "ERROR: couldn't validate fits filename" ); + return error; + } + this->add_filename_key(); // add filename to system keys database + + #ifdef LOGLEVEL_DEBUG + logwrite( function, "[DEBUG] reset extension=0 and opening new fits file" ); + #endif + // reset the extension number and open the fits file + // + this->camera_info.extension = 0; + error = this->fits_file.open_file( + this->camera.writekeys_when == "before", this->camera_info ); + if ( error != NO_ERROR ) { + this->camera.log_error( function, "couldn't open fits file" ); + return error; + } + } - // If the frame isn't done by the predicted time then - // enough time has passed to trigger a timeout error. - // - if (clock_now > clock_timeout) { - done = true; - error = ERROR; - message.str(""); - message << "timeout waiting for new frame exceeded " << waittime << ". lastframe = " << this->lastframe; - this->camera.log_error(function, message.str()); - break; - } - clock_now = get_clock_time(); + if ( this->camera_info.exposure_time != 0 ) { // wait for the exposure delay to complete (if there is one) + error = this->wait_for_exposure(); + if ( error != NO_ERROR ) { + logwrite( function, "ERROR: waiting for exposure" ); + return error; + } + } - // ASYNC status message reports the number of lines read so far, - // which is buflines not from this->frame.index but from the NEXT index... - // - message.str(""); - message << "LINECOUNT:" << this->frame.buflines[this->frame.next_index]; -#ifdef LOGLEVEL_DEBUG - message << " [DEBUG] "; - message << " index=" << this->frame.index << " next_index=" << this->frame.next_index << " | "; - for ( int i=0; i < Archon::nbufs; i++ ) { message << " " << this->frame.buflines[ i ]; } -#endif - this->camera.async.enqueue(message.str()); - } // end while (!done && !this->abort) + if (this->camera.writekeys_when=="after") this->copy_keydb(); // copy the ACF and userkeys database into camera_info - // After exiting while loop, one update to ensure accurate ASYNC message - // reporting of LINECOUNT. - // - if (error == NO_ERROR) { - error = this->get_frame_status(); - if (error != NO_ERROR) { - logwrite(function, "ERROR: unable to get frame status"); - return error; - } - message.str(""); - message << "LINECOUNT:" << this->frame.buflines[this->frame.index]; - this->camera.async.enqueue(message.str()); - } + error = this->wait_for_readout(); // Wait for the readout into frame buffer, - if (error != NO_ERROR) { - return error; - } + if ( error != NO_ERROR ) { + logwrite( function, "ERROR: waiting for readout" ); + this->fits_file.close_file( + this->camera.writekeys_when == "after", this->camera_info ); + return error; + } -#ifdef LOGLEVEL_DEBUG - message.str(""); - message << "[DEBUG] lastframe=" << this->lastframe - << " currentframe=" << currentframe - << " bufcomplete=" << this->frame.bufcomplete[this->frame.index]; - logwrite(function, message.str()); -#endif - this->lastframe = currentframe; + error = read_frame(); // then read the frame buffer to host (and write file) when frame ready. + if ( error != NO_ERROR ) { + logwrite( function, "ERROR: reading frame buffer" ); + this->fits_file.close_file( + this->camera.writekeys_when == "after", this->camera_info ); + return error; + } - // On success, write the value to the log and return - // - if (!this->abort) { - message.str(""); - message << "received currentframe: " << currentframe << " from buffer " << this->frame.index + 1; - logwrite(function, message.str()); - return NO_ERROR; - } else if (this->abort) { - // If the wait was stopped, log a message and return NO_ERROR - logwrite(function, "wait for readout stopped by external signal"); - return NO_ERROR; - } else { - // Throw an error for any other errors (should be impossible) - this->camera.log_error(function, "waiting for readout"); - return error; - } - } + // For non-sequence multiple exposures, including cubeamps, close the fits file here + // + if ( !this->camera.datacube() || this->camera.cubeamps() ) { // Error or not, close the file. + #ifdef LOGLEVEL_DEBUG + logwrite( function, "[DEBUG] closing fits file (1)" ); + #endif + this->fits_file.close_file( + this->camera.writekeys_when == "after", this->camera_info ); // close the file when not using datacubes + this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" + + // ASYNC status message on completion of each file + // + message.str(""); message << "FILE:" << this->camera_info.fits_name << " COMPLETE"; + this->camera.async.enqueue( message.str() ); + logwrite( function, message.str() ); + } - /**************** Archon::Interface::wait_for_readout ***********************/ + if (error != NO_ERROR) break; // should be impossible but don't try additional sequences if there were errors + + } // end of sequence loop, while (nseq-- > 0) + + } else if ( mode == "RAW") { + error = this->get_frame_status(); // Get the current frame buffer status + if (error != NO_ERROR) { + logwrite( function, "ERROR: unable to get frame status" ); + return ERROR; + } + error = this->camera.get_fitsname( this->camera_info.fits_name ); // Assemble the FITS filename + if ( error != NO_ERROR ) { + logwrite( function, "ERROR: couldn't validate fits filename" ); + return error; + } + this->add_filename_key(); // add filename to system keys database + + this->camera_info.systemkeys.keydb = this->systemkeys.keydb; // copy the systemkeys database into camera_info + + this->copy_keydb(); // copy the ACF and userkeys databases into camera_info + + error = this->fits_file.open_file( + this->camera.writekeys_when == "before", this->camera_info ); + if ( error != NO_ERROR ) { + this->camera.log_error( function, "couldn't open fits file" ); + return error; + } + error = read_frame(); // For raw mode just read immediately + this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info ); + this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" + } + // for multi-exposure (non-cubeamp) cubes, close the FITS file now that they've all been written + // + if ( this->camera.datacube() && !this->camera.cubeamps() ) { + #ifdef LOGLEVEL_DEBUG + logwrite( function, "[DEBUG] closing fits file (2)" ); + #endif + this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info ); + this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" + + // ASYNC status message on completion of each file + // + message.str(""); message << "FILE:" << this->camera_info.fits_name << " " << ( error==NO_ERROR ? "COMPLETE" : "ERROR" ); + this->camera.async.enqueue( message.str() ); + error == NO_ERROR ? logwrite( function, message.str() ) : this->camera.log_error( function, message.str() ); + } - /**************** Archon::Interface::hwait_for_readout ***********************/ - /** - * @fn hwait_for_readout - * @brief creates a wait until the next frame buffer is ready - * @param none - * @return ERROR or NO_ERROR - * - * This function polls the Archon frame status until a new frame is ready. - * - */ - long Interface::hwait_for_readout() { - std::string function = "Archon::Interface::hwait_for_readout"; - std::stringstream message; - long error = NO_ERROR; - int currentframe = this->lastframe + 1; + // remember the cubeamps setting used for the last completed exposure + // TODO revisit once region-of-interest is implemented + // + this->lastcubeamps = this->camera.cubeamps(); - message.str(""); - message << "waiting for new frame: current frame=" << this->lastframe << " current buffer=" << this->frame.index - + 1; - logwrite(function, message.str()); + return (error); + } + /**************** Archon::Interface::video *********************************/ - usleep(700); // tune for size of window - this->frame.index += 1; + /**************** Archon::Interface::wait_for_exposure **********************/ + /** + * @fn wait_for_exposure + * @brief creates a wait until the exposure delay has completed + * @param none + * @return ERROR or NO_ERROR + * + * This is not the actual exposure delay, nor does it accurately time the exposure + * delay. This function merely creates a reasonably accurate wait on the host to + * allow time for the Archon to complete its exposure delay. This is done by using + * the exposure time given to the Archon and by using the Archon's internal timer, + * which is queried here. There is no sense in polling the Archon's timer for the + * entire exposure time, so this function waits internally for about 90% of the + * exposure time, then only starts polling the Archon for the remaining time. + * + * A prediction is made of what the Archon's timer will be at the end, in order + * to provide an estimate of completion. + * + */ + long Interface::wait_for_exposure() { + std::string function = "Archon::Interface::wait_for_exposure"; + std::stringstream message; + long error = NO_ERROR; + + int exposure_timeout_time; // Time to wait for the exposure delay to time out + unsigned long int timer, increment=0; + + // For long exposures, waittime is 1 second less than the exposure time. + // For short exposures, waittime is an integral number of msec below 90% of the exposure time + // and will be used to keep track of elapsed time, for timeout errors. + // + double waittime; + if ( this->is_longexposure ) { + waittime = this->camera_info.exposure_time - 1; - // Wrap frame.index - if (this->frame.index >= (int) this->frame.bufframen.size()) { - this->frame.index = 0; - } + } else { + waittime = floor( 0.9 * this->camera_info.exposure_time / this->camera_info.exposure_factor ); // in units of this->camera_info.exposure_unit + } - this->frame.bufframen[this->frame.index] = currentframe; - this->frame.frame = currentframe; + // Wait, (don't sleep) for the above waittime. + // This is a period that could be aborted by setting the this->abort flag. //TODO not yet implemented? + // All that is happening here is a wait -- there is no Archon polling going on here. + // + double start_time = get_clock_time(); // get the current clock time from host (in seconds) + double now = start_time; -#ifdef LOGLEVEL_DEBUG - message.str(""); - message << "[DEBUG] lastframe=" << this->lastframe - << " currentframe=" << currentframe - << " bufcomplete=" << this->frame.bufcomplete[this->frame.index]; - logwrite(function, message.str()); -#endif - this->lastframe = currentframe; + // Prediction is the predicted finish_timer, used to compute exposure time progress, + // and is computed as start_time + exposure_time in Archon ticks. + // Each Archon tick is 10 nsec (1e8 sec). Divide by exposure_factor (=1 for sec, =1000 for msec). + // + unsigned long int prediction = this->start_timer + this->camera_info.exposure_time * 1e8 / this->camera_info.exposure_factor; + + std::cerr << "exposure progress: "; + while ((now - (waittime + start_time) < 0) && !this->abort) { + // sleep 10 msec = 1e6 Archon ticks + std::this_thread::sleep_for( std::chrono::milliseconds( 10 )); + increment += 1000000; + now = get_clock_time(); + this->camera_info.exposure_progress = (double)increment / (double)(prediction - this->start_timer); + if (this->camera_info.exposure_progress < 0 || this->camera_info.exposure_progress > 1) this->camera_info.exposure_progress=1; + std::cerr << std::setw(3) << (int)(this->camera_info.exposure_progress*100) << "\b\b\b"; + + // ASYNC status message reports the elapsed time in the chosen unit + // + message.str(""); message << "EXPOSURE:" << (int)(this->camera_info.exposure_time - (this->camera_info.exposure_progress * this->camera_info.exposure_time)); + this->camera.async.enqueue( message.str() ); + } - // On success, write the value to the log and return - // - if (!this->abort) { - message.str(""); - message << "received currentframe: " << currentframe << " from buffer " << this->frame.index + 1; - logwrite(function, message.str()); - return NO_ERROR; - } else if (this->abort) { - // If the wait was stopped, log a message and return NO_ERROR - logwrite(function, "wait for readout stopped by external signal"); - return NO_ERROR; - } else { - // Throw an error for any other errors (should be impossible) - this->camera.log_error(function, "waiting for readout"); - return error; - } + if (this->abort) { + std::cerr << "\n"; + logwrite(function, "exposure aborted"); + return NO_ERROR; } - /**************** Archon::Interface::hwait_for_readout ***********************/ + // Set the time-out value in ms. If the exposure time is less than a second, set + // the timeout to 1 second. Otherwise, set it to the exposure time plus + // 1 second. + // + if ( this->camera_info.exposure_time / this->camera_info.exposure_factor < 1 ) { + exposure_timeout_time = 1000; //ms + } else { + exposure_timeout_time = (this->camera_info.exposure_time / this->camera_info.exposure_factor) * 1000 + 1000; + } - /**************** Archon::Interface::get_parameter **************************/ - /** - * @fn get_parameter - * @brief get parameter using read_parameter() - * @param string - * @return ERROR or NO_ERROR - * - */ - long Interface::get_parameter(std::string parameter, std::string &retstring) { - std::string function = "Archon::Interface::get_parameter"; + // Now start polling the Archon for the last remaining portion of the exposure delay + // + bool done = false; + while (!done && !this->abort) { + // Poll Archon's internal timer + // + if ( (error=this->get_timer(&timer)) == ERROR ) { + std::cerr << "\n"; + logwrite( function, "ERROR: could not get Archon timer" ); + break; + } - return this->read_parameter(parameter, retstring); - } + // update progress + // + this->camera_info.exposure_progress = (double)(timer - this->start_timer) / (double)(prediction - this->start_timer); + if (this->camera_info.exposure_progress < 0 || this->camera_info.exposure_progress > 1) this->camera_info.exposure_progress=1; + + // ASYNC status message reports the elapsed time in the chosen unit + // + message.str(""); message << "EXPOSURE:" << (int)(this->camera_info.exposure_time - (this->camera_info.exposure_progress * this->camera_info.exposure_time)); + this->camera.async.enqueue( message.str() ); + + std::cerr << std::setw(3) << (int)(this->camera_info.exposure_progress*100) << "\b\b\b"; // send to stderr in case anyone is watching + + // Archon timer ticks are in 10 nsec (1e-8) so when comparing to exposure_time, + // multiply exposure_time by 1e8/exposure_factor, where exposure_factor=1 or =1000 for exposure_unit sec or msec. + // + if ( (timer - this->start_timer) >= ( this->camera_info.exposure_time * 1e8 / this->camera_info.exposure_factor ) ) { + this->finish_timer = timer; + done = true; + break; + } - /**************** Archon::Interface::get_parameter **************************/ + // a little pause to slow down the requests to Archon + std::this_thread::sleep_for( std::chrono::milliseconds( 1 )); + // Added protection against infinite loops, probably will never be invoked + // because an Archon error getting the timer would exit the loop. + // exposure_timeout_time is in msec, and it's a little more than 1 msec to get + // through this loop. If this is decremented each time through then it should + // never hit zero before the exposure is finished, unless there is a serious + // problem. + // + if (--exposure_timeout_time < 0) { + std::cerr << "\n"; + error = ERROR; + this->camera.log_error( function, "timeout waiting for exposure" ); + break; + } + } // end while (done == false && this->abort == false) + std::cerr << "\n"; - /**************** Archon::Interface::set_parameter **************************/ - /** - * @fn set_parameter - * @brief set an Archon parameter - * @param string - * @return ERROR or NO_ERROR - * - * This function calls "prep_parameter()" and "load_parameter()" - * - */ - long Interface::set_parameter(std::string parameter, long value) { - std::stringstream paramstring; - paramstring << parameter << " " << value; - return (set_parameter(paramstring.str())); + if (this->abort) { + logwrite(function, "exposure aborted"); } - long Interface::set_parameter(std::string parameter) { - std::string function = "Archon::Interface::set_parameter"; - std::stringstream message; - long ret = ERROR; - std::vector tokens; + return error; + } + /**************** Archon::Interface::wait_for_exposure **********************/ + + + /**************** Archon::Interface::wait_for_readout ***********************/ + /** + * @fn wait_for_readout + * @brief creates a wait until the next frame buffer is ready + * @param none + * @return ERROR or NO_ERROR + * + * This function polls the Archon frame status until a new frame is ready. + * + */ + long Interface::wait_for_readout() { + std::string function = "Archon::Interface::wait_for_readout"; + std::stringstream message; + long error = NO_ERROR; + int currentframe=this->lastframe; + int busycount=0; + bool done = false; - Tokenize(parameter, tokens, " "); + message.str(""); + message << "waiting for new frame: current frame=" << this->lastframe << " current buffer=" << this->frame.index+1; + logwrite(function, message.str()); - if (tokens.size() != 2) { - message.str(""); - message << "param expected 2 arguments (paramname and value) but got " << tokens.size(); - this->camera.log_error(function, message.str()); - ret = ERROR; - } else { - ret = this->prep_parameter(tokens[0], tokens[1]); - if (ret == NO_ERROR) ret = this->load_parameter(tokens[0], tokens[1]); - } - return ret; + // waittime is 10% over the specified readout time + // and will be used to keep track of timeout errors + // + double waittime; + try { + waittime = this->camera.readout_time.at(0) * 1.1; // this is in msec + + } catch(std::out_of_range &) { + message.str(""); message << "readout time for Archon not found from config file"; + this->camera.log_error( function, message.str() ); + return ERROR; } - /**************** Archon::Interface::set_parameter **************************/ + double clock_now = get_clock_time(); // get_clock_time returns seconds + double clock_timeout = clock_now + waittime/1000.; // must receive frame by this time + // Poll frame status until current frame is not the last frame and the buffer is ready to read. + // The last frame was recorded before the readout was triggered in get_frame(). + // + while (!done && !this->abort) { - /**************** Archon::Interface::exptime ********************************/ - /** - * @fn exptime - * @brief set/get the exposure time - * @param string - * @return ERROR or NO_ERROR - * - * This function calls "set_parameter()" and "get_parameter()" using - * the "exptime" parameter (which must already be defined in the ACF file). - * - */ - long Interface::exptime(std::string exptime_in, std::string &retstring) { - std::string function = "Archon::Interface::exptime"; - std::stringstream message; - long ret = NO_ERROR; - int32_t exp_time = -1; + if (this->is_longexposure) usleep( 10000 ); // reduces polling frequency + error = this->get_frame_status(); - if (!exptime_in.empty()) { - // Convert to integer to check the value - // - try { - exp_time = std::stoi(exptime_in); - } catch (std::invalid_argument &) { - message.str(""); - message << "converting exposure time: " << exptime_in << " to integer"; - this->camera.log_error(function, message.str()); - return ERROR; - } catch (std::out_of_range &) { - message.str(""); - message << "requested exposure time: " << exptime_in << " outside integer range"; - this->camera.log_error(function, message.str()); - return ERROR; - } + // If Archon is busy then ignore it, keep trying for up to ~ 3 second + // (300 attempts, ~10000us between attempts) + // + if (error == BUSY) { + if ( ++busycount > 300 ) { + done = true; + this->camera.log_error( function, "received BUSY from Archon too many times trying to get frame status" ); + break; - // Archon allows only 20 bit parameters - // - if (exp_time < 0 || exp_time > 0xFFFFF) { - message.str(""); - message << "requested exposure time: " << exptime_in << " out of range {0:1048575}"; - this->camera.log_error(function, message.str()); - return ERROR; - } + } else continue; - // Now that the value is OK set the parameter on the Archon - // - std::stringstream cmd; - cmd << "exptime " << exptime_in; - ret = this->set_parameter(cmd.str()); + } else busycount=0; - // If parameter was set OK then save the new value - // - if (ret == NO_ERROR) this->camera_info.exposure_time = exp_time; - } + if (error == ERROR) { + done = true; + logwrite( function, "ERROR: unable to get frame status" ); + break; + } - // add exposure time to system keys db - // - message.str(""); - message << "EXPTIME=" << this->camera_info.exposure_time << " // exposure time in " << ( - this->is_longexposure ? "sec" : "msec"); - this->systemkeys.addkey(message.str()); + // get current frame number and check the status of its buffer + currentframe = this->frame.bufframen[this->frame.index]; + if ( (currentframe != this->lastframe) && (this->frame.bufcomplete[this->frame.index]==1) ) { + done = true; + error = NO_ERROR; + break; + } + + // If the frame isn't done by the predicted time then + // enough time has passed to trigger a timeout error. + // + if (clock_now > clock_timeout) { + done = true; + error = ERROR; + message.str(""); message << "timeout waiting for new frame exceeded " << waittime << ". lastframe = " << this->lastframe; + this->camera.log_error( function, message.str() ); + break; + } + clock_now = get_clock_time(); + + // ASYNC status message reports the number of lines read so far, + // which is buflines not from this->frame.index but from the NEXT index... + // + message.str(""); message << "LINECOUNT:" << this->frame.buflines[ this->frame.next_index ]; + #ifdef LOGLEVEL_DEBUG + message << " [DEBUG] "; + message << " index=" << this->frame.index << " next_index=" << this->frame.next_index << " | "; + for ( int i=0; i < Archon::nbufs; i++ ) { message << " " << this->frame.buflines[ i ]; } + #endif + this->camera.async.enqueue( message.str() ); + + } // end while (!done && !this->abort) + + // After exiting while loop, one update to ensure accurate ASYNC message + // reporting of LINECOUNT. + // + if ( error == NO_ERROR ) { + error = this->get_frame_status(); + if ( error != NO_ERROR ) { + logwrite( function, "ERROR: unable to get frame status" ); + return error; + } + message.str(""); message << "LINECOUNT:" << this->frame.buflines[ this->frame.index ]; + this->camera.async.enqueue( message.str() ); + } + + if ( error != NO_ERROR ) { + return error; + } + + #ifdef LOGLEVEL_DEBUG + message.str(""); + message << "[DEBUG] lastframe=" << this->lastframe + << " currentframe=" << currentframe + << " bufcomplete=" << this->frame.bufcomplete[this->frame.index]; + logwrite(function, message.str()); + #endif + this->lastframe = currentframe; - // prepare the return value - // - message.str(""); - message << this->camera_info.exposure_time << (this->is_longexposure ? " sec" : " msec"); - retstring = message.str(); + // On success, write the value to the log and return + // + if (!this->abort) { + message.str(""); + message << "received currentframe: " << currentframe << " from buffer " << this->frame.index+1; + logwrite(function, message.str()); + return NO_ERROR; - message.str(""); - message << "exposure time is " << retstring; - logwrite(function, message.str()); + } else if (this->abort) { + // If the wait was stopped, log a message and return NO_ERROR + logwrite(function, "wait for readout stopped by external signal"); + return NO_ERROR; - return ret; + } else { + // Throw an error for any other errors (should be impossible) + this->camera.log_error( function, "waiting for readout" ); + return error; } - - /**************** Archon::Interface::exptime ********************************/ + } + /**************** Archon::Interface::wait_for_readout ***********************/ - /** Camera::Camera::shutter *************************************************/ + /**************** Archon::Interface::hwait_for_readout ***********************/ /** - * @fn shutter - * @brief set or get the shutter enable state - * @param std::string shutter_in - * @param std::string& shutter_out + * @fn hwait_for_readout + * @brief creates a wait until the next frame buffer is ready + * @param none * @return ERROR or NO_ERROR * + * This function polls the Archon frame status until a new frame is ready. + * */ - long Interface::shutter(std::string shutter_in, std::string &shutter_out) { - std::string function = "Archon::Interface::shutter"; + long Interface::hwait_for_readout() { + std::string function = "Archon::Interface::hwait_for_readout"; std::stringstream message; long error = NO_ERROR; - int level = 0, force = 0; // trigout level and force for activate + int currentframe=this->lastframe + 1; - if (this->shutenableparam.empty()) { - this->camera.log_error(function, "SHUTENABLE_PARAM is not defined in configuration file"); - return ERROR; - } + message.str(""); + message << "waiting for new frame: current frame=" << this->lastframe << " current buffer=" << this->frame.index+1; + logwrite(function, message.str()); - if (!shutter_in.empty()) { - try { - bool shutten; // shutter enable state read from command - bool ability = false; // are we going to change the ability (enable/disable)? - bool activate = false; // are we going to activate the shutter (open/close)? - std::string activate_str; - bool dontcare; - std::transform(shutter_in.begin(), shutter_in.end(), shutter_in.begin(), ::tolower); // make lowercase - if (shutter_in == "disable") { - ability = true; - shutten = false; - } else if (shutter_in == "enable") { - ability = true; - shutten = true; - } else if (shutter_in == "open") { - activate = true; - force = 1; - level = 1; - activate_str = "open"; - } else if (shutter_in == "close") { - activate = true; - force = 1; - level = 0; - activate_str = "closed"; - } else if (shutter_in == "reset") { - activate = true; - force = 0; - level = 0; - activate_str = ""; - // shutter back to normal operation; - // shutter force level keyword has no context so remove it from the system keys db - // - this->systemkeys.EraseKeys("SHUTFORC"); - } else { - message.str(""); - message << shutter_in << " is invalid. Expecting { enable | disable | open | close | reset }"; - this->camera.log_error(function, message.str()); - error = ERROR; - } - // if changing the ability (enable/disable) then do that now - if (error == NO_ERROR && ability) { - std::stringstream cmd; - cmd << this->shutenableparam << " " << ( - shutten ? this->shutenable_enable : this->shutenable_disable); - error = this->set_parameter(cmd.str()); + usleep( 700 ); // tune for size of window - // If parameter was set OK then save the new value - if (error == NO_ERROR) this->camera_info.shutterenable = shutten; - } - // if changing the activation (open/close/reset) then do that now - if (error == NO_ERROR && activate) { - if (this->configmap.find("TRIGOUTFORCE") != this->configmap.end()) { - error = this->write_config_key("TRIGOUTFORCE", force, dontcare); - } else { - this->camera.log_error(function, "TRIGOUTFORCE not found in configmap"); - error = ERROR; - } + this->frame.index += 1; - if (this->configmap.find("TRIGOUTLEVEL") != this->configmap.end()) { - if (error == NO_ERROR) error = this->write_config_key("TRIGOUTLEVEL", level, dontcare); - } else { - this->camera.log_error(function, "TRIGOUTLEVEL not found in configmap"); - error = ERROR; - } - if (error == NO_ERROR) error = this->archon_cmd(APPLYSYSTEM); - if (error == NO_ERROR) { this->camera_info.shutteractivate = activate_str; } - } - } catch (...) { - message.str(""); - message << "converting requested shutter state: " << shutter_in << " to lowercase"; - this->camera.log_error(function, message.str()); - return ERROR; - } + // Wrap frame.index + if (this->frame.index >= (int)this->frame.bufframen.size()) { + this->frame.index = 0; } - // set the return value and report the state now, either setting or getting - // - shutter_out = this->camera_info.shutterenable ? "enabled" : "disabled"; - - // if the shutteractivate string is not empty then use it for the return string, instead - // - if (!this->camera_info.shutteractivate.empty()) shutter_out = this->camera_info.shutteractivate; + this->frame.bufframen[ this->frame.index ] = currentframe; + this->frame.frame = currentframe; +#ifdef LOGLEVEL_DEBUG message.str(""); - message << "shutter is " << shutter_out; - logwrite(function, message.str()); + message << "[DEBUG] lastframe=" << this->lastframe + << " currentframe=" << currentframe + << " bufcomplete=" << this->frame.bufcomplete[this->frame.index]; + logwrite(function, message.str()); +#endif + this->lastframe = currentframe; - // If the shutter was forced open or closed then add that to the system keys db + // On success, write the value to the log and return // - if (force) { + if (!this->abort) { message.str(""); - message << "SHUTFORC=" << level << "// shutter force level"; - this->systemkeys.addkey(message.str()); + message << "received currentframe: " << currentframe << " from buffer " << this->frame.index+1; + logwrite(function, message.str()); + return NO_ERROR; + + } else if (this->abort) { + // If the wait was stopped, log a message and return NO_ERROR + logwrite(function, "wait for readout stopped by external signal"); + return NO_ERROR; + + } else { + // Throw an error for any other errors (should be impossible) + this->camera.log_error( function, "waiting for readout" ); + return error; } + } + /**************** Archon::Interface::hwait_for_readout ***********************/ - // Add the shutter enable keyword to the system keys db - // - message.str(""); - message << "SHUTTEN=" << (this->camera_info.shutterenable ? "T" : "F") << "// shutter was enabled"; - this->systemkeys.addkey(message.str()); - return error; + /**************** Archon::Interface::get_parameter **************************/ + /** + * @fn get_parameter + * @brief get parameter using read_parameter() + * @param string + * @return ERROR or NO_ERROR + * + */ + long Interface::get_parameter(std::string parameter, std::string &retstring) { + std::string function = "Archon::Interface::get_parameter"; + + return this->read_parameter(parameter, retstring); + } + /**************** Archon::Interface::get_parameter **************************/ + + + /**************** Archon::Interface::set_parameter **************************/ + /** + * @fn set_parameter + * @brief set an Archon parameter + * @param string + * @return ERROR or NO_ERROR + * + * This function calls "prep_parameter()" and "load_parameter()" + * + */ + long Interface::set_parameter( std::string parameter, long value ) { + std::stringstream paramstring; + paramstring << parameter << " " << value; + return( set_parameter( paramstring.str() ) ); + } + long Interface::set_parameter(std::string parameter) { + std::string function = "Archon::Interface::set_parameter"; + std::stringstream message; + long ret=ERROR; + std::vector tokens; + + Tokenize(parameter, tokens, " "); + + if (tokens.size() != 2) { + message.str(""); message << "param expected 2 arguments (paramname and value) but got " << tokens.size(); + this->camera.log_error( function, message.str() ); + ret=ERROR; + + } else { + ret = this->prep_parameter(tokens[0], tokens[1]); + if (ret == NO_ERROR) ret = this->load_parameter(tokens[0], tokens[1]); } + return ret; + } + /**************** Archon::Interface::set_parameter **************************/ + + + /**************** Archon::Interface::exptime ********************************/ + /** + * @fn exptime + * @brief set/get the exposure time + * @param string + * @return ERROR or NO_ERROR + * + * This function calls "set_parameter()" and "get_parameter()" using + * the "exptime" parameter (which must already be defined in the ACF file). + * + */ + long Interface::exptime(std::string exptime_in, std::string &retstring) { + std::string function = "Archon::Interface::exptime"; + std::stringstream message; + long ret=NO_ERROR; + int32_t exp_time = -1; + + if ( !exptime_in.empty() ) { + // Convert to integer to check the value + // + try { + exp_time = std::stoi( exptime_in ); + + } catch (std::invalid_argument &) { + message.str(""); message << "converting exposure time: " << exptime_in << " to integer"; + this->camera.log_error( function, message.str() ); + return ERROR; + + } catch (std::out_of_range &) { + message.str(""); message << "requested exposure time: " << exptime_in << " outside integer range"; + this->camera.log_error( function, message.str() ); + return ERROR; + } - /** Camera::Camera::shutter *************************************************/ + // Archon allows only 20 bit parameters + // + if ( exp_time < 0 || exp_time > 0xFFFFF ) { + message.str(""); message << "requested exposure time: " << exptime_in << " out of range {0:1048575}"; + this->camera.log_error( function, message.str() ); + return ERROR; + } + // Now that the value is OK set the parameter on the Archon + // + std::stringstream cmd; + cmd << "exptime " << exptime_in; + ret = this->set_parameter( cmd.str() ); - /**************** Archon::Interface::hdrshift *******************************/ - /** - * @fn hdrshift - * @brief set/get number of hdrshift bits - * @param bits_in - * @param &bits_out - * @return ERROR or NO_ERROR - * - * This function sets (or gets) this->n_hdrshift. - * - * In HDR mode (i.e. SAMPLEMODE=1, 32 bits per pixel) this->write_frame() - * will right-shift the Archon data buffer by this->n_hdrshift. - * - */ - long Interface::hdrshift(std::string bits_in, std::string &bits_out) { - std::string function = "Archon::Interface::hdrshift"; - std::stringstream message; - int hdrshift_req = -1; + // If parameter was set OK then save the new value + // + if ( ret==NO_ERROR ) this->camera_info.exposure_time = exp_time; + } - // If something is passed then try to use it to set the number of hdrshifts - // - if (!bits_in.empty()) { - try { - hdrshift_req = std::stoi(bits_in); - } catch (std::invalid_argument &) { - message.str(""); - message << "converting hdrshift: " << bits_in << " to integer"; - this->camera.log_error(function, message.str()); - return ERROR; - } catch (std::out_of_range &) { - message.str(""); - message << "hdrshift: " << bits_in << " is outside integer range"; - this->camera.log_error(function, message.str()); - return ERROR; - } - } + // add exposure time to system keys db + // + message.str(""); message << "EXPTIME=" << this->camera_info.exposure_time << " // exposure time in " << ( this->is_longexposure ? "sec" : "msec" ); + this->systemkeys.addkey( message.str() ); - if (hdrshift_req < 0 || hdrshift_req > 31) { - this->camera.log_error(function, "hdrshift outside range {0:31}"); - return ERROR; - } else this->n_hdrshift = hdrshift_req; + // prepare the return value + // + message.str(""); message << this->camera_info.exposure_time << ( this->is_longexposure ? " sec" : " msec" ); + retstring = message.str(); - // error or not, the number of bits reported now will be whatever was last successfully set - // - bits_out = std::to_string(this->n_hdrshift); + message.str(""); message << "exposure time is " << retstring; + logwrite(function, message.str()); - // add to system keyword database - // - std::stringstream keystr; - keystr << "HDRSHIFT=" << this->n_hdrshift << "// number of HDR right-shift bits"; - this->systemkeys.addkey(keystr.str()); + return ret; + } + /**************** Archon::Interface::exptime ********************************/ + + + /** Camera::Camera::shutter *************************************************/ + /** + * @fn shutter + * @brief set or get the shutter enable state + * @param std::string shutter_in + * @param std::string& shutter_out + * @return ERROR or NO_ERROR + * + */ + long Interface::shutter(std::string shutter_in, std::string& shutter_out) { + std::string function = "Archon::Interface::shutter"; + std::stringstream message; + long error = NO_ERROR; + int level=0, force=0; // trigout level and force for activate + + if ( this->shutenableparam.empty() ) { + this->camera.log_error( function, "SHUTENABLE_PARAM is not defined in configuration file" ); + return ERROR; + } - return NO_ERROR; + if ( !shutter_in.empty() ) { + try { + bool shutten; // shutter enable state read from command + bool ability=false; // are we going to change the ability (enable/disable)? + bool activate=false; // are we going to activate the shutter (open/close)? + std::string activate_str; + bool dontcare; + std::transform( shutter_in.begin(), shutter_in.end(), shutter_in.begin(), ::tolower ); // make lowercase + if ( shutter_in == "disable" ) { + ability = true; + shutten = false; + + } else if ( shutter_in == "enable" ) { + ability = true; + shutten = true; + + } else if ( shutter_in == "open" ) { + activate = true; + force = 1; + level = 1; + activate_str = "open"; + + } else if ( shutter_in == "close" ) { + activate = true; + force = 1; + level = 0; + activate_str = "closed"; + + } else if ( shutter_in == "reset" ) { + activate = true; + force = 0; + level = 0; + activate_str = ""; + // shutter back to normal operation; + // shutter force level keyword has no context so remove it from the system keys db + // + this->systemkeys.EraseKeys( "SHUTFORC" ); + + } else { + message.str(""); message << shutter_in << " is invalid. Expecting { enable | disable | open | close | reset }"; + this->camera.log_error( function, message.str() ); + error = ERROR; + } + // if changing the ability (enable/disable) then do that now + if ( error == NO_ERROR && ability ) { + std::stringstream cmd; + cmd << this->shutenableparam << " " << ( shutten ? this->shutenable_enable : this->shutenable_disable ); + error = this->set_parameter( cmd.str() ); + + // If parameter was set OK then save the new value + if ( error == NO_ERROR ) this->camera_info.shutterenable = shutten; + } + // if changing the activation (open/close/reset) then do that now + if ( error == NO_ERROR && activate ) { + if ( this->configmap.find( "TRIGOUTFORCE" ) != this->configmap.end() ) { + error = this->write_config_key( "TRIGOUTFORCE", force, dontcare ); + + } else { + this->camera.log_error( function, "TRIGOUTFORCE not found in configmap" ); + error = ERROR; + } + + if ( this->configmap.find( "TRIGOUTLEVEL" ) != this->configmap.end() ) { + if ( error == NO_ERROR) error = this->write_config_key( "TRIGOUTLEVEL", level, dontcare ); + + } else { + this->camera.log_error( function, "TRIGOUTLEVEL not found in configmap" ); + error = ERROR; + } + if ( error == NO_ERROR ) error = this->archon_cmd( APPLYSYSTEM ); + if ( error == NO_ERROR ) { this->camera_info.shutteractivate = activate_str; } + } + + } catch (...) { + message.str(""); message << "converting requested shutter state: " << shutter_in << " to lowercase"; + this->camera.log_error( function, message.str() ); + return ERROR; + } } - /**************** Archon::Interface::hdrshift *******************************/ + // set the return value and report the state now, either setting or getting + // + shutter_out = this->camera_info.shutterenable ? "enabled" : "disabled"; + // if the shutteractivate string is not empty then use it for the return string, instead + // + if ( !this->camera_info.shutteractivate.empty() ) shutter_out = this->camera_info.shutteractivate; - /**************** Archon::Interface::trigin *********************************/ - /** - * @fn trigin - * @brief setup Archon for external triggering of exposures - * @param state_in - * @return ERROR or NO_ERROR - * - * This function sets three Archon parameters, as defined in - * the configuration file for: - * TRIGIN_EXPOSE_PARAM - * TRIGIN_UNTIMED_PARAM - * TRIGIN_READOUT_PARAM - * - * Calling trigin() should be considered analagous to calling expose() - * with the exception that the actual exposure is expected to be triggered - * in the Archon ACF by a TRIGIN pulse. - * - * This trigin() function then assumes that the exposure was started and - * then waits for the exposure delay, waits for readout, reads the frame - * buffer, then writes the frame to disk. In the case of "untimed" then - * it doesn't wait for the exposure delay but returns immediately, - * requiring a second call to trigin() with the argument "readout" in - * order to enter the sequence of wait for readout, read frame buffer, - * write frame to disk. - * - */ - long Interface::trigin(std::string state_in) { - std::string function = "Archon::Interface::trigin"; - std::string newstate; - std::stringstream message; - std::vector tokens; - long error = NO_ERROR; + message.str(""); + message << "shutter is " << shutter_out; + logwrite( function, message.str() ); - std::string mode = this->camera_info.current_observing_mode; // local copy for convenience + // If the shutter was forced open or closed then add that to the system keys db + // + if ( force ) { + message.str(""); message << "SHUTFORC=" << level << "// shutter force level"; + this->systemkeys.addkey( message.str() ); + } - if (!this->modeselected) { - this->camera.log_error(function, "no mode selected"); - return ERROR; - } + // Add the shutter enable keyword to the system keys db + // + message.str(""); message << "SHUTTEN=" << ( this->camera_info.shutterenable ? "T" : "F" ) << "// shutter was enabled"; + this->systemkeys.addkey( message.str() ); + + return error; + } + /** Camera::Camera::shutter *************************************************/ + + + /**************** Archon::Interface::hdrshift *******************************/ + /** + * @fn hdrshift + * @brief set/get number of hdrshift bits + * @param bits_in + * @param &bits_out + * @return ERROR or NO_ERROR + * + * This function sets (or gets) this->n_hdrshift. + * + * In HDR mode (i.e. SAMPLEMODE=1, 32 bits per pixel) this->write_frame() + * will right-shift the Archon data buffer by this->n_hdrshift. + * + */ + long Interface::hdrshift(std::string bits_in, std::string &bits_out) { + std::string function = "Archon::Interface::hdrshift"; + std::stringstream message; + int hdrshift_req=-1; + + // If something is passed then try to use it to set the number of hdrshifts + // + if ( !bits_in.empty() ) { + try { + hdrshift_req = std::stoi( bits_in ); + + } catch ( std::invalid_argument & ) { + message.str(""); message << "converting hdrshift: " << bits_in << " to integer"; + this->camera.log_error( function, message.str() ); + return ERROR; + + } catch ( std::out_of_range & ) { + message.str(""); message << "hdrshift: " << bits_in << " is outside integer range"; + this->camera.log_error( function, message.str() ); + return ERROR; + } + } - std::transform(state_in.begin(), state_in.end(), state_in.begin(), ::tolower); // make lowercase + if ( hdrshift_req < 0 || hdrshift_req > 31 ) { + this->camera.log_error( function, "hdrshift outside range {0:31}" ); + return ERROR; - Tokenize(state_in, tokens, " "); // break into tokens + } else this->n_hdrshift = hdrshift_req; - // Must have 1 or 2 arguments only - // - if (tokens.empty() || tokens.size() > 2) { - message.str(""); - message << "requested trigin state: " << state_in << - " is invalid. Expected { expose [N] | untimed | readout | disable }"; - this->camera.log_error(function, message.str()); - error = ERROR; - } + // error or not, the number of bits reported now will be whatever was last successfully set + // + bits_out = std::to_string( this->n_hdrshift ); - // If the basic checks have failed then get out now, no point in continuing - // - if (error != NO_ERROR) return error; + // add to system keyword database + // + std::stringstream keystr; + keystr << "HDRSHIFT=" << this->n_hdrshift << "// number of HDR right-shift bits"; + this->systemkeys.addkey( keystr.str() ); + + return NO_ERROR; + } + /**************** Archon::Interface::hdrshift *******************************/ + + + /**************** Archon::Interface::trigin *********************************/ + /** + * @fn trigin + * @brief setup Archon for external triggering of exposures + * @param state_in + * @return ERROR or NO_ERROR + * + * This function sets three Archon parameters, as defined in + * the configuration file for: + * TRIGIN_EXPOSE_PARAM + * TRIGIN_UNTIMED_PARAM + * TRIGIN_READOUT_PARAM + * + * Calling trigin() should be considered analagous to calling expose() + * with the exception that the actual exposure is expected to be triggered + * in the Archon ACF by a TRIGIN pulse. + * + * This trigin() function then assumes that the exposure was started and + * then waits for the exposure delay, waits for readout, reads the frame + * buffer, then writes the frame to disk. In the case of "untimed" then + * it doesn't wait for the exposure delay but returns immediately, + * requiring a second call to trigin() with the argument "readout" in + * order to enter the sequence of wait for readout, read frame buffer, + * write frame to disk. + * + */ + long Interface::trigin( std::string state_in ) { + std::string function = "Archon::Interface::trigin"; + std::string newstate; + std::stringstream message; + std::vector tokens; + long error = NO_ERROR; + + std::string mode = this->camera_info.current_observing_mode; // local copy for convenience + + if ( ! this->modeselected ) { + this->camera.log_error( function, "no mode selected" ); + return ERROR; + } - // Now look at the arguments, expecting { expose [N] | untimed | readout } - // and set the Archon parameters appropriately. The assumption is that - // after setting these parameters, the Archon will receive a TRIGIN which - // resets the timing core so that the ACF will take appropriate action - // based on the settings of these parameters. - // - try { - // Ultimately, only will only write the parameter *IF* the parameter has been defined in the config file. - // In other words, make no requirement on having all trigger parameters (TRIGIN_EXPOSE_PARAM, - // TRIGIN_UNTIMED_PARAM, TRIGIN_READOUT_PARAM) defined. But if the "trigin expose" command is sent then - // obviously the TRIGIN_EXPOSE_PARAM must be defined, and so on. - // - if (tokens.at(0) == "expose") { - // timed exposures can take an optional argument - // At minimum, expose requires that TRIGIN_EXPOSE_PARAM be defined - // - if (this->trigin_exposeparam.empty()) { - message.str(""); - message << "TRIGIN_EXPOSE_PARAM not defined in configuration file " << this->config.filename; - this->camera.log_error(function, message.str()); - return ERROR; - } - if (tokens.size() == 1) { - // no size given so = 1 - this->trigin_expose = 1; - this->trigin_untimed = this->trigin_untimed_disable; - this->trigin_readout = this->trigin_readout_disable; - } else { - // size given so try to set trigin_expose to requested value - int expnum = std::stoi(tokens.at(1)); - if (expnum < 1) { - this->camera.log_error(function, "number of timed exposures must be greater than zero"); - error = ERROR; - } else { - this->trigin_expose = expnum; - this->trigin_untimed = this->trigin_untimed_disable; - this->trigin_readout = this->trigin_readout_disable; - } - } - } else if (tokens.at(0) == "untimed") { - // untimed exposures - // At minimum, untimed requires that TRIGIN_UNTIMED_PARAM be defined - if (this->trigin_untimedparam.empty()) { - message.str(""); - message << "TRIGIN_UNTIMED_PARAM not defined in configuration file " << this->config.filename; - this->camera.log_error(function, message.str()); - return ERROR; - } - this->trigin_expose = 0; - this->trigin_untimed = this->trigin_untimed_enable; - this->trigin_readout = this->trigin_readout_disable; - } else if (tokens.at(0) == "readout") { - // readout is used at the end of untimed exposures - // At minimum, readout requires that TRIGIN_READOUT_PARAM be defined - if (this->trigin_readoutparam.empty()) { - message.str(""); - message << "TRIGIN_READOUT_PARAM not defined in configuration file " << this->config.filename; - this->camera.log_error(function, message.str()); - return ERROR; - } - this->trigin_expose = 0; - this->trigin_untimed = this->trigin_untimed_disable; - this->trigin_readout = this->trigin_readout_enable; - } else if (tokens.at(0) == "disable") { - // used to disable untimed exposures - this->trigin_expose = 0; - this->trigin_untimed = this->trigin_untimed_disable; - this->trigin_readout = this->trigin_readout_disable; - } else { - message.str(""); - message << "requested trigin state: " << state_in << - " is invalid. Expected { expose [N] | untimed | readout | disable }"; - this->camera.log_error(function, message.str()); - error = ERROR; - } + std::transform( state_in.begin(), state_in.end(), state_in.begin(), ::tolower ); // make lowercase - // The first token is taken to be the state name. - // Only after the parameters have been successfully written will we set this->trigin_state = newstate - // - newstate = tokens.at(0); - } catch (std::invalid_argument &) { - message.str(""); - message << "converting number of timed external-trigger exposures: " << state_in << " to integer"; - this->camera.log_error(function, message.str()); - error = ERROR; - } catch (std::out_of_range &) { - message.str(""); - message << "parsing trigin string: " << state_in; - this->camera.log_error(function, message.str()); - error = ERROR; - } catch (...) { - message.str(""); - message << "unknown exception parsing trigin string: " << state_in; - this->camera.log_error(function, message.str()); + Tokenize( state_in, tokens, " " ); // break into tokens + + // Must have 1 or 2 arguments only + // + if ( tokens.empty() || tokens.size() > 2 ) { + message.str(""); message << "requested trigin state: " << state_in << " is invalid. Expected { expose [N] | untimed | readout | disable }"; + this->camera.log_error( function, message.str() ); + error = ERROR; + } + + // If the basic checks have failed then get out now, no point in continuing + // + if ( error != NO_ERROR ) return error; + + // Now look at the arguments, expecting { expose [N] | untimed | readout } + // and set the Archon parameters appropriately. The assumption is that + // after setting these parameters, the Archon will receive a TRIGIN which + // resets the timing core so that the ACF will take appropriate action + // based on the settings of these parameters. + // + try { + + // Ultimately, only will only write the parameter *IF* the parameter has been defined in the config file. + // In other words, make no requirement on having all trigger parameters (TRIGIN_EXPOSE_PARAM, + // TRIGIN_UNTIMED_PARAM, TRIGIN_READOUT_PARAM) defined. But if the "trigin expose" command is sent then + // obviously the TRIGIN_EXPOSE_PARAM must be defined, and so on. + // + if ( tokens.at(0) == "expose" ) { // timed exposures can take an optional argument + // At minimum, expose requires that TRIGIN_EXPOSE_PARAM be defined + // + if ( this->trigin_exposeparam.empty() ) { + message.str(""); message << "TRIGIN_EXPOSE_PARAM not defined in configuration file " << this->config.filename; + this->camera.log_error( function, message.str() ); + return ERROR; + } + if ( tokens.size() == 1 ) { // no size given so = 1 + this->trigin_expose = 1; + this->trigin_untimed = this->trigin_untimed_disable; + this->trigin_readout = this->trigin_readout_disable; + + } else { // size given so try to set trigin_expose to requested value + int expnum = std::stoi( tokens.at(1) ); + if ( expnum < 1 ) { + this->camera.log_error( function, "number of timed exposures must be greater than zero" ); error = ERROR; - } - // Get out now if any internal errors from above - // - if (error != NO_ERROR) return error; + } else { + this->trigin_expose = expnum; + this->trigin_untimed = this->trigin_untimed_disable; + this->trigin_readout = this->trigin_readout_disable; + } + } + + } else if ( tokens.at(0) == "untimed" ) { // untimed exposures + // At minimum, untimed requires that TRIGIN_UNTIMED_PARAM be defined + if ( this->trigin_untimedparam.empty() ) { + message.str(""); message << "TRIGIN_UNTIMED_PARAM not defined in configuration file " << this->config.filename; + this->camera.log_error( function, message.str() ); + return ERROR; + } + this->trigin_expose = 0; + this->trigin_untimed = this->trigin_untimed_enable; + this->trigin_readout = this->trigin_readout_disable; + + } else if ( tokens.at(0) == "readout" ) { // readout is used at the end of untimed exposures + // At minimum, readout requires that TRIGIN_READOUT_PARAM be defined + if ( this->trigin_readoutparam.empty() ) { + message.str(""); message << "TRIGIN_READOUT_PARAM not defined in configuration file " << this->config.filename; + this->camera.log_error( function, message.str() ); + return ERROR; + } + this->trigin_expose = 0; + this->trigin_untimed = this->trigin_untimed_disable; + this->trigin_readout = this->trigin_readout_enable; + + } else if ( tokens.at(0) == "disable" ) { // used to disable untimed exposures + this->trigin_expose = 0; + this->trigin_untimed = this->trigin_untimed_disable; + this->trigin_readout = this->trigin_readout_disable; + + } else { + message.str(""); message << "requested trigin state: " << state_in << " is invalid. Expected { expose [N] | untimed | readout | disable }"; + this->camera.log_error( function, message.str() ); + error = ERROR; + } - // Build up a vector of the parameters and their values that have been defined. - // - typedef struct { - std::string param; - int value; - } triginfo_t; + // The first token is taken to be the state name. + // Only after the parameters have been successfully written will we set this->trigin_state = newstate + // + newstate = tokens.at(0); + } catch ( std::invalid_argument & ) { + message.str(""); message << "converting number of timed external-trigger exposures: " << state_in << " to integer"; + this->camera.log_error( function, message.str() ); + error = ERROR; + + } catch ( std::out_of_range & ) { + message.str(""); message << "parsing trigin string: " << state_in; + this->camera.log_error( function, message.str() ); + error = ERROR; + + } catch (...) { + message.str(""); message << "unknown exception parsing trigin string: " << state_in; + this->camera.log_error( function, message.str() ); + error = ERROR; + } + + // Get out now if any internal errors from above + // + if ( error != NO_ERROR ) return error; - std::vector trigger; + // Build up a vector of the parameters and their values that have been defined. + // + typedef struct { + std::string param; + int value; + } triginfo_t; - // If the parameter has been defined then push that parameter and its value into the vector. - // Check this for each of the three trigin parameters. - // + std::vector trigger; - if (!this->trigin_exposeparam.empty()) { - // TRIGIN_EXPOSE - triginfo_t expose; - expose.param = this->trigin_exposeparam; - expose.value = this->trigin_expose; - trigger.push_back(expose); -#ifdef LOGLEVEL_DEBUG + // If the parameter has been defined then push that parameter and its value into the vector. + // Check this for each of the three trigin parameters. + // + + if ( !this->trigin_exposeparam.empty() ) { // TRIGIN_EXPOSE + triginfo_t expose; + expose.param = this->trigin_exposeparam; + expose.value = this->trigin_expose; + trigger.push_back( expose ); + #ifdef LOGLEVEL_DEBUG message.str(""); message << "[DEBUG] EXPOSE param=" << this->trigin_exposeparam << " value=" << this->trigin_expose; logwrite( function, message.str() ); -#endif - } + #endif + } - if (!this->trigin_untimedparam.empty()) { - // TRIGIN_UNTIMED - triginfo_t untimed; - untimed.param = this->trigin_untimedparam; - untimed.value = this->trigin_untimed; - trigger.push_back(untimed); -#ifdef LOGLEVEL_DEBUG + if ( !this->trigin_untimedparam.empty() ) { // TRIGIN_UNTIMED + triginfo_t untimed; + untimed.param = this->trigin_untimedparam; + untimed.value = this->trigin_untimed; + trigger.push_back( untimed ); + #ifdef LOGLEVEL_DEBUG message.str(""); message << "[DEBUG] UNTIMED param=" << this->trigin_untimedparam << " value=" << this->trigin_untimed; logwrite( function, message.str() ); -#endif - } + #endif + } - if (!this->trigin_readoutparam.empty()) { - // TRIGIN_READOUT - triginfo_t readout; - readout.param = this->trigin_readoutparam; - readout.value = this->trigin_readout; - trigger.push_back(readout); -#ifdef LOGLEVEL_DEBUG + if ( !this->trigin_readoutparam.empty() ) { // TRIGIN_READOUT + triginfo_t readout; + readout.param = this->trigin_readoutparam; + readout.value = this->trigin_readout; + trigger.push_back( readout ); + #ifdef LOGLEVEL_DEBUG message.str(""); message << "[DEBUG] READOUT param=" << this->trigin_readoutparam << " value=" << this->trigin_readout; logwrite( function, message.str() ); -#endif - } + #endif + } - // Now write those parameters to the Archon - // - for (const auto &trig: trigger) { - if (error == NO_ERROR) error = this->set_parameter(trig.param, trig.value); - } + // Now write those parameters to the Archon + // + for ( const auto &trig : trigger ) { + if ( error == NO_ERROR ) error = this->set_parameter( trig.param, trig.value ); + } - // Now that the parameters have been successfully written we are in the new state - // - if (error == NO_ERROR) this->trigin_state = newstate; + // Now that the parameters have been successfully written we are in the new state + // + if ( error == NO_ERROR ) this->trigin_state = newstate; - // Save the last frame number acquired -- wait_for_readout() will need this later - // - if (error == NO_ERROR) error = this->get_frame_status(); - if (error == NO_ERROR) this->lastframe = this->frame.bufframen[this->frame.index]; + // Save the last frame number acquired -- wait_for_readout() will need this later + // + if ( error == NO_ERROR) error = this->get_frame_status(); + if ( error == NO_ERROR) this->lastframe = this->frame.bufframen[this->frame.index]; -#ifdef LOGLEVEL_DEBUG + #ifdef LOGLEVEL_DEBUG message.str(""); message << "[DEBUG] lastframe=" << this->lastframe; logwrite( function, message.str() ); -#endif + #endif - // For "untimed" prepare camera_info and open the FITS file - // - if (error == NO_ERROR && this->trigin_state == "untimed") { - this->camera_info.start_time = get_timestamp(); // current system time formatted as YYYY-MM-DDTHH:MM:SS.sss - this->get_timer(&this->start_timer); // Archon internal timer (one tick=10 nsec) - this->camera.set_fitstime(this->camera_info.start_time); - // sets camera.fitstime (YYYYMMDDHHMMSS) used for filename - error = this->camera.get_fitsname(this->camera_info.fits_name); // Assemble the FITS filename - this->add_filename_key(); // add filename to system keys database - this->camera_info.systemkeys.keydb = this->systemkeys.keydb; - // copy the systemkeys database into camera_info - if (this->camera.writekeys_when == "before") this->copy_keydb(); - // copy the ACF and userkeys database into camera_info - error = this->fits_file.open_file(this->camera.writekeys_when == "before", this->camera_info); - logwrite(function, "untimed exposure trigger enabled"); - return error; - } + // For "untimed" prepare camera_info and open the FITS file + // + if ( error == NO_ERROR && this->trigin_state == "untimed" ) { + this->camera_info.start_time = get_timestamp(); // current system time formatted as YYYY-MM-DDTHH:MM:SS.sss + this->get_timer(&this->start_timer); // Archon internal timer (one tick=10 nsec) + this->camera.set_fitstime(this->camera_info.start_time); // sets camera.fitstime (YYYYMMDDHHMMSS) used for filename + error=this->camera.get_fitsname(this->camera_info.fits_name); // Assemble the FITS filename + this->add_filename_key(); // add filename to system keys database + this->camera_info.systemkeys.keydb = this->systemkeys.keydb; // copy the systemkeys database into camera_info + if (this->camera.writekeys_when=="before") this->copy_keydb(); // copy the ACF and userkeys database into camera_info + error = this->fits_file.open_file(this->camera.writekeys_when == "before", this->camera_info ); + logwrite( function, "untimed exposure trigger enabled" ); + return error; + } - // For "disable" there's nothing more to do right now (except close any open FITS file) - // - if (error == NO_ERROR && this->trigin_state == "disable") { - if (this->fits_file.isopen()) { - // "untimed" could have opened a FITS file - this->fits_file.close_file(false, this->camera_info); // close the FITS file container - std::remove(this->camera_info.fits_name.c_str()); // remove the (empty) file it created - } - logwrite(function, "untimed exposure trigger disabled"); - return error; - } + // For "disable" there's nothing more to do right now (except close any open FITS file) + // + if ( error == NO_ERROR && this->trigin_state == "disable" ) { + if ( this->fits_file.isopen() ) { // "untimed" could have opened a FITS file + this->fits_file.close_file( false, this->camera_info ); // close the FITS file container + std::remove( this->camera_info.fits_name.c_str() ); // remove the (empty) file it created + } + logwrite( function, "untimed exposure trigger disabled" ); + return error; + } - // Don't continue if Archon parameters were not written properly - // - if (error != NO_ERROR) return error; + // Don't continue if Archon parameters were not written properly + // + if ( error != NO_ERROR ) return error; - // ------------------------------------------------------------------------ - // Now the rest is somewhat like the "expose" function... - // ------------------------------------------------------------------------ + // ------------------------------------------------------------------------ + // Now the rest is somewhat like the "expose" function... + // ------------------------------------------------------------------------ - // Always initialize the extension number because someone could - // set datacube true and then send "expose" without a number. - // - this->camera_info.extension = 0; + // Always initialize the extension number because someone could + // set datacube true and then send "expose" without a number. + // + this->camera_info.extension = 0; + + // ------------------------------------------------------------------------ + // "readout" + // The assumption here is that the exposure has started and this command is + // sent to prepare for the end of exposure. The exposure is nearly complete. + // The next TRIGIN will end the exposure and begin the readout, so now we + // begin waiting for the new frame to appear in the Archon frame buffer. + // ------------------------------------------------------------------------ + // + if ( this->trigin_state == "readout" ) { + + // Save the datacube state in camera_info so that the FITS writer can know about it + // + this->camera_info.iscube = this->camera.datacube(); + + this->camera_info.systemkeys.keydb = this->systemkeys.keydb; // copy systemkeys database object into camera_info + + if (this->camera.writekeys_when=="after") this->copy_keydb(); // copy the ACF and userkeys database into camera_info + if (error==NO_ERROR) error = this->wait_for_readout(); // Wait for the readout into frame buffer, + if (error==NO_ERROR) error = read_frame(); // then read the frame buffer to host (and write file) when frame ready. + + this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info ); // close the file when not using datacubes + this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" + + // ASYNC status message on completion of each file + // + message.str(""); message << "FILE:" << this->camera_info.fits_name << " " << ( error==NO_ERROR ? "COMPLETE" : "ERROR" ); + this->camera.async.enqueue( message.str() ); + error == NO_ERROR ? logwrite( function, message.str() ) : this->camera.log_error( function, message.str() ); + + } else if ( this->trigin_state == "expose" ) { // ------------------------------------------------------------------------ - // "readout" - // The assumption here is that the exposure has started and this command is - // sent to prepare for the end of exposure. The exposure is nearly complete. - // The next TRIGIN will end the exposure and begin the readout, so now we - // begin waiting for the new frame to appear in the Archon frame buffer. + // "expose" + // The assumption here is that the exposure hasn't started yet. The next + // TRIGIN will initiate the complete exposure sequence in the Archon, + // exposure delay followed by sensor readout, so now we are waiting for that + // sequence to start. // ------------------------------------------------------------------------ // - if (this->trigin_state == "readout") { - // Save the datacube state in camera_info so that the FITS writer can know about it - // - this->camera_info.iscube = this->camera.datacube(); - this->camera_info.systemkeys.keydb = this->systemkeys.keydb; - // copy systemkeys database object into camera_info + // get system time and Archon's timer after exposure starts + // start_timer is used to determine when the exposure has ended, in wait_for_exposure() + // + this->camera_info.start_time = get_timestamp(); // current system time formatted as YYYY-MM-DDTHH:MM:SS.sss + error = this->get_timer(&this->start_timer); // Archon internal timer (one tick=10 nsec) - if (this->camera.writekeys_when == "after") this->copy_keydb(); - // copy the ACF and userkeys database into camera_info + this->camera.set_fitstime(this->camera_info.start_time); // sets camera.fitstime (YYYYMMDDHHMMSS) used for filename + error=this->camera.get_fitsname(this->camera_info.fits_name); // assemble the FITS filename + this->add_filename_key(); // add filename to system keys database - if (error == NO_ERROR) error = this->wait_for_readout(); // Wait for the readout into frame buffer, - if (error == NO_ERROR) error = read_frame(); - // then read the frame buffer to host (and write file) when frame ready. - - this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info); - // close the file when not using datacubes - this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" + this->camera_info.systemkeys.keydb = this->systemkeys.keydb; // copy the systemkeys database into camera_info + if (this->camera.writekeys_when=="before") this->copy_keydb();// copy the ACF and userkeys database into camera_info - // ASYNC status message on completion of each file - // - message.str(""); - message << "FILE:" << this->camera_info.fits_name << " " << (error == NO_ERROR ? "COMPLETE" : "ERROR"); - this->camera.async.enqueue(message.str()); - error == NO_ERROR ? logwrite(function, message.str()) : this->camera.log_error(function, message.str()); - } else if (this->trigin_state == "expose") { - // ------------------------------------------------------------------------ - // "expose" - // The assumption here is that the exposure hasn't started yet. The next - // TRIGIN will initiate the complete exposure sequence in the Archon, - // exposure delay followed by sensor readout, so now we are waiting for that - // sequence to start. - // ------------------------------------------------------------------------ - // + // If mode is not "RAW" but RAWENABLE is set then we're going to require a multi-extension data cube, + // one extension for the image and a separate extension for raw data. + // + if ( (error == NO_ERROR) && (mode != "RAW") && (this->modemap[mode].rawenable) ) { + if ( !this->camera.datacube() ) { // if datacube not already set then it must be overridden here + this->camera.async.enqueue( "NOTICE:override datacube true" ); // let everyone know + logwrite( function, "NOTICE:override datacube true" ); + this->camera.datacube(true); + } + this->camera_info.extension = 0; + } - // get system time and Archon's timer after exposure starts - // start_timer is used to determine when the exposure has ended, in wait_for_exposure() - // - this->camera_info.start_time = get_timestamp(); // current system time formatted as YYYY-MM-DDTHH:MM:SS.sss - error = this->get_timer(&this->start_timer); // Archon internal timer (one tick=10 nsec) + // Save the datacube state in camera_info so that the FITS writer can know about it + // + this->camera_info.iscube = this->camera.datacube(); - this->camera.set_fitstime(this->camera_info.start_time); - // sets camera.fitstime (YYYYMMDDHHMMSS) used for filename - error = this->camera.get_fitsname(this->camera_info.fits_name); // assemble the FITS filename - this->add_filename_key(); // add filename to system keys database + // Open the FITS file now for cubes + // + if ( this->camera.datacube() ) { + error = this->fits_file.open_file( + this->camera.writekeys_when == "before", this->camera_info ); + if ( error != NO_ERROR ) { + this->camera.log_error( function, "couldn't open fits file" ); + return error; + } + } - this->camera_info.systemkeys.keydb = this->systemkeys.keydb; - // copy the systemkeys database into camera_info - if (this->camera.writekeys_when == "before") this->copy_keydb(); - // copy the ACF and userkeys database into camera_info + if (this->trigin_expose > 1) { + message.str(""); message << "starting sequence of " << this->trigin_expose << " frames. lastframe=" << this->lastframe; + logwrite(function, message.str()); + } - // If mode is not "RAW" but RAWENABLE is set then we're going to require a multi-extension data cube, - // one extension for the image and a separate extension for raw data. - // - if ((error == NO_ERROR) && (mode != "RAW") && (this->modemap[mode].rawenable)) { - if (!this->camera.datacube()) { - // if datacube not already set then it must be overridden here - this->camera.async.enqueue("NOTICE:override datacube true"); // let everyone know - logwrite(function, "NOTICE:override datacube true"); - this->camera.datacube(true); - } - this->camera_info.extension = 0; + // If not RAW mode then wait for Archon frame buffer to be ready, + // then read the latest ready frame buffer to the host. If this + // is a squence, then loop over all expected frames. + // + if ( (error == NO_ERROR) && (mode != "RAW") ) { // If not raw mode then + while ( this->trigin_expose-- > 0 ) { + + // Open a new FITS file for each frame when not using datacubes + // + if ( !this->camera.datacube() ) { + this->camera_info.start_time = get_timestamp(); // current system time formatted as YYYY-MM-DDTHH:MM:SS.sss + this->get_timer(&this->start_timer); // Archon internal timer (one tick=10 nsec) + this->camera.set_fitstime(this->camera_info.start_time); // sets camera.fitstime (YYYYMMDDHHMMSS) used for filename + error=this->camera.get_fitsname(this->camera_info.fits_name); // Assemble the FITS filename + if ( error != NO_ERROR ) { + this->camera.log_error( function, "couldn't validate fits filename" ); + return error; + } + this->add_filename_key(); // add filename to system keys database + error = this->fits_file.open_file( + this->camera.writekeys_when == "before", this->camera_info ); + if ( error != NO_ERROR ) { + this->camera.log_error( function, "couldn't open fits file" ); + return error; } + } - // Save the datacube state in camera_info so that the FITS writer can know about it - // - this->camera_info.iscube = this->camera.datacube(); + if (error==NO_ERROR && this->camera_info.exposure_time != 0) { // wait for the exposure delay to complete (if there is one) + error = this->wait_for_exposure(); + } + + if (error==NO_ERROR) error = this->wait_for_readout(); // Wait for the readout into frame buffer, + if (error==NO_ERROR) error = read_frame(); // then read the frame buffer to host (and write file) when frame ready. + + if ( !this->camera.datacube() ) { // Error or not, close the file. + this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info ); // close the file when not using datacubes + this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" - // Open the FITS file now for cubes + // ASYNC status message on completion of each file // - if (this->camera.datacube()) { - error = this->fits_file.open_file( - this->camera.writekeys_when == "before", this->camera_info); - if (error != NO_ERROR) { - this->camera.log_error(function, "couldn't open fits file"); - return error; - } - } + message.str(""); message << "FILE:" << this->camera_info.fits_name << " " << ( error==NO_ERROR ? "COMPLETE" : "ERROR" ); + this->camera.async.enqueue( message.str() ); + error == NO_ERROR ? logwrite( function, message.str() ) : this->camera.log_error( function, message.str() ); + } + + if (error != NO_ERROR) break; // don't try additional sequences if there were errors + } + + } else if ( (error == NO_ERROR) && (mode == "RAW") ) { + error = this->get_frame_status(); // Get the current frame buffer status + if (error==NO_ERROR) error = this->camera.get_fitsname( this->camera_info.fits_name ); // Assemble the FITS filename + this->add_filename_key(); // add filename to system keys database + if (error==NO_ERROR) error = this->fits_file.open_file( + this->camera.writekeys_when == "before", this->camera_info ); + if (error==NO_ERROR) error = read_frame(); // For raw mode just read immediately + this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info ); + this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" + } + // for cubes, close the FITS file now that they've all been written + // + if ( this->camera.datacube() ) { + this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info ); + this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" + + // ASYNC status message on completion of each file + // + message.str(""); message << "FILE:" << this->camera_info.fits_name << " " << ( error==NO_ERROR ? "COMPLETE" : "ERROR" ); + this->camera.async.enqueue( message.str() ); + error == NO_ERROR ? logwrite( function, message.str() ) : this->camera.log_error( function, message.str() ); + } + // end if ( this->trigin_state == "expose" ) - if (this->trigin_expose > 1) { - message.str(""); - message << "starting sequence of " << this->trigin_expose << " frames. lastframe=" << this->lastframe; - logwrite(function, message.str()); - } + } else { + // unknown (should be impossible) + message.str(""); message << "unexpected error processing trigin state " << this->trigin_state; + this->camera.log_error( function, message.str() ); + error = ERROR; + } - // If not RAW mode then wait for Archon frame buffer to be ready, - // then read the latest ready frame buffer to the host. If this - // is a squence, then loop over all expected frames. - // - if ((error == NO_ERROR) && (mode != "RAW")) { - // If not raw mode then - while (this->trigin_expose-- > 0) { - // Open a new FITS file for each frame when not using datacubes - // - if (!this->camera.datacube()) { - this->camera_info.start_time = get_timestamp(); - // current system time formatted as YYYY-MM-DDTHH:MM:SS.sss - this->get_timer(&this->start_timer); // Archon internal timer (one tick=10 nsec) - this->camera.set_fitstime(this->camera_info.start_time); - // sets camera.fitstime (YYYYMMDDHHMMSS) used for filename - error = this->camera.get_fitsname(this->camera_info.fits_name); // Assemble the FITS filename - if (error != NO_ERROR) { - this->camera.log_error(function, "couldn't validate fits filename"); - return error; - } - this->add_filename_key(); // add filename to system keys database - error = this->fits_file.open_file( - this->camera.writekeys_when == "before", this->camera_info); - if (error != NO_ERROR) { - this->camera.log_error(function, "couldn't open fits file"); - return error; - } - } + return error; + } + /**************** Archon::Interface::trigin *********************************/ - if (error == NO_ERROR && this->camera_info.exposure_time != 0) { - // wait for the exposure delay to complete (if there is one) - error = this->wait_for_exposure(); - } - if (error == NO_ERROR) error = this->wait_for_readout(); // Wait for the readout into frame buffer, - if (error == NO_ERROR) error = read_frame(); - // then read the frame buffer to host (and write file) when frame ready. - - if (!this->camera.datacube()) { - // Error or not, close the file. - this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info); - // close the file when not using datacubes - this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" - - // ASYNC status message on completion of each file - // - message.str(""); - message << "FILE:" << this->camera_info.fits_name << " " << (error == NO_ERROR - ? "COMPLETE" - : "ERROR"); - this->camera.async.enqueue(message.str()); - error == NO_ERROR - ? logwrite(function, message.str()) - : this->camera.log_error(function, message.str()); - } + /**************** Archon::Interface::copy_keydb *****************************/ + /** + * @fn copy_keydb + * @brief copy the ACF and user keyword databases into camera_info + * @param[in] none + * @return none + * + */ + void Interface::copy_keydb() { + std::string function = "Archon::Interface::copy_keydb"; - if (error != NO_ERROR) break; // don't try additional sequences if there were errors - } - } else if ((error == NO_ERROR) && (mode == "RAW")) { - error = this->get_frame_status(); // Get the current frame buffer status - if (error == NO_ERROR) error = this->camera.get_fitsname(this->camera_info.fits_name); - // Assemble the FITS filename - this->add_filename_key(); // add filename to system keys database - if (error == NO_ERROR) - error = this->fits_file.open_file( - this->camera.writekeys_when == "before", this->camera_info); - if (error == NO_ERROR) error = read_frame(); // For raw mode just read immediately - this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info); - this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" - } - // for cubes, close the FITS file now that they've all been written - // - if (this->camera.datacube()) { - this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info); - this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" - - // ASYNC status message on completion of each file - // - message.str(""); - message << "FILE:" << this->camera_info.fits_name << " " << (error == NO_ERROR ? "COMPLETE" : "ERROR"); - this->camera.async.enqueue(message.str()); - error == NO_ERROR ? logwrite(function, message.str()) : this->camera.log_error(function, message.str()); - } - // end if ( this->trigin_state == "expose" ) - } else { - // unknown (should be impossible) - message.str(""); - message << "unexpected error processing trigin state " << this->trigin_state; - this->camera.log_error(function, message.str()); - error = ERROR; - } + // copy the userkeys database object into camera_info + // + this->camera_info.userkeys.keydb = this->userkeys.keydb; - return error; + // add any keys from the ACF file (from modemap[mode].acfkeys) into the + // camera_info.userkeys object + // + std::string mode = this->camera_info.current_observing_mode; + Common::FitsKeys::fits_key_t::iterator keyit; + for ( keyit = this->modemap[mode].acfkeys.keydb.begin(); + keyit != this->modemap[mode].acfkeys.keydb.end(); + keyit++ ) { + this->camera_info.userkeys.keydb[keyit->second.keyword].keyword = keyit->second.keyword; + this->camera_info.userkeys.keydb[keyit->second.keyword].keytype = keyit->second.keytype; + this->camera_info.userkeys.keydb[keyit->second.keyword].keyvalue = keyit->second.keyvalue; + this->camera_info.userkeys.keydb[keyit->second.keyword].keycomment = keyit->second.keycomment; } - /**************** Archon::Interface::trigin *********************************/ + #ifdef LOGLEVEL_DEBUG + logwrite( function, "[DEBUG] copied userkeys db to camera_info" ); + #endif + } + /**************** Archon::Interface::copy_keydb *****************************/ + + + /**************** Archon::Interface::longexposure ***************************/ + /** + * @fn longexposure + * @brief set/get longexposure mode + * @param string + * @return ERROR or NO_ERROR + * + */ + long Interface::longexposure(std::string state_in, std::string &state_out) { + std::string function = "Archon::Interface::longexposure"; + std::stringstream message; + long error = NO_ERROR; + + // If something is passed then try to use it to set the longexposure state + // + if ( !state_in.empty() ) { + try { + std::transform( state_in.begin(), state_in.end(), state_in.begin(), ::toupper ); // make uppercase + if ( state_in == "FALSE" || state_in == "0" ) this->is_longexposure = false; + else + if ( state_in == "TRUE" || state_in == "1" ) this->is_longexposure = true; + else { + message.str(""); message << "longexposure state " << state_in << " is invalid. Expecting {true,false,0,1}"; + this->camera.log_error( function, message.str() ); + return ERROR; + } + + } catch (...) { + message.str(""); message << "unknown exception converting longexposure state " << state_in << " to uppercase"; + this->camera.log_error( function, message.str() ); + return ERROR; + } + } + // error or not, the state reported now will be whatever was last successfully set + // + this->camera_info.exposure_unit = ( this->is_longexposure ? "sec" : "msec" ); + this->camera_info.exposure_factor = ( this->is_longexposure ? 1 : 1000 ); + state_out = ( this->is_longexposure ? "true" : "false" ); - /**************** Archon::Interface::copy_keydb *****************************/ - /** - * @fn copy_keydb - * @brief copy the ACF and user keyword databases into camera_info - * @param[in] none - * @return none - * - */ - void Interface::copy_keydb() { - std::string function = "Archon::Interface::copy_keydb"; + // if no error then set the parameter on the Archon + // + if ( error==NO_ERROR ) { + std::stringstream cmd; + cmd << "longexposure " << ( this->is_longexposure ? 1 : 0 ); + if ( error==NO_ERROR ) error = this->set_parameter( cmd.str() ); + } - // copy the userkeys database object into camera_info - // - this->camera_info.userkeys.keydb = this->userkeys.keydb; + return error; + } + /**************** Archon::Interface::longexposure ***************************/ + + + /**************** Archon::Interface::heater *********************************/ + /** + * @fn heater + * @brief heater control, set/get state, target, PID, ramp + * @param args contains various allowable strings (see full descsription) + * @return ERROR or NO_ERROR + * + * valid args format, + * to set or get the enable state and target for heater A or B on the specified module: + * < A | B > [ ] + * possible args: 2 (get) or 3 or 4 + * + * to set or get the PID parameters for heater A or B on the specified module: + * < A | B > PID [

] + * possible args: 3 (get) or 6 (set) + * + * to set or get the ramp and ramprate for heater A or B on the specified module: + * < A | B > RAMP [ [ramprate] ] + * possible args: 3 (get) or 5 (set) + * + * to set or get the current limit for heater A or B on the specified module: + * < A | B > ILIM [ ] + * possible args: 3 (get) or 4 (set) + * + * to set or get the input sensor for heater A or B on the specified module: + * < A | B > INPUT [ A | B | C ] + * possible args: 3 (get) or 4 (set) + * + */ + long Interface::heater(std::string args, std::string &retstring) { + std::string function = "Archon::Interface::heater"; + std::stringstream message; + std::vector tokens; + int module; + std::string heaterid; //!< A|B + bool readonly=false; + float pid_p, pid_i, pid_d; //!< requested PID values + int ramprate; //!< requested ramp rate value + float target; //!< requested heater target value + std::vector heaterconfig; //!< vector of configuration lines to read or write + std::vector heatervalue; //!< vector of values associated with config lines (for write) + + // must have loaded firmware // TODO implement a command to read the configuration + // // memory from Archon, in order to remove this restriction. + // + if ( ! this->firmwareloaded ) { + this->camera.log_error( function, "firmware not loaded" ); + return ERROR; + } - // add any keys from the ACF file (from modemap[mode].acfkeys) into the - // camera_info.userkeys object - // - std::string mode = this->camera_info.current_observing_mode; - Common::FitsKeys::fits_key_t::iterator keyit; - for (keyit = this->modemap[mode].acfkeys.keydb.begin(); - keyit != this->modemap[mode].acfkeys.keydb.end(); - keyit++) { - this->camera_info.userkeys.keydb[keyit->second.keyword].keyword = keyit->second.keyword; - this->camera_info.userkeys.keydb[keyit->second.keyword].keytype = keyit->second.keytype; - this->camera_info.userkeys.keydb[keyit->second.keyword].keyvalue = keyit->second.keyvalue; - this->camera_info.userkeys.keydb[keyit->second.keyword].keycomment = keyit->second.keycomment; - } + std::transform( args.begin(), args.end(), args.begin(), ::toupper ); // make uppercase -#ifdef LOGLEVEL_DEBUG - logwrite( function, "[DEBUG] copied userkeys db to camera_info" ); -#endif + // RAMP requires a minimum backplane version + // + int ret = compare_versions( this->backplaneversion, REV_RAMP ); + if ( ret < 0 ) { + if ( ret == -999 ) { + message << "comparing backplane version " << this->backplaneversion << " to " << REV_RAMP; + + } else { + message << "requires backplane version " << REV_RAMP << " or newer. (" + << this->backplaneversion << " detected)"; + } + this->camera.log_error( function, message.str() ); + return ERROR; } - /**************** Archon::Interface::copy_keydb *****************************/ + Tokenize(args, tokens, " "); + // At minimum there must be two tokens, A|B + // + if ( tokens.size() < 2 ) { + this->camera.log_error( function, "expected at least two arguments: A|B" ); + return ERROR; + } - /**************** Archon::Interface::longexposure ***************************/ - /** - * @fn longexposure - * @brief set/get longexposure mode - * @param string - * @return ERROR or NO_ERROR - * - */ - long Interface::longexposure(std::string state_in, std::string &state_out) { - std::string function = "Archon::Interface::longexposure"; - std::stringstream message; - long error = NO_ERROR; + // As long as there are at least two tokens, get the module and heaterid + // which will be common to everything. + // + try { + module = std::stoi( tokens[0] ); + heaterid = tokens[1]; + if ( heaterid != "A" && heaterid != "B" ) { + message.str(""); message << "invalid heater " << heaterid << ": expected A|B"; + this->camera.log_error( function, message.str() ); + return ERROR; + } - // If something is passed then try to use it to set the longexposure state - // - if (!state_in.empty()) { - try { - std::transform(state_in.begin(), state_in.end(), state_in.begin(), ::toupper); // make uppercase - if (state_in == "FALSE" || state_in == "0") this->is_longexposure = false; - else if (state_in == "TRUE" || state_in == "1") this->is_longexposure = true; - else { - message.str(""); - message << "longexposure state " << state_in << " is invalid. Expecting {true,false,0,1}"; - this->camera.log_error(function, message.str()); - return ERROR; - } - } catch (...) { - message.str(""); - message << "unknown exception converting longexposure state " << state_in << " to uppercase"; - this->camera.log_error(function, message.str()); - return ERROR; - } - } + } catch (std::invalid_argument &) { + message.str(""); message << "converting heater " << tokens[0] << " to integer"; + this->camera.log_error( function, message.str() ); + return ERROR; - // error or not, the state reported now will be whatever was last successfully set - // - this->camera_info.exposure_unit = (this->is_longexposure ? "sec" : "msec"); - this->camera_info.exposure_factor = (this->is_longexposure ? 1 : 1000); - state_out = (this->is_longexposure ? "true" : "false"); + } catch (std::out_of_range &) { + message.str(""); message << "heater : " << tokens[0] << " is outside integer range"; + this->camera.log_error( function, message.str() ); + return ERROR; + } - // if no error then set the parameter on the Archon - // - if (error == NO_ERROR) { - std::stringstream cmd; - cmd << "longexposure " << (this->is_longexposure ? 1 : 0); - if (error == NO_ERROR) error = this->set_parameter(cmd.str()); - } + // check that requested module is valid + // + switch ( this->modtype[ module-1 ] ) { + case 0: + message.str(""); message << "module " << module << " not installed"; + this->camera.log_error( function, message.str() ); + return ERROR; + case 5: // Heater + case 11: // HeaterX + break; + default: + message.str(""); message << "module " << module << " not a heater board"; + this->camera.log_error( function, message.str() ); + return ERROR; + } - return error; + // Now that we've passed the basic tests (firmware OK, basic syntax OK, and requested module is a heater) + // go ahead and set up some needed variables. + // + + // heater target min/max depends on backplane version + // + ret = compare_versions( this->backplaneversion, REV_HEATERTARGET ); + if ( ret == -999 ) { + message.str(""); message << "comparing backplane version " << this->backplaneversion << " to " << REV_HEATERTARGET; + this->camera.log_error( function, message.str() ); + return ERROR; + + } else if ( ret == -1 ) { + this->heater_target_min = -150.0; + this->heater_target_max = 50.0; + + } else { + this->heater_target_min = -250.0; + this->heater_target_max = 50.0; } - /**************** Archon::Interface::longexposure ***************************/ + heaterconfig.clear(); + heatervalue.clear(); + std::stringstream ss; + // Any one heater command can require reading or writing multiple configuration lines. + // The code will look at each case and push each heater configuration line that needs + // to be read or written into a vector (heaterconfig). For those that need to be written, + // the value to write will be pushed into a separate vector (heatervalue). After these + // vectors have been built up, the code will loop through each element of the vectors, + // reading and/or writing each. - /**************** Archon::Interface::heater *********************************/ - /** - * @fn heater - * @brief heater control, set/get state, target, PID, ramp - * @param args contains various allowable strings (see full descsription) - * @return ERROR or NO_ERROR - * - * valid args format, - * to set or get the enable state and target for heater A or B on the specified module: - * < A | B > [ ] - * possible args: 2 (get) or 3 or 4 - * - * to set or get the PID parameters for heater A or B on the specified module: - * < A | B > PID [

] - * possible args: 3 (get) or 6 (set) - * - * to set or get the ramp and ramprate for heater A or B on the specified module: - * < A | B > RAMP [ [ramprate] ] - * possible args: 3 (get) or 5 (set) - * - * to set or get the current limit for heater A or B on the specified module: - * < A | B > ILIM [ ] - * possible args: 3 (get) or 4 (set) - * - * to set or get the input sensor for heater A or B on the specified module: - * < A | B > INPUT [ A | B | C ] - * possible args: 3 (get) or 4 (set) - * - */ - long Interface::heater(std::string args, std::string &retstring) { - std::string function = "Archon::Interface::heater"; - std::stringstream message; - std::vector tokens; - int module; - std::string heaterid; //!< A|B - bool readonly = false; - float pid_p, pid_i, pid_d; //!< requested PID values - int ramprate; //!< requested ramp rate value - float target; //!< requested heater target value - std::vector heaterconfig; //!< vector of configuration lines to read or write - std::vector heatervalue; //!< vector of values associated with config lines (for write) - - // must have loaded firmware // TODO implement a command to read the configuration - // // memory from Archon, in order to remove this restriction. + // Start looking at the heater arguments passed, which have been tokenized. + // There can be only 2, 3, 4, 5, or 6 tokens allowed. + + // If there are exactly two (2) tokens then we have received only: + // A|B + // which means we're reading the state and target. + // + if ( tokens.size() == 2 ) { // no args reads ENABLE, TARGET + readonly = true; + ss.str(""); ss << "MOD" << module << "/HEATER" << heaterid << "ENABLE"; heaterconfig.push_back( ss.str() ); + ss.str(""); ss << "MOD" << module << "/HEATER" << heaterid << "TARGET"; heaterconfig.push_back( ss.str() ); + + } else if ( tokens.size() == 3 ) { + // If there are three (3) tokens then the 3rd must be one of the following: + // ON | OFF (for ENABLE), , PID, RAMP, ILIM + if ( tokens[2] == "ON" ) { // ON + ss.str(""); ss << "MOD" << module << "/HEATER" << heaterid << "ENABLE"; heaterconfig.push_back( ss.str() ); + heatervalue.emplace_back("1" ); + + } else if ( tokens[2] == "OFF" ) { // OFF + ss.str(""); ss << "MOD" << module << "/HEATER" << heaterid << "ENABLE"; heaterconfig.push_back( ss.str() ); + heatervalue.emplace_back("0" ); + + } else if ( tokens[2] == "RAMP" ) { // RAMP + readonly = true; + ss.str(""); ss << "MOD" << module << "/HEATER" << heaterid << "RAMP"; heaterconfig.push_back( ss.str() ); + ss.str(""); ss << "MOD" << module << "/HEATER" << heaterid << "RAMPRATE"; heaterconfig.push_back( ss.str() ); + + } else if ( tokens[2] == "PID" ) { // PID + readonly = true; + ss.str(""); ss << "MOD" << module << "/HEATER" << heaterid << "P"; heaterconfig.push_back( ss.str() ); + ss.str(""); ss << "MOD" << module << "/HEATER" << heaterid << "I"; heaterconfig.push_back( ss.str() ); + ss.str(""); ss << "MOD" << module << "/HEATER" << heaterid << "D"; heaterconfig.push_back( ss.str() ); + + } else if ( tokens[2] == "ILIM" ) { // ILIM + readonly = true; + ss.str(""); ss << "MOD" << module << "/HEATER" << heaterid << "IL"; heaterconfig.push_back( ss.str() ); + + } else if ( tokens[2] == "INPUT" ) { // INPUT + readonly = true; + ss.str(""); ss << "MOD" << module << "/HEATER" << heaterid << "SENSOR"; heaterconfig.push_back( ss.str() ); + + } else { // + // first check that the requested target is a valid number and within range... // - if (!this->firmwareloaded) { - this->camera.log_error(function, "firmware not loaded"); + try { + target = std::stof( tokens[2] ); + if ( target < this->heater_target_min || target > this->heater_target_max ) { + message.str(""); message << "requested heater target " << target << " outside range {" + << this->heater_target_min << ":" << this->heater_target_max << "}"; + this->camera.log_error( function, message.str() ); return ERROR; + } + + } catch (std::invalid_argument &) { + message.str(""); message << "converting heater =" << tokens[2] << " to float"; + this->camera.log_error( function, message.str() ); + return ERROR; + + } catch (std::out_of_range &) { + message.str(""); message << "heater : " << tokens[2] << " outside range of float"; + this->camera.log_error( function, message.str() ); + return ERROR; } + // ...if target is OK then push into the config, value vectors + // + ss.str(""); ss << "MOD" << module << "/HEATER" << heaterid << "TARGET"; heaterconfig.push_back( ss.str() ); + heatervalue.push_back( tokens[2] ); + } - std::transform(args.begin(), args.end(), args.begin(), ::toupper); // make uppercase + } else if ( tokens.size() == 4 ) { + // If there are four (4) tokens then the 4th must be one of the following: + // in < A | B > [ ] + // | ON | OFF in < A | B > RAMP [ [ramprate] ] + // in < A | B > ILIM [ ] + // A | B | C in < A | B > INPUT [ A | B | C ] - // RAMP requires a minimum backplane version + if ( tokens[2] == "ON" ) { // ON + // first check that the requested target is a valid number and within range... // - int ret = compare_versions(this->backplaneversion, REV_RAMP); - if (ret < 0) { - if (ret == -999) { - message << "comparing backplane version " << this->backplaneversion << " to " << REV_RAMP; - } else { - message << "requires backplane version " << REV_RAMP << " or newer. (" - << this->backplaneversion << " detected)"; - } - this->camera.log_error(function, message.str()); + try { + target = std::stof( tokens[3] ); + if ( target < this->heater_target_min || target > this->heater_target_max ) { + message.str(""); message << "requested heater target " << target << " outside range {" + << this->heater_target_min << ":" << this->heater_target_max << "}"; + this->camera.log_error( function, message.str() ); return ERROR; - } + } - Tokenize(args, tokens, " "); + } catch (std::invalid_argument &) { + message.str(""); message << "converting heater " << tokens[3] << " to float"; + this->camera.log_error( function, message.str() ); + return ERROR; - // At minimum there must be two tokens, A|B + } catch (std::out_of_range &) { + message.str(""); message << "heater : " << tokens[3] << " outside range of float"; + this->camera.log_error( function, message.str() ); + return ERROR; + } + // ...if target is OK then push into the config, value vectors // - if (tokens.size() < 2) { - this->camera.log_error(function, "expected at least two arguments: A|B"); + ss.str(""); ss << "MOD" << module << "/HEATER" << heaterid << "ENABLE"; heaterconfig.push_back( ss.str() ); + heatervalue.emplace_back("1" ); + ss.str(""); ss << "MOD" << module << "/HEATER" << heaterid << "TARGET"; heaterconfig.push_back( ss.str() ); + heatervalue.push_back( tokens[3] ); + + } else if ( tokens[2] == "RAMP" ) { // RAMP x + + if ( tokens[3] == "ON" || tokens[3] == "OFF" ) { // RAMP ON|OFF + ss.str(""); ss << "MOD" << module << "/HEATER" << heaterid << "RAMP"; heaterconfig.push_back( ss.str() ); + std::string state = ( tokens[3]=="ON" ? "1" : "0" ); + heatervalue.push_back( state ); + + } else { // RAMP + try { + ramprate = std::stoi( tokens[3] ); + if ( ramprate < 1 || ramprate > 32767 ) { + message.str(""); message << "heater ramprate " << ramprate << " outside range {1:32767}"; + this->camera.log_error( function, message.str() ); + return ERROR; + } + + } catch (std::invalid_argument &) { + message.str(""); message << "converting RAMP " << tokens[3] << " to integer"; + this->camera.log_error( function, message.str() ); + return ERROR; + + } catch (std::out_of_range &) { + message.str(""); message << "RAMP : " << tokens[3] << " outside range of integer"; + this->camera.log_error( function, message.str() ); return ERROR; + } + ss.str(""); ss << "MOD" << module << "/HEATER" << heaterid << "RAMPRATE"; heaterconfig.push_back( ss.str() ); + heatervalue.push_back( tokens[3] ); } - // As long as there are at least two tokens, get the module and heaterid - // which will be common to everything. - // + } else if ( tokens[2] == "ILIM" ) { // ILIM x + int il_value=0; try { - module = std::stoi(tokens[0]); - heaterid = tokens[1]; - if (heaterid != "A" && heaterid != "B") { - message.str(""); - message << "invalid heater " << heaterid << ": expected A|B"; - this->camera.log_error(function, message.str()); - return ERROR; - } - } catch (std::invalid_argument &) { - message.str(""); - message << "converting heater " << tokens[0] << " to integer"; - this->camera.log_error(function, message.str()); + il_value = std::stoi( tokens[3] ); + if ( il_value < 0 || il_value > 10000 ) { + message.str(""); message << "heater ilim " << il_value << " outside range {0:10000}"; + this->camera.log_error( function, message.str() ); return ERROR; + } + + } catch (std::invalid_argument &) { + message.str(""); message << "converting ILIM " << tokens[3] << " to integer"; + this->camera.log_error( function, message.str() ); + return ERROR; + } catch (std::out_of_range &) { - message.str(""); - message << "heater : " << tokens[0] << " is outside integer range"; - this->camera.log_error(function, message.str()); + message.str(""); message << "ILIM : " << tokens[3] << " outside range of integer"; + this->camera.log_error( function, message.str() ); + return ERROR; + } + ss.str(""); ss << "MOD" << module << "/HEATER" << heaterid << "IL"; heaterconfig.push_back( ss.str() ); + heatervalue.push_back( tokens[3] ); + + } else if ( tokens[2] == "INPUT" ) { // INPUT A|B|C + std::string sensorid; + if ( tokens[3] == "A" ) { + sensorid = "0"; + + } else if ( tokens[3] == "B" ) { + sensorid = "1"; + + } else if ( tokens[3] == "C" ) { + sensorid = "2"; + // input C supported only on HeaterX cards + if ( this->modtype[ module-1 ] != 11 ) { + message.str(""); message << "sensor C not supported on module " << module << ": HeaterX module required"; + this->camera.log_error( function, message.str() ); return ERROR; - } + } - // check that requested module is valid - // - switch (this->modtype[module - 1]) { - case 0: - message.str(""); - message << "module " << module << " not installed"; - this->camera.log_error(function, message.str()); - return ERROR; - case 5: // Heater - case 11: // HeaterX - break; - default: - message.str(""); - message << "module " << module << " not a heater board"; - this->camera.log_error(function, message.str()); - return ERROR; + } else { + message.str(""); message << "invalid sensor " << tokens.at(3) << ": expected A|B INPUT A|B|C"; + this->camera.log_error( function, message.str() ); + return ERROR; } + ss.str(""); ss << "MOD" << module << "/HEATER" << heaterid << "SENSOR"; heaterconfig.push_back( ss.str() ); + heatervalue.push_back( sensorid ); - // Now that we've passed the basic tests (firmware OK, basic syntax OK, and requested module is a heater) - // go ahead and set up some needed variables. - // + } else { + message.str(""); message << "expected heater <" << module << "> ON | RAMP for 3rd argument but got " << tokens[2]; + this->camera.log_error( function, message.str() ); + return ERROR; + } - // heater target min/max depends on backplane version - // - ret = compare_versions(this->backplaneversion, REV_HEATERTARGET); - if (ret == -999) { - message.str(""); - message << "comparing backplane version " << this->backplaneversion << " to " << REV_HEATERTARGET; - this->camera.log_error(function, message.str()); + } else if ( tokens.size() == 5 ) { // RAMP ON + // If there are five (5) tokens then they must be + // A|B RAMP ON + if ( tokens[2] != "RAMP" && tokens[3] != "ON" ) { + message.str(""); message << "expected RAMP ON but got"; for (int i=2; i<5; i++) message << " " << tokens[i]; + this->camera.log_error( function, message.str() ); + return ERROR; + + } else { // got " A|B RAMP ON" now check that the last (5th) token is a number + try { + ramprate = std::stoi( tokens[4] ); + if ( ramprate < 1 || ramprate > 32767 ) { + message.str(""); message << "heater ramprate " << ramprate << " outside range {1:32767}"; + this->camera.log_error( function, message.str() ); return ERROR; - } else if (ret == -1) { - this->heater_target_min = -150.0; - this->heater_target_max = 50.0; - } else { - this->heater_target_min = -250.0; - this->heater_target_max = 50.0; - } + } - heaterconfig.clear(); - heatervalue.clear(); - std::stringstream ss; + } catch (std::invalid_argument &) { + message.str(""); message << "expected RAMP ON but unable to convert =" << tokens[4] << " to integer"; + this->camera.log_error( function, message.str() ); + return ERROR; - // Any one heater command can require reading or writing multiple configuration lines. - // The code will look at each case and push each heater configuration line that needs - // to be read or written into a vector (heaterconfig). For those that need to be written, - // the value to write will be pushed into a separate vector (heatervalue). After these - // vectors have been built up, the code will loop through each element of the vectors, - // reading and/or writing each. + } catch (std::out_of_range &) { + message.str(""); message << "expected RAMP ON but =" << tokens[4] << " outside range of integer"; + this->camera.log_error( function, message.str() ); + return ERROR; + } + ss.str(""); ss << "MOD" << module << "/HEATER" << heaterid << "RAMP"; heaterconfig.push_back( ss.str() ); + heatervalue.emplace_back("1" ); + ss.str(""); ss << "MOD" << module << "/HEATER" << heaterid << "RAMPRATE"; heaterconfig.push_back( ss.str() ); + heatervalue.push_back( tokens[4] ); + } - // Start looking at the heater arguments passed, which have been tokenized. - // There can be only 2, 3, 4, 5, or 6 tokens allowed. + } else if ( tokens.size() == 6 ) { + // If there are six (6) tokens then they must be + // A|B PID

+ if ( tokens[2] != "PID" ) { + message.str(""); message << "expected PID

but got"; for (int i=2; i<6; i++) message << " " << tokens[i]; + this->camera.log_error( function, message.str() ); + return ERROR; - // If there are exactly two (2) tokens then we have received only: - // A|B - // which means we're reading the state and target. + } else { // got " A|B PID

" now check that the last 3 tokens are numbers + // Fractional PID requires a minimum backplane version // - if (tokens.size() == 2) { - // no args reads ENABLE, TARGET - readonly = true; - ss.str(""); - ss << "MOD" << module << "/HEATER" << heaterid << "ENABLE"; - heaterconfig.push_back(ss.str()); - ss.str(""); - ss << "MOD" << module << "/HEATER" << heaterid << "TARGET"; - heaterconfig.push_back(ss.str()); - } else if (tokens.size() == 3) { - // If there are three (3) tokens then the 3rd must be one of the following: - // ON | OFF (for ENABLE), , PID, RAMP, ILIM - if (tokens[2] == "ON") { - // ON - ss.str(""); - ss << "MOD" << module << "/HEATER" << heaterid << "ENABLE"; - heaterconfig.push_back(ss.str()); - heatervalue.emplace_back("1"); - } else if (tokens[2] == "OFF") { - // OFF - ss.str(""); - ss << "MOD" << module << "/HEATER" << heaterid << "ENABLE"; - heaterconfig.push_back(ss.str()); - heatervalue.emplace_back("0"); - } else if (tokens[2] == "RAMP") { - // RAMP - readonly = true; - ss.str(""); - ss << "MOD" << module << "/HEATER" << heaterid << "RAMP"; - heaterconfig.push_back(ss.str()); - ss.str(""); - ss << "MOD" << module << "/HEATER" << heaterid << "RAMPRATE"; - heaterconfig.push_back(ss.str()); - } else if (tokens[2] == "PID") { - // PID - readonly = true; - ss.str(""); - ss << "MOD" << module << "/HEATER" << heaterid << "P"; - heaterconfig.push_back(ss.str()); - ss.str(""); - ss << "MOD" << module << "/HEATER" << heaterid << "I"; - heaterconfig.push_back(ss.str()); - ss.str(""); - ss << "MOD" << module << "/HEATER" << heaterid << "D"; - heaterconfig.push_back(ss.str()); - } else if (tokens[2] == "ILIM") { - // ILIM - readonly = true; - ss.str(""); - ss << "MOD" << module << "/HEATER" << heaterid << "IL"; - heaterconfig.push_back(ss.str()); - } else if (tokens[2] == "INPUT") { - // INPUT - readonly = true; - ss.str(""); - ss << "MOD" << module << "/HEATER" << heaterid << "SENSOR"; - heaterconfig.push_back(ss.str()); - } else { - // - // first check that the requested target is a valid number and within range... - // - try { - target = std::stof(tokens[2]); - if (target < this->heater_target_min || target > this->heater_target_max) { - message.str(""); - message << "requested heater target " << target << " outside range {" - << this->heater_target_min << ":" << this->heater_target_max << "}"; - this->camera.log_error(function, message.str()); - return ERROR; - } - } catch (std::invalid_argument &) { - message.str(""); - message << "converting heater =" << tokens[2] << " to float"; - this->camera.log_error(function, message.str()); - return ERROR; - } catch (std::out_of_range &) { - message.str(""); - message << "heater : " << tokens[2] << " outside range of float"; - this->camera.log_error(function, message.str()); - return ERROR; - } - // ...if target is OK then push into the config, value vectors - // - ss.str(""); - ss << "MOD" << module << "/HEATER" << heaterid << "TARGET"; - heaterconfig.push_back(ss.str()); - heatervalue.push_back(tokens[2]); - } - } else if (tokens.size() == 4) { - // If there are four (4) tokens then the 4th must be one of the following: - // in < A | B > [ ] - // | ON | OFF in < A | B > RAMP [ [ramprate] ] - // in < A | B > ILIM [ ] - // A | B | C in < A | B > INPUT [ A | B | C ] - - if (tokens[2] == "ON") { - // ON - // first check that the requested target is a valid number and within range... - // - try { - target = std::stof(tokens[3]); - if (target < this->heater_target_min || target > this->heater_target_max) { - message.str(""); - message << "requested heater target " << target << " outside range {" - << this->heater_target_min << ":" << this->heater_target_max << "}"; - this->camera.log_error(function, message.str()); - return ERROR; - } - } catch (std::invalid_argument &) { - message.str(""); - message << "converting heater " << tokens[3] << " to float"; - this->camera.log_error(function, message.str()); - return ERROR; - } catch (std::out_of_range &) { - message.str(""); - message << "heater : " << tokens[3] << " outside range of float"; - this->camera.log_error(function, message.str()); - return ERROR; - } - // ...if target is OK then push into the config, value vectors - // - ss.str(""); - ss << "MOD" << module << "/HEATER" << heaterid << "ENABLE"; - heaterconfig.push_back(ss.str()); - heatervalue.emplace_back("1"); - ss.str(""); - ss << "MOD" << module << "/HEATER" << heaterid << "TARGET"; - heaterconfig.push_back(ss.str()); - heatervalue.push_back(tokens[3]); - } else if (tokens[2] == "RAMP") { - // RAMP x - - if (tokens[3] == "ON" || tokens[3] == "OFF") { - // RAMP ON|OFF - ss.str(""); - ss << "MOD" << module << "/HEATER" << heaterid << "RAMP"; - heaterconfig.push_back(ss.str()); - std::string state = (tokens[3] == "ON" ? "1" : "0"); - heatervalue.push_back(state); - } else { - // RAMP - try { - ramprate = std::stoi(tokens[3]); - if (ramprate < 1 || ramprate > 32767) { - message.str(""); - message << "heater ramprate " << ramprate << " outside range {1:32767}"; - this->camera.log_error(function, message.str()); - return ERROR; - } - } catch (std::invalid_argument &) { - message.str(""); - message << "converting RAMP " << tokens[3] << " to integer"; - this->camera.log_error(function, message.str()); - return ERROR; - } catch (std::out_of_range &) { - message.str(""); - message << "RAMP : " << tokens[3] << " outside range of integer"; - this->camera.log_error(function, message.str()); - return ERROR; - } - ss.str(""); - ss << "MOD" << module << "/HEATER" << heaterid << "RAMPRATE"; - heaterconfig.push_back(ss.str()); - heatervalue.push_back(tokens[3]); - } - } else if (tokens[2] == "ILIM") { - // ILIM x - int il_value = 0; - try { - il_value = std::stoi(tokens[3]); - if (il_value < 0 || il_value > 10000) { - message.str(""); - message << "heater ilim " << il_value << " outside range {0:10000}"; - this->camera.log_error(function, message.str()); - return ERROR; - } - } catch (std::invalid_argument &) { - message.str(""); - message << "converting ILIM " << tokens[3] << " to integer"; - this->camera.log_error(function, message.str()); - return ERROR; - } catch (std::out_of_range &) { - message.str(""); - message << "ILIM : " << tokens[3] << " outside range of integer"; - this->camera.log_error(function, message.str()); - return ERROR; - } - ss.str(""); - ss << "MOD" << module << "/HEATER" << heaterid << "IL"; - heaterconfig.push_back(ss.str()); - heatervalue.push_back(tokens[3]); - } else if (tokens[2] == "INPUT") { - // INPUT A|B|C - std::string sensorid; - if (tokens[3] == "A") { - sensorid = "0"; - } else if (tokens[3] == "B") { - sensorid = "1"; - } else if (tokens[3] == "C") { - sensorid = "2"; - // input C supported only on HeaterX cards - if (this->modtype[module - 1] != 11) { - message.str(""); - message << "sensor C not supported on module " << module << ": HeaterX module required"; - this->camera.log_error(function, message.str()); - return ERROR; - } - } else { - message.str(""); - message << "invalid sensor " << tokens.at(3) << ": expected A|B INPUT A|B|C"; - this->camera.log_error(function, message.str()); - return ERROR; - } - ss.str(""); - ss << "MOD" << module << "/HEATER" << heaterid << "SENSOR"; - heaterconfig.push_back(ss.str()); - heatervalue.push_back(sensorid); - } else { - message.str(""); - message << "expected heater <" << module << "> ON | RAMP for 3rd argument but got " << tokens[2]; - this->camera.log_error(function, message.str()); - return ERROR; - } - } else if (tokens.size() == 5) { - // RAMP ON - // If there are five (5) tokens then they must be - // A|B RAMP ON - if (tokens[2] != "RAMP" && tokens[3] != "ON") { - message.str(""); - message << "expected RAMP ON but got"; - for (int i = 2; i < 5; i++) message << " " << tokens[i]; - this->camera.log_error(function, message.str()); - return ERROR; - } else { - // got " A|B RAMP ON" now check that the last (5th) token is a number - try { - ramprate = std::stoi(tokens[4]); - if (ramprate < 1 || ramprate > 32767) { - message.str(""); - message << "heater ramprate " << ramprate << " outside range {1:32767}"; - this->camera.log_error(function, message.str()); - return ERROR; - } - } catch (std::invalid_argument &) { - message.str(""); - message << "expected RAMP ON but unable to convert =" << tokens[4] << - " to integer"; - this->camera.log_error(function, message.str()); - return ERROR; - } catch (std::out_of_range &) { - message.str(""); - message << "expected RAMP ON but =" << tokens[4] << - " outside range of integer"; - this->camera.log_error(function, message.str()); - return ERROR; - } - ss.str(""); - ss << "MOD" << module << "/HEATER" << heaterid << "RAMP"; - heaterconfig.push_back(ss.str()); - heatervalue.emplace_back("1"); - ss.str(""); - ss << "MOD" << module << "/HEATER" << heaterid << "RAMPRATE"; - heaterconfig.push_back(ss.str()); - heatervalue.push_back(tokens[4]); - } - } else if (tokens.size() == 6) { - // If there are six (6) tokens then they must be - // A|B PID

- if (tokens[2] != "PID") { - message.str(""); - message << "expected PID

but got"; - for (int i = 2; i < 6; i++) message << " " << tokens[i]; - this->camera.log_error(function, message.str()); - return ERROR; - } else { - // got " A|B PID

" now check that the last 3 tokens are numbers - // Fractional PID requires a minimum backplane version - // - bool fractionalpid_ok = false; - ret = compare_versions(this->backplaneversion, REV_FRACTIONALPID); - if (ret == -999) { - message.str(""); - message << "comparing backplane version " << this->backplaneversion << " to " << REV_FRACTIONALPID; - this->camera.log_error(function, message.str()); - return ERROR; - } else if (ret == -1) { - fractionalpid_ok = false; - } else fractionalpid_ok = true; + bool fractionalpid_ok = false; + ret = compare_versions( this->backplaneversion, REV_FRACTIONALPID ); + if ( ret == -999 ) { + message.str(""); message << "comparing backplane version " << this->backplaneversion << " to " << REV_FRACTIONALPID; + this->camera.log_error( function, message.str() ); + return ERROR; - try { - // If using backplane where fractional PID was not allowed, check for decimal points - // - if ((!fractionalpid_ok) && - ((tokens[3].find('.') != std::string::npos) || - (tokens[4].find('.') != std::string::npos) || - (tokens[5].find('.') != std::string::npos))) { - fesetround(FE_TONEAREST); // round to value nearest X. halfway cases rounded away from zero - tokens[3] = std::to_string(std::lrint(std::stof(tokens[3]))); - // replace token with rounded integer - tokens[4] = std::to_string(std::lrint(std::stof(tokens[4]))); - // replace token with rounded integer - tokens[5] = std::to_string(std::lrint(std::stof(tokens[5]))); - // replace token with rounded integer - - message.str(""); - message << "NOTICE:fractional heater PID requires backplane version " << REV_FRACTIONALPID << - " or newer"; - logwrite(function, message.str()); - this->camera.async.enqueue(message.str()); - message.str(""); - message << "NOTICE:backplane version " << this->backplaneversion << " detected"; - logwrite(function, message.str()); - this->camera.async.enqueue(message.str()); - message.str(""); - message << "NOTICE:PIDs converted to: " << tokens[3] << " " << tokens[4] << " " << tokens[5]; - this->camera.async.enqueue(message.str()); - logwrite(function, message.str()); - } - pid_p = std::stof(tokens[3]); - pid_i = std::stof(tokens[4]); - pid_d = std::stof(tokens[5]); - if (pid_p < 0 || pid_p > 10000 || pid_i < 0 || pid_i > 10000 || pid_d < 0 || pid_d > 10000) { - message.str(""); - message << "one or more heater PID values outside range {0:10000}"; - this->camera.log_error(function, message.str()); - return ERROR; - } - } catch (std::invalid_argument &) { - message.str(""); - message << "converting one or more heater PID values to numbers:"; - for (int i = 3; i < 6; i++) message << " " << tokens[i]; - this->camera.log_error(function, message.str()); - return ERROR; - } catch (std::out_of_range &) { - message.str(""); - message << "heater PID exception: one or more values outside range:"; - for (int i = 3; i < 6; i++) message << " " << tokens[i]; - this->camera.log_error(function, message.str()); - return ERROR; - } - ss.str(""); - ss << "MOD" << module << "/HEATER" << heaterid << "P"; - heaterconfig.push_back(ss.str()); - heatervalue.push_back(tokens[3]); - ss.str(""); - ss << "MOD" << module << "/HEATER" << heaterid << "I"; - heaterconfig.push_back(ss.str()); - heatervalue.push_back(tokens[4]); - ss.str(""); - ss << "MOD" << module << "/HEATER" << heaterid << "D"; - heaterconfig.push_back(ss.str()); - heatervalue.push_back(tokens[5]); - } - } else { - // Otherwise we have an invalid number of tokens - message.str(""); - message << "received " << tokens.size() << " arguments but expected 2, 3, 4, 5, or 6"; - this->camera.log_error(function, message.str()); + } else if ( ret == -1 ) { + fractionalpid_ok = false; + + } else fractionalpid_ok = true; + + try { + // If using backplane where fractional PID was not allowed, check for decimal points + // + if ( (!fractionalpid_ok) && + ( ( tokens[3].find('.') != std::string::npos ) || + ( tokens[4].find('.') != std::string::npos ) || + ( tokens[5].find('.') != std::string::npos ) ) ) { + fesetround(FE_TONEAREST); // round to value nearest X. halfway cases rounded away from zero + tokens[3] = std::to_string( std::lrint( std::stof( tokens[3] ) ) ); // replace token with rounded integer + tokens[4] = std::to_string( std::lrint( std::stof( tokens[4] ) ) ); // replace token with rounded integer + tokens[5] = std::to_string( std::lrint( std::stof( tokens[5] ) ) ); // replace token with rounded integer + + message.str(""); message << "NOTICE:fractional heater PID requires backplane version " << REV_FRACTIONALPID << " or newer"; + logwrite( function, message.str() ); + this->camera.async.enqueue( message.str() ); + message.str(""); message << "NOTICE:backplane version " << this->backplaneversion << " detected"; + logwrite( function, message.str() ); + this->camera.async.enqueue( message.str() ); + message.str(""); message << "NOTICE:PIDs converted to: " << tokens[3] << " " << tokens[4] << " " << tokens[5]; + this->camera.async.enqueue( message.str() ); + logwrite( function, message.str() ); + + } + pid_p = std::stof( tokens[3] ); + pid_i = std::stof( tokens[4] ); + pid_d = std::stof( tokens[5] ); + if ( pid_p < 0 || pid_p > 10000 || pid_i < 0 || pid_i > 10000 || pid_d < 0 || pid_d > 10000 ) { + message.str(""); message << "one or more heater PID values outside range {0:10000}"; + this->camera.log_error( function, message.str() ); return ERROR; - } + } - long error; + } catch (std::invalid_argument &) { + message.str(""); message << "converting one or more heater PID values to numbers:"; + for (int i=3; i<6; i++) message << " " << tokens[i]; + this->camera.log_error( function, message.str() ); + return ERROR; - if (!readonly) { - // For writing, loop through the heaterconfig and heatervalue vectors. - // They MUST be the same size! This should be impossible. - // - if (heaterconfig.size() != heatervalue.size()) { - message.str(""); - message << "BUG DETECTED: heaterconfig (" << heaterconfig.size() - << ") - heatervalue (" << heatervalue.size() << ") vector size mismatch"; - this->camera.log_error(function, message.str()); - return ERROR; - } + } catch (std::out_of_range &) { + message.str(""); message << "heater PID exception: one or more values outside range:"; + for (int i=3; i<6; i++) message << " " << tokens[i]; + this->camera.log_error( function, message.str() ); + return ERROR; + } + ss.str(""); ss << "MOD" << module << "/HEATER" << heaterid << "P"; heaterconfig.push_back( ss.str() ); + heatervalue.push_back( tokens[3] ); + ss.str(""); ss << "MOD" << module << "/HEATER" << heaterid << "I"; heaterconfig.push_back( ss.str() ); + heatervalue.push_back( tokens[4] ); + ss.str(""); ss << "MOD" << module << "/HEATER" << heaterid << "D"; heaterconfig.push_back( ss.str() ); + heatervalue.push_back( tokens[5] ); + } - // Write the configuration lines - // - bool changed = false; - size_t error_count = 0; - for (size_t i = 0; i < heaterconfig.size(); i++) { - error = this->write_config_key(heaterconfig[i].c_str(), heatervalue[i].c_str(), changed); - message.str(""); - if (error != NO_ERROR) { - message << "writing configuration " << heaterconfig[i] << "=" << heatervalue[i]; - error_count++; // this counter will be checked before the APPLYMOD command - } else if (!changed) { - message << "heater configuration: " << heaterconfig[i] << "=" << heatervalue[i] << " unchanged"; - } else { - message << "updated heater configuration: " << heaterconfig[i] << "=" << heatervalue[i]; - } - error == NO_ERROR ? logwrite(function, message.str()) : this->camera.log_error(function, message.str()); - } + } else { + // Otherwise we have an invalid number of tokens + message.str(""); message << "received " << tokens.size() << " arguments but expected 2, 3, 4, 5, or 6"; + this->camera.log_error( function, message.str() ); + return ERROR; + } - // send the APPLMODxx command which parses and applies the configuration for module xx - // The APPLYMOD is sent even if an error occured writing the config key(s) because - // it's possible that one of the config keys was written. - // - // If error_count is the same as the number of configuration lines, then they all failed - // to write, in which case do not send APPLYMOD. But if even one config key was written - // then the APPLYMOD command must be sent. - // - if (error_count == heaterconfig.size()) { - return ERROR; - } + long error; + + if ( ! readonly ) { + + // For writing, loop through the heaterconfig and heatervalue vectors. + // They MUST be the same size! This should be impossible. + // + if ( heaterconfig.size() != heatervalue.size() ) { + message.str(""); + message << "BUG DETECTED: heaterconfig (" << heaterconfig.size() + << ") - heatervalue (" << heatervalue.size() << ") vector size mismatch"; + this->camera.log_error( function, message.str() ); + return ERROR; + } - std::stringstream applystr; - applystr << "APPLYMOD" - << std::setfill('0') - << std::setw(2) - << std::hex - << (module - 1); + // Write the configuration lines + // + bool changed = false; + size_t error_count = 0; + for ( size_t i=0; i < heaterconfig.size(); i++ ) { + error = this->write_config_key( heaterconfig[i].c_str(), heatervalue[i].c_str(), changed ); + message.str(""); + if ( error != NO_ERROR ) { + message << "writing configuration " << heaterconfig[i] << "=" << heatervalue[i]; + error_count++; // this counter will be checked before the APPLYMOD command - error = this->archon_cmd(applystr.str()); + } else if ( !changed ) { + message << "heater configuration: " << heaterconfig[i] << "=" << heatervalue[i] << " unchanged"; - if (error != NO_ERROR) { - logwrite(function, "ERROR: applying heater configuration"); - } + } else { + message << "updated heater configuration: " << heaterconfig[i] << "=" << heatervalue[i]; } + error == NO_ERROR ? logwrite( function, message.str() ) : this->camera.log_error( function, message.str() ); + } - // Now read the configuration line(s). - // For multiple lines, concatenate all values into one space-delimited string. + // send the APPLMODxx command which parses and applies the configuration for module xx + // The APPLYMOD is sent even if an error occured writing the config key(s) because + // it's possible that one of the config keys was written. + // + // If error_count is the same as the number of configuration lines, then they all failed + // to write, in which case do not send APPLYMOD. But if even one config key was written + // then the APPLYMOD command must be sent. + // + if ( error_count == heaterconfig.size() ) { + return ERROR; + } - // loop through the vector of heaterconfig keys, - // getting the value for each, and putting them into retss - // - std::string value; - std::stringstream retss; - for (const auto &key: heaterconfig) { - error = this->get_configmap_value(key, value); + std::stringstream applystr; + applystr << "APPLYMOD" + << std::setfill('0') + << std::setw(2) + << std::hex + << (module-1); - if (error != NO_ERROR) { - message.str(""); - message << "reading heater configuration " << key; - logwrite(function, message.str()); - return error; - } else { - // If key ends with "ENABLE" or "RAMP" - // then convert the values 0,1 to OFF,ON, respectively - // - if (key.substr(key.length() - 6) == "ENABLE" || - key.substr(key.length() - 4) == "RAMP") { - if (value == "0") value = "OFF"; - else if (value == "1") value = "ON"; - else { - message.str(""); - message << "bad value " << value << " from configuration. expected 0 or 1"; - this->camera.log_error(function, message.str()); - error = ERROR; - } - } else if (key.substr(key.length() - 6) == "SENSOR") { - // or if key ends with "SENSOR" then map the value (0,1,2) to the name (A,B,C) - if (value == "0") value = "A"; - else if (value == "1") value = "B"; - else if (value == "2") value = "C"; - else { - message.str(""); - message << "bad value " << value << " from configuration. expected 0,1,2"; - this->camera.log_error(function, message.str()); - error = ERROR; - } - } - retss << value << " "; - message.str(""); - message << key << "=" << value; - logwrite(function, message.str()); - } - } - retstring = retss.str(); // return value to calling function, passed by reference + error = this->archon_cmd( applystr.str() ); - return (error); + if ( error != NO_ERROR ) { + logwrite( function, "ERROR: applying heater configuration" ); + } } - /**************** Archon::Interface::heater *********************************/ + // Now read the configuration line(s). + // For multiple lines, concatenate all values into one space-delimited string. + // loop through the vector of heaterconfig keys, + // getting the value for each, and putting them into retss + // + std::string value; + std::stringstream retss; + for ( const auto &key : heaterconfig ) { - /**************** Archon::Interface::sensor *********************************/ - /** - * @fn sensor - * @brief set or get temperature sensor current - * @param args contains various allowable strings (see full descsription) - * @return ERROR or NO_ERROR - * - * sensor < A | B | C > [ current ] - * possible args: 2 (get) or 3 (set) - * - * sensor < A | B | C > AVG [ N ] - * possible args: 3 (get) or 4 (set) - * - * Sets or gets the temperature sensor current for the specified - * sensor on the specified module . refers to the - * (integer) module number. is specified in nano-amps. - * This is used only for RTDs - * - * When the AVG arg is used then set or get digital averaging - * - */ - long Interface::sensor(std::string args, std::string &retstring) { - std::string function = "Archon::Interface::sensor"; - std::stringstream message; - std::vector tokens; - std::string sensorid; //!< A | B | C - std::stringstream sensorconfig; //!< configuration line to read or write - std::string sensorvalue; //!< configuration line value - int module; //!< integer module number - bool readonly = true; //!< true is reading, not writing current - long error; - - // must have loaded firmware // TODO implement a command to read the configuration - // // memory from Archon, in order to remove this restriction. - // - if (!this->firmwareloaded) { - this->camera.log_error(function, "firmware not loaded"); - return ERROR; - } + error = this->get_configmap_value( key, value ); - // requires a minimum backplane version - // - int ret = compare_versions(this->backplaneversion, REV_SENSORCURRENT); - if (ret < 0) { - if (ret == -999) { - message << "comparing backplane version " << this->backplaneversion << " to " << REV_SENSORCURRENT; - } else { - message << "requires backplane version " << REV_SENSORCURRENT << " or newer. (" - << this->backplaneversion << " detected)"; - } - this->camera.log_error(function, message.str()); - return ERROR; + if ( error != NO_ERROR ) { + message.str(""); message << "reading heater configuration " << key; + logwrite( function, message.str() ); + return error; + + } else { + // If key ends with "ENABLE" or "RAMP" + // then convert the values 0,1 to OFF,ON, respectively + // + if ( key.substr( key.length()-6 ) == "ENABLE" || + key.substr( key.length()-4 ) == "RAMP" ) { + if ( value == "0" ) value = "OFF"; + else if ( value == "1" ) value = "ON"; + else { + message.str(""); message << "bad value " << value << " from configuration. expected 0 or 1"; + this->camera.log_error( function, message.str() ); + error = ERROR; + } + + } else if ( key.substr( key.length()-6 ) == "SENSOR" ) { + // or if key ends with "SENSOR" then map the value (0,1,2) to the name (A,B,C) + if ( value == "0" ) value = "A"; + else if ( value == "1" ) value = "B"; + else if ( value == "2" ) value = "C"; + else { + message.str(""); message << "bad value " << value << " from configuration. expected 0,1,2"; + this->camera.log_error( function, message.str() ); + error = ERROR; + } } + retss << value << " "; + message.str(""); message << key << "=" << value; + logwrite( function, message.str() ); + } + } + retstring = retss.str(); // return value to calling function, passed by reference + + return ( error ); + } + /**************** Archon::Interface::heater *********************************/ + + + /**************** Archon::Interface::sensor *********************************/ + /** + * @fn sensor + * @brief set or get temperature sensor current + * @param args contains various allowable strings (see full descsription) + * @return ERROR or NO_ERROR + * + * sensor < A | B | C > [ current ] + * possible args: 2 (get) or 3 (set) + * + * sensor < A | B | C > AVG [ N ] + * possible args: 3 (get) or 4 (set) + * + * Sets or gets the temperature sensor current for the specified + * sensor on the specified module . refers to the + * (integer) module number. is specified in nano-amps. + * This is used only for RTDs + * + * When the AVG arg is used then set or get digital averaging + * + */ + long Interface::sensor(std::string args, std::string &retstring) { + std::string function = "Archon::Interface::sensor"; + std::stringstream message; + std::vector tokens; + std::string sensorid; //!< A | B | C + std::stringstream sensorconfig; //!< configuration line to read or write + std::string sensorvalue; //!< configuration line value + int module; //!< integer module number + bool readonly=true; //!< true is reading, not writing current + long error; + + // must have loaded firmware // TODO implement a command to read the configuration + // // memory from Archon, in order to remove this restriction. + // + if ( ! this->firmwareloaded ) { + this->camera.log_error( function, "firmware not loaded" ); + return ERROR; + } + + // requires a minimum backplane version + // + int ret = compare_versions( this->backplaneversion, REV_SENSORCURRENT ); + if ( ret < 0 ) { + if ( ret == -999 ) { + message << "comparing backplane version " << this->backplaneversion << " to " << REV_SENSORCURRENT; + + } else { + message << "requires backplane version " << REV_SENSORCURRENT << " or newer. (" + << this->backplaneversion << " detected)"; + } + this->camera.log_error( function, message.str() ); + return ERROR; + } + + std::transform( args.begin(), args.end(), args.begin(), ::toupper ); // make uppercase + + Tokenize( args, tokens, " " ); + + // At minimum there must be two tokens, + // + if ( tokens.size() < 2 ) { + this->camera.log_error( function, "expected at least two arguments: A|B" ); + return ERROR; + } + + // Get the module and sensorid + // + try { + module = std::stoi( tokens.at(0) ); + sensorid = tokens.at(1); + + if ( sensorid != "A" && sensorid != "B" && sensorid != "C" ) { + message.str(""); message << "invalid sensor " << sensorid << ": expected [ current | AVG [N] ]"; + this->camera.log_error( function, message.str() ); + return ERROR; + } - std::transform(args.begin(), args.end(), args.begin(), ::toupper); // make uppercase + } catch ( std::invalid_argument & ) { + message.str(""); message << "parsing argument: " << args << ": expected [ current | AVG [N] ]"; + this->camera.log_error( function, message.str() ); + return ERROR; - Tokenize(args, tokens, " "); + } catch ( std::out_of_range & ) { + message.str(""); message << "argument outside range in " << args << ": expected [ current | AVG [N] ]"; + this->camera.log_error( function, message.str() ); + return ERROR; + } - // At minimum there must be two tokens, - // - if (tokens.size() < 2) { - this->camera.log_error(function, "expected at least two arguments: A|B"); - return ERROR; - } + // check that the requested module is valid + // + switch ( this->modtype[ module-1 ] ) { + case 0: + message.str(""); message << "module " << module << " not installed"; + this->camera.log_error( function, message.str() ); + return ERROR; + case 5: // Heater + case 11: // HeaterX + break; + default: + message.str(""); message << "module " << module << " is not a heater board"; + this->camera.log_error( function, message.str() ); + return ERROR; + } - // Get the module and sensorid - // + // input C supported only on HeaterX cards + if ( sensorid == "C" && this->modtype[ module-1 ] != 11 ) { + message.str(""); message << "sensor C not supported on module " << module << ": HeaterX module required"; + this->camera.log_error( function, message.str() ); + return ERROR; + } + + // Now check the number of tokens to decide how to next proceed... + + // If there are 2 tokens then must be to read the current, + // < A | B | C > + // + if ( tokens.size() == 2 ) { + readonly = true; + sensorconfig << "MOD" << module << "/SENSOR" << sensorid << "CURRENT"; + + } else if ( tokens.size() == 3 ) { + // If there are 3 tokens then it is either to write the current, + // < A | B | C > current + // or to read the average, + // < A | B | C > AVG + // if the 3rd arg is AVG then read the average (MODmSENSORxFILTER) + // + if ( tokens[2] == "AVG" ) { + readonly = true; + sensorconfig << "MOD" << module << "/SENSOR" << sensorid << "FILTER"; + } else { + // if it's not AVG then assume it's a current value and try to convert it to int + // + int current_val=-1; try { - module = std::stoi(tokens.at(0)); - sensorid = tokens.at(1); + current_val = std::stoi( tokens[2] ); - if (sensorid != "A" && sensorid != "B" && sensorid != "C") { - message.str(""); - message << "invalid sensor " << sensorid << ": expected [ current | AVG [N] ]"; - this->camera.log_error(function, message.str()); - return ERROR; - } - } catch (std::invalid_argument &) { - message.str(""); - message << "parsing argument: " << args << ": expected [ current | AVG [N] ]"; - this->camera.log_error(function, message.str()); - return ERROR; - } catch (std::out_of_range &) { - message.str(""); - message << "argument outside range in " << args << ": expected [ current | AVG [N] ]"; - this->camera.log_error(function, message.str()); - return ERROR; - } + } catch ( std::invalid_argument & ) { + message.str(""); message << "parsing \"" << args << "\" : expected \"AVG\" or integer for arg 3"; + this->camera.log_error( function, message.str() ); + return ERROR; - // check that the requested module is valid - // - switch (this->modtype[module - 1]) { - case 0: - message.str(""); - message << "module " << module << " not installed"; - this->camera.log_error(function, message.str()); - return ERROR; - case 5: // Heater - case 11: // HeaterX - break; - default: - message.str(""); - message << "module " << module << " is not a heater board"; - this->camera.log_error(function, message.str()); - return ERROR; + } catch ( std::out_of_range & ) { + message.str(""); message << "parsing \"" << args << "\" : arg 3 outside integer range"; + this->camera.log_error( function, message.str() ); + return ERROR; } - // input C supported only on HeaterX cards - if (sensorid == "C" && this->modtype[module - 1] != 11) { - message.str(""); - message << "sensor C not supported on module " << module << ": HeaterX module required"; - this->camera.log_error(function, message.str()); - return ERROR; + // successfully converted value so check the range + // + if ( current_val < 0 || current_val > 1600000 ) { + message.str(""); message << "requested current " << current_val << " outside range {0:1600000}"; + this->camera.log_error( function, message.str() ); + return ERROR; } - // Now check the number of tokens to decide how to next proceed... - - // If there are 2 tokens then must be to read the current, - // < A | B | C > + // prepare sensorconfig string for writing // - if (tokens.size() == 2) { - readonly = true; - sensorconfig << "MOD" << module << "/SENSOR" << sensorid << "CURRENT"; - } else if (tokens.size() == 3) { - // If there are 3 tokens then it is either to write the current, - // < A | B | C > current - // or to read the average, - // < A | B | C > AVG - // if the 3rd arg is AVG then read the average (MODmSENSORxFILTER) - // - if (tokens[2] == "AVG") { - readonly = true; - sensorconfig << "MOD" << module << "/SENSOR" << sensorid << "FILTER"; - } else { - // if it's not AVG then assume it's a current value and try to convert it to int - // - int current_val = -1; - try { - current_val = std::stoi(tokens[2]); - } catch (std::invalid_argument &) { - message.str(""); - message << "parsing \"" << args << "\" : expected \"AVG\" or integer for arg 3"; - this->camera.log_error(function, message.str()); - return ERROR; - } catch (std::out_of_range &) { - message.str(""); - message << "parsing \"" << args << "\" : arg 3 outside integer range"; - this->camera.log_error(function, message.str()); - return ERROR; - } + readonly = false; + sensorconfig << "MOD" << module << "/SENSOR" << sensorid << "CURRENT"; + sensorvalue = tokens[2]; + } + } else if ( tokens.size() == 4 ) { // set avg + // If there are 4 tokens thenn it must be to write the average, + // < A | B | C > AVG N + // check the contents of the 3rd arg + // + if ( tokens[2] != "AVG" ) { + message.str(""); message << "invalid syntax \"" << tokens[2] << "\". expected A|B|C AVG N"; + } - // successfully converted value so check the range - // - if (current_val < 0 || current_val > 1600000) { - message.str(""); - message << "requested current " << current_val << " outside range {0:1600000}"; - this->camera.log_error(function, message.str()); - return ERROR; - } + // convert the avg value N to int and check for proper value + int filter_val=-1; + try { + filter_val = std::stoi( tokens[3] ); - // prepare sensorconfig string for writing - // - readonly = false; - sensorconfig << "MOD" << module << "/SENSOR" << sensorid << "CURRENT"; - sensorvalue = tokens[2]; - } - } else if (tokens.size() == 4) { - // set avg - // If there are 4 tokens thenn it must be to write the average, - // < A | B | C > AVG N - // check the contents of the 3rd arg - // - if (tokens[2] != "AVG") { - message.str(""); - message << "invalid syntax \"" << tokens[2] << "\". expected A|B|C AVG N"; - } + } catch ( std::invalid_argument & ) { + message.str(""); message << "parsing \"" << args << "\" : expected integer for arg 4"; + this->camera.log_error( function, message.str() ); + return ERROR; - // convert the avg value N to int and check for proper value - int filter_val = -1; - try { - filter_val = std::stoi(tokens[3]); - } catch (std::invalid_argument &) { - message.str(""); - message << "parsing \"" << args << "\" : expected integer for arg 4"; - this->camera.log_error(function, message.str()); - return ERROR; - } catch (std::out_of_range &) { - message.str(""); - message << "parsing \"" << args << "\" : arg 4 outside integer range"; - this->camera.log_error(function, message.str()); - return ERROR; - } + } catch ( std::out_of_range & ) { + message.str(""); message << "parsing \"" << args << "\" : arg 4 outside integer range"; + this->camera.log_error( function, message.str() ); + return ERROR; + } - // prepare sensorconfig string for writing - // - readonly = false; - sensorconfig << "MOD" << module << "/SENSOR" << sensorid << "FILTER"; - - switch (filter_val) { - case 1: sensorvalue = "0"; - break; - case 2: sensorvalue = "1"; - break; - case 4: sensorvalue = "2"; - break; - case 8: sensorvalue = "3"; - break; - case 16: sensorvalue = "4"; - break; - case 32: sensorvalue = "5"; - break; - case 64: sensorvalue = "6"; - break; - case 128: sensorvalue = "7"; - break; - case 256: sensorvalue = "8"; - break; - break; - default: - message.str(""); - message << "requested average " << filter_val << " outside range {1,2,4,8,16,32,64,128,256}"; - this->camera.log_error(function, message.str()); - return (ERROR); - } - } else { - // Otherwise an invalid number of tokens - message.str(""); - message << "received " << tokens.size() << " arguments but expected 2, 3, or 4"; - this->camera.log_error(function, message.str()); - return ERROR; - } + // prepare sensorconfig string for writing + // + readonly = false; + sensorconfig << "MOD" << module << "/SENSOR" << sensorid << "FILTER"; + + switch ( filter_val ) { + case 1: sensorvalue = "0"; break; + case 2: sensorvalue = "1"; break; + case 4: sensorvalue = "2"; break; + case 8: sensorvalue = "3"; break; + case 16: sensorvalue = "4"; break; + case 32: sensorvalue = "5"; break; + case 64: sensorvalue = "6"; break; + case 128: sensorvalue = "7"; break; + case 256: sensorvalue = "8"; break; + break; + default: + message.str(""); message << "requested average " << filter_val << " outside range {1,2,4,8,16,32,64,128,256}"; + this->camera.log_error( function, message.str() ); + return ( ERROR ); + } + } else { + // Otherwise an invalid number of tokens + message.str(""); message << "received " << tokens.size() << " arguments but expected 2, 3, or 4"; + this->camera.log_error( function, message.str() ); + return ERROR; + } -#ifdef LOGLEVEL_DEBUG + #ifdef LOGLEVEL_DEBUG message.str(""); message << "[DEBUG] module=" << module << " sensorid=" << sensorid << " readonly=" << ( readonly ? "true" : "false" ) << " sensorconfig=" << sensorconfig.str() << " sensorvalue=" << sensorvalue; logwrite( function, message.str() ); -#endif + #endif - std::string sensorkey = sensorconfig.str(); + std::string sensorkey = sensorconfig.str(); - if (!readonly) { - // should be impossible but just make sure these aren't empty because they're needed - // - if (sensorconfig.rdbuf()->in_avail() == 0 || sensorvalue.empty()) { - this->camera.log_error(function, "BUG DETECTED: sensorconfig and sensorvalue cannot be empty"); - return (ERROR); - } + if ( ! readonly ) { - // Write the config line to update the sensor current - // - bool changed = false; - error = this->write_config_key(sensorkey.c_str(), sensorvalue.c_str(), changed); + // should be impossible but just make sure these aren't empty because they're needed + // + if ( sensorconfig.rdbuf()->in_avail() == 0 || sensorvalue.empty() ) { + this->camera.log_error( function, "BUG DETECTED: sensorconfig and sensorvalue cannot be empty" ); + return ( ERROR ); + } - // Now send the APPLYMODx command - // - std::stringstream applystr; - applystr << "APPLYMOD" - << std::setfill('0') - << std::setw(2) - << std::hex - << (module - 1); + // Write the config line to update the sensor current + // + bool changed = false; + error = this->write_config_key( sensorkey.c_str(), sensorvalue.c_str(), changed ); - if (error == NO_ERROR) error = this->archon_cmd(applystr.str()); + // Now send the APPLYMODx command + // + std::stringstream applystr; + applystr << "APPLYMOD" + << std::setfill('0') + << std::setw(2) + << std::hex + << (module-1); - message.str(""); + if ( error == NO_ERROR ) error = this->archon_cmd( applystr.str() ); - if (error != NO_ERROR) { - message << "writing sensor configuration: " << sensorkey << "=" << sensorvalue; - } else if (!changed) { - message << "sensor configuration: " << sensorkey << "=" << sensorvalue << " unchanged"; - } else { - message << "updated sensor configuration: " << sensorkey << "=" << sensorvalue; - } - logwrite(function, message.str()); - } + message.str(""); - // now read back the configuration line - // - std::string value; - error = this->get_configmap_value(sensorkey, value); + if ( error != NO_ERROR ) { + message << "writing sensor configuration: " << sensorkey << "=" << sensorvalue; - if (error != NO_ERROR) { - message.str(""); - message << "reading sensor configuration " << sensorkey; - logwrite(function, message.str()); - return error; - } + } else if ( !changed ) { + message << "sensor configuration: " << sensorkey << "=" << sensorvalue << " unchanged"; - // return value to calling function, passed by reference - // - retstring = value; + } else { + message << "updated sensor configuration: " << sensorkey << "=" << sensorvalue; + } + logwrite( function, message.str() ); + } - // if key ends with "FILTER" - // then convert the return value {0,1,2,...} to {1,2,4,8,...} - // - if (sensorkey.substr(sensorkey.length() - 6) == "FILTER") { - // array of filter values that humans use - // - std::array filter = {"1", "2", "4", "8", "16", "32", "64", "128", "256"}; + // now read back the configuration line + // + std::string value; + error = this->get_configmap_value( sensorkey, value ); - // the value in the configuration is an index into the above array - // - int findex = 0; + if ( error != NO_ERROR ) { + message.str(""); message << "reading sensor configuration " << sensorkey; + logwrite( function, message.str() ); + return error; + } - try { - findex = std::stoi(value); - } catch (std::invalid_argument &) { - message.str(""); - message << "bad value: " << value << " read back from configuration. expected integer"; - this->camera.log_error(function, message.str()); - return ERROR; - } catch (std::out_of_range &) { - message.str(""); - message << "value: " << value << " read back from configuration outside integer range"; - this->camera.log_error(function, message.str()); - return ERROR; - } + // return value to calling function, passed by reference + // + retstring = value; - try { - retstring = filter.at(findex); // return value to calling function, passed by reference - } catch (std::out_of_range &) { - message.str(""); - message << "filter index " << findex << " outside range: {0:" << filter.size() - 1 << "}"; - this->camera.log_error(function, message.str()); - return ERROR; - } - } + // if key ends with "FILTER" + // then convert the return value {0,1,2,...} to {1,2,4,8,...} + // + if ( sensorkey.substr( sensorkey.length()-6 ) == "FILTER" ) { - message.str(""); - message << sensorkey << "=" << value << " (" << retstring << ")"; - logwrite(function, message.str()); + // array of filter values that humans use + // + std::array< std::string, 9 > filter = { "1", "2", "4", "8", "16", "32", "64", "128", "256" }; - return (error); + // the value in the configuration is an index into the above array + // + int findex=0; + + try { + findex = std::stoi( value ); + + } catch ( std::invalid_argument & ) { + message.str(""); message << "bad value: " << value << " read back from configuration. expected integer"; + this->camera.log_error( function, message.str() ); + return ERROR; + + } catch ( std::out_of_range & ) { + message.str(""); message << "value: " << value << " read back from configuration outside integer range"; + this->camera.log_error( function, message.str() ); + return ERROR; + } + + try { + retstring = filter.at( findex ); // return value to calling function, passed by reference + + } catch ( std::out_of_range & ) { + message.str(""); message << "filter index " << findex << " outside range: {0:" << filter.size()-1 << "}"; + this->camera.log_error( function, message.str() ); + return ERROR; + } } - /**************** Archon::Interface::sensor *********************************/ + message.str(""); message << sensorkey << "=" << value << " (" << retstring << ")"; + logwrite( function, message.str() ); + return ( error ); + } + /**************** Archon::Interface::sensor *********************************/ + + + /**************** Archon::Interface::bias ***********************************/ + /** + * @fn bias + * @brief set a bias + * @param args contains: module, channel, bias + * @return ERROR or NO_ERROR + * + */ + long Interface::bias(std::string args, std::string &retstring) { + std::string function = "Archon::Interface::bias"; + std::stringstream message; + std::vector tokens; + std::stringstream biasconfig; + int module; + int channel; + float voltage; + float vmin, vmax; + bool readonly=true; + + // must have loaded firmware + // + if ( ! this->firmwareloaded ) { + this->camera.log_error( function, "firmware not loaded" ); + return ERROR; + } - /**************** Archon::Interface::bias ***********************************/ - /** - * @fn bias - * @brief set a bias - * @param args contains: module, channel, bias - * @return ERROR or NO_ERROR - * - */ - long Interface::bias(std::string args, std::string &retstring) { - std::string function = "Archon::Interface::bias"; - std::stringstream message; - std::vector tokens; - std::stringstream biasconfig; - int module; - int channel; - float voltage; - float vmin, vmax; - bool readonly = true; - - // must have loaded firmware - // - if (!this->firmwareloaded) { - this->camera.log_error(function, "firmware not loaded"); - return ERROR; - } + Tokenize(args, tokens, " "); - Tokenize(args, tokens, " "); + if (tokens.size() == 2) { + readonly = true; - if (tokens.size() == 2) { - readonly = true; - } else if (tokens.size() == 3) { - readonly = false; - } else { - message.str(""); - message << "incorrect number of arguments: " << args << ": expected module channel [voltage]"; - this->camera.log_error(function, message.str()); - return ERROR; - } + } else if (tokens.size() == 3) { + readonly = false; - std::transform(args.begin(), args.end(), args.begin(), ::toupper); // make uppercase + } else { + message.str(""); message << "incorrect number of arguments: " << args << ": expected module channel [voltage]"; + this->camera.log_error( function, message.str() ); + return ERROR; + } - try { - module = std::stoi(tokens[0]); - channel = std::stoi(tokens[1]); - if (!readonly) voltage = std::stof(tokens[2]); - } catch (std::invalid_argument &) { - message.str(""); - message << "parsing bias arguments: " << args << ": expected [ voltage ]"; - this->camera.log_error(function, message.str()); - return ERROR; - } catch (std::out_of_range &) { - message.str(""); - message << "argument range: " << args << ": expected [ voltage ]"; - this->camera.log_error(function, message.str()); - return ERROR; - } + std::transform( args.begin(), args.end(), args.begin(), ::toupper ); // make uppercase - // Check that the module number is valid - // - if ((module < 0) || (module > nmods)) { - message.str(""); - message << "module " << module << ": outside range {0:" << nmods << "}"; - this->camera.log_error(function, message.str()); - return ERROR; - } + try { + module = std::stoi( tokens[0] ); + channel = std::stoi( tokens[1] ); + if (!readonly) voltage = std::stof( tokens[2] ); - // Use the module type to get LV or HV Bias - // and start building the bias configuration string. - // - switch (this->modtype[module - 1]) { - case 0: - message.str(""); - message << "module " << module << " not installed"; - this->camera.log_error(function, message.str()); - return ERROR; - break; - case 3: // LVBias - case 9: // LVXBias - biasconfig << "MOD" << module << "/LV"; - vmin = -14.0; - vmax = +14.0; - break; - case 4: // HVBias - case 8: // HVXBias - biasconfig << "MOD" << module << "/HV"; - vmin = 0.0; - vmax = +31.0; - break; - default: - message.str(""); - message << "module " << module << " not a bias board"; - this->camera.log_error(function, message.str()); - return ERROR; - break; - } + } catch (std::invalid_argument &) { + message.str(""); message << "parsing bias arguments: " << args << ": expected [ voltage ]"; + this->camera.log_error( function, message.str() ); + return ERROR; - // Check that the channel number is valid - // and add it to the bias configuration string. - // - if ((channel < 1) || (channel > 30)) { - message.str(""); - message << "bias channel " << module << ": outside range {1:30}"; - this->camera.log_error(function, message.str()); - return ERROR; - } - if ((channel > 0) && (channel < 25)) { - biasconfig << "LC_V" << channel; - } - if ((channel > 24) && (channel < 31)) { - channel -= 24; - biasconfig << "HC_V" << channel; - } + } catch (std::out_of_range &) { + message.str(""); message << "argument range: " << args << ": expected [ voltage ]"; + this->camera.log_error( function, message.str() ); + return ERROR; + } - if ((voltage < vmin) || (voltage > vmax)) { - message.str(""); - message << "bias voltage " << voltage << ": outside range {" << vmin << ":" << vmax << "}"; - this->camera.log_error(function, message.str()); - return ERROR; - } + // Check that the module number is valid + // + if ( (module < 0) || (module > nmods) ) { + message.str(""); message << "module " << module << ": outside range {0:" << nmods << "}"; + this->camera.log_error( function, message.str() ); + return ERROR; + } - // Locate this line in the configuration so that it can be written to the Archon - // - std::string key = biasconfig.str(); - std::string value = std::to_string(voltage); - bool changed = false; - long error; + // Use the module type to get LV or HV Bias + // and start building the bias configuration string. + // + switch ( this->modtype[ module-1 ] ) { + case 0: + message.str(""); message << "module " << module << " not installed"; + this->camera.log_error( function, message.str() ); + return ERROR; + break; + case 3: // LVBias + case 9: // LVXBias + biasconfig << "MOD" << module << "/LV"; + vmin = -14.0; + vmax = +14.0; + break; + case 4: // HVBias + case 8: // HVXBias + biasconfig << "MOD" << module << "/HV"; + vmin = 0.0; + vmax = +31.0; + break; + default: + message.str(""); message << "module " << module << " not a bias board"; + this->camera.log_error( function, message.str() ); + return ERROR; + break; + } - // If no voltage suppled (readonly) then just read the configuration and exit - // - if (readonly) { - message.str(""); - error = this->get_configmap_value(key, voltage); - if (error != NO_ERROR) { - message << "reading bias " << key; - } else { - retstring = std::to_string(voltage); - message << "read bias " << key << "=" << voltage; - } - logwrite(function, message.str()); - return error; - } + // Check that the channel number is valid + // and add it to the bias configuration string. + // + if ( (channel < 1) || (channel > 30) ) { + message.str(""); message << "bias channel " << module << ": outside range {1:30}"; + this->camera.log_error( function, message.str() ); + return ERROR; + } + if ( (channel > 0) && (channel < 25) ) { + biasconfig << "LC_V" << channel; + } + if ( (channel > 24) && (channel < 31) ) { + channel -= 24; + biasconfig << "HC_V" << channel; + } - // Write the config line to update the bias voltage - // - error = this->write_config_key(key.c_str(), value.c_str(), changed); + if ( (voltage < vmin) || (voltage > vmax) ) { + message.str(""); message << "bias voltage " << voltage << ": outside range {" << vmin << ":" << vmax << "}"; + this->camera.log_error( function, message.str() ); + return ERROR; + } - // Now send the APPLYMODx command - // - std::stringstream applystr; - applystr << "APPLYMOD" - << std::setfill('0') - << std::setw(2) - << std::hex - << (module - 1); + // Locate this line in the configuration so that it can be written to the Archon + // + std::string key = biasconfig.str(); + std::string value = std::to_string(voltage); + bool changed = false; + long error; - if (error == NO_ERROR) error = this->archon_cmd(applystr.str()); + // If no voltage suppled (readonly) then just read the configuration and exit + // + if (readonly) { + message.str(""); + error = this->get_configmap_value(key, voltage); + if (error != NO_ERROR) { + message << "reading bias " << key; + + } else { + retstring = std::to_string(voltage); + message << "read bias " << key << "=" << voltage; + } + logwrite( function, message.str() ); + return error; + } - if (error != NO_ERROR) { - message << "writing bias configuration: " << key << "=" << value; - } else if (!changed) { - message << "bias configuration: " << key << "=" << value << " unchanged"; - } else { - message << "updated bias configuration: " << key << "=" << value; - } + // Write the config line to update the bias voltage + // + error = this->write_config_key(key.c_str(), value.c_str(), changed); - logwrite(function, message.str()); + // Now send the APPLYMODx command + // + std::stringstream applystr; + applystr << "APPLYMOD" + << std::setfill('0') + << std::setw(2) + << std::hex + << (module-1); - return error; + if (error == NO_ERROR) error = this->archon_cmd(applystr.str()); + + if (error != NO_ERROR) { + message << "writing bias configuration: " << key << "=" << value; + + } else if (!changed) { + message << "bias configuration: " << key << "=" << value <<" unchanged"; + + } else { + message << "updated bias configuration: " << key << "=" << value; } - /**************** Archon::Interface::bias ***********************************/ + logwrite(function, message.str()); + return error; + } + /**************** Archon::Interface::bias ***********************************/ + + + /**************** Archon::Interface::cds ************************************/ + /** + * @fn cds + * @brief set / get CDS parameters + * @param + * @return ERROR or NO_ERROR + * + */ + long Interface::cds(std::string args, std::string &retstring) { + std::string function = "Archon::Interface::cds"; + std::stringstream message; + std::vector tokens; + std::string key, value; + bool changed; + long error = ERROR; + + if ( args.empty() ) { + this->camera.log_error( function, "no argument: expected cds [ value ]" ); + return ERROR; + } - /**************** Archon::Interface::cds ************************************/ - /** - * @fn cds - * @brief set / get CDS parameters - * @param - * @return ERROR or NO_ERROR - * - */ - long Interface::cds(std::string args, std::string &retstring) { - std::string function = "Archon::Interface::cds"; - std::stringstream message; - std::vector tokens; - std::string key, value; - bool changed; - long error = ERROR; + try { + Tokenize(args, tokens, " "); - if (args.empty()) { - this->camera.log_error(function, "no argument: expected cds [ value ]"); - return ERROR; - } + // One token -- + // get the configuration key value + // + if ( tokens.size() == 1 ) { + key = tokens.at(0); + std::transform( key.begin(), key.end(), key.begin(), ::toupper ); // make uppercase + error = this->get_configmap_value(key, retstring); // read + } + else + + // Two tokens -- + // set the configuration key to value, send APPLYCDS, then read back the config key + // + if ( tokens.size() == 2 ) { + key = tokens.at(0); + std::transform( key.begin(), key.end(), key.begin(), ::toupper ); // make uppercase + value = tokens.at(1); + error = this->write_config_key( key.c_str(), value.c_str(), changed ); // set + if (error == NO_ERROR) error = this->archon_cmd(APPLYCDS); // apply + if (error == NO_ERROR) error = this->get_configmap_value(key, retstring); // read back + + } else { + // More than two tokens is an error + this->camera.log_error( function, "Too many arguments. Expected cds [ value ]" ); + return ERROR; + } - try { - Tokenize(args, tokens, " "); + } catch(std::out_of_range &) { + message << "parsing cds arguments: " << args << ": Expected cds [ value ]"; + this->camera.log_error( function, message.str() ); + return ERROR; - // One token -- - // get the configuration key value - // - if (tokens.size() == 1) { - key = tokens.at(0); - std::transform(key.begin(), key.end(), key.begin(), ::toupper); // make uppercase - error = this->get_configmap_value(key, retstring); // read - } else - - // Two tokens -- - // set the configuration key to value, send APPLYCDS, then read back the config key - // - if (tokens.size() == 2) { - key = tokens.at(0); - std::transform(key.begin(), key.end(), key.begin(), ::toupper); // make uppercase - value = tokens.at(1); - error = this->write_config_key(key.c_str(), value.c_str(), changed); // set - if (error == NO_ERROR) error = this->archon_cmd(APPLYCDS); // apply - if (error == NO_ERROR) error = this->get_configmap_value(key, retstring); // read back - } else { - // More than two tokens is an error - this->camera.log_error(function, "Too many arguments. Expected cds [ value ]"); - return ERROR; - } - } catch (std::out_of_range &) { - message << "parsing cds arguments: " << args << ": Expected cds [ value ]"; - this->camera.log_error(function, message.str()); - return ERROR; - } catch (...) { - message << "unknown exception parsing cds arguments: " << args << ": Expected cds [ value ]"; - this->camera.log_error(function, message.str()); - return ERROR; - } + } catch(...) { + message << "unknown exception parsing cds arguments: " << args << ": Expected cds [ value ]"; + this->camera.log_error( function, message.str() ); + return ERROR; + } - return error; + return error; + } + /**************** Archon::Interface::cds ************************************/ + + + /**************** Archon::Interface::inreg **********************************/ + /** + * @fn inreg + * @brief write to a VCPU INREGi + * @param args contains: module inreg value + * @return ERROR or NO_ERROR + * + */ + long Interface::inreg( std::string args ) { + std::string function = "Archon::Interface::inreg"; + std::stringstream message; + std::vector tokens; + int module, reg, value; + + // must have loaded firmware // TODO implement a command to read the configuration + // // memory from Archon, in order to remove this restriction. + // + if ( ! this->firmwareloaded ) { + this->camera.log_error( function, "firmware not loaded" ); + return ERROR; } - /**************** Archon::Interface::cds ************************************/ + // VCPU requires a minimum backplane version + // + int ret = compare_versions( this->backplaneversion, REV_VCPU ); + if ( ret < 0 ) { + if ( ret == -999 ) { + message << "comparing backplane version " << this->backplaneversion << " to " << REV_VCPU; + + } else { + message << "requires backplane version " << REV_VCPU << " or newer. (" + << this->backplaneversion << " detected)"; + } + this->camera.log_error( function, message.str() ); + return ERROR; + } + Tokenize(args, tokens, " "); - /**************** Archon::Interface::inreg **********************************/ - /** - * @fn inreg - * @brief write to a VCPU INREGi - * @param args contains: module inreg value - * @return ERROR or NO_ERROR - * - */ - long Interface::inreg(std::string args) { - std::string function = "Archon::Interface::inreg"; - std::stringstream message; - std::vector tokens; - int module, reg, value; + // There must be three tokens, + // + if ( tokens.size() != 3 ) { + this->camera.log_error( function, "expected three arguments: " ); + return ERROR; + } + + // Now get the module and + // which will be common to everything. + // + try { + module = std::stoi( tokens[0] ); + reg = std::stoi( tokens[1] ); + value = std::stoi( tokens[2] ); + } + catch (std::invalid_argument &) { + message.str(""); message << "unable to convert one of \"" << args << "\" to integer"; + this->camera.log_error( function, message.str() ); + return ERROR; + } + catch (std::out_of_range &) { + message.str(""); message << "one of \"" << args << "\" is outside integer range"; + this->camera.log_error( function, message.str() ); + return ERROR; + } + + // check that requested module is valid + // + switch ( this->modtype[ module-1 ] ) { + case 0: + message.str(""); message << "requested module " << module << " not installed"; + this->camera.log_error( function, message.str() ); + return ERROR; + case 3: // LVBias + case 5: // Heater + case 7: // HS + case 9: // LVXBias + case 10: // LVDS + case 11: // HeaterX + break; + default: + message.str(""); message << "requested module " << module << " does not contain a VCPU"; + this->camera.log_error( function, message.str() ); + return ERROR; + } + + // check that register number is valid + // + if ( reg < 0 || reg > 15 ) { + message.str(""); message << "requested register " << reg << " outside range {0:15}"; + this->camera.log_error( function, message.str() ); + return ERROR; + } + + // check that value is within range + // + if ( value < 0 || value > 65535 ) { + message.str(""); message << "requested value " << value << " outside range {0:65535}"; + this->camera.log_error( function, message.str() ); + return ERROR; + } + + std::stringstream inreg_key; + bool changed = false; + inreg_key << "MOD" << module << "/VCPU_INREG" << reg; + long error = this->write_config_key( inreg_key.str().c_str(), value, changed ); + if ( error != NO_ERROR ) { + message.str(""); message << "configuration " << inreg_key.str() << "=" << value; + logwrite( function, message.str() ); + return ERROR; + + } else { + std::stringstream applystr; + applystr << "APPLYDIO" + << std::setfill('0') + << std::setw(2) + << std::hex + << (module-1); + error = this->archon_cmd( applystr.str() ); + return error; + } + } + /**************** Archon::Interface::inreg **********************************/ + + + /**************** Archon::Interface::test ***********************************/ + /** + * @fn test + * @brief test routines + * @param string args contains test name and arguments + * @param reference to retstring for any return values + * @return ERROR or NO_ERROR + * + * This is the place to put various debugging and system testing tools. + * It is placed here, rather than in camera, to allow for controller- + * specific tests. This means some common tests may need to be duplicated + * for each controller. + * + * These are kept out of server.cpp so that I don't need a separate, + * potentially conflicting command for each test I come up with, and also + * reduces the chance of someone accidentally, inadvertently entering one + * of these test commands. + * + * The server command is "test", the next parameter is the test name, + * and any parameters needed for the particular test are extracted as + * tokens from the args string passed in. + * + * The input args string is tokenized and tests are separated by a simple + * series of if..else.. conditionals. + * + * current tests are: + * ampinfo - print what is known about the amplifiers + * busy - Override the archon_busy flag + * fitsname - show what the fitsname will look like + * builddate - log the build date + * async - queue an asynchronous status message + * modules - Log all installed modules and their types + * parammap - Log all parammap entries found in the ACF file + * configmap - Log all configmap entries found in the ACF file + * bw - tests the exposure sequence bandwidth by running a sequence of exposures + * timer - test Archon time against system time + */ + long Interface::test(std::string args, std::string &retstring) { + std::string function = "Archon::Interface::test"; + std::stringstream message; + std::vector tokens; + long error; + + Tokenize(args, tokens, " "); + + if (tokens.empty()) { + this->camera.log_error( function, "no test name provided" ); + return ERROR; + } + + std::string testname; + /* the first token is the test name */ + try { + testname = tokens.at(0); + + } catch ( std::out_of_range & ) { + this->camera.log_error( function, "testname token out of range" ); + return ERROR; + } + + // ---------------------------------------------------- + // ampinfo + // ---------------------------------------------------- + // print what is known about the amplifiers + // + if (testname == "ampinfo") { + + std::string mode = this->camera_info.current_observing_mode; + int framemode = this->modemap[mode].geometry.framemode; + + message.str(""); message << "[ampinfo] observing mode=" << mode; + logwrite( function, message.str() ); + message.str(""); message << "[ampinfo] FRAMEMODE=" << this->modemap[mode].geometry.framemode; + logwrite( function, message.str() ); + message.str(""); message << "[ampinfo] LINECOUNT=" << this->modemap[mode].geometry.linecount << " PIXELCOUNT=" << this->modemap[mode].geometry.pixelcount; + logwrite( function, message.str() ); + message.str(""); message << "[ampinfo] num_taps=" << this->modemap[mode].tapinfo.num_taps; + logwrite( function, message.str() ); + message.str(""); message << "[ampinfo] hori_amps=" << this->modemap[mode].geometry.amps[0] << " vert_amps=" << this->modemap[mode].geometry.amps[1]; + logwrite( function, message.str() ); + + message.str(""); message << "[ampinfo] gains ="; + for ( auto gn : this->gain ) { + message << " " << gn; + } + logwrite( function, message.str() ); + + int rows = this->modemap[mode].geometry.linecount; + int cols = this->modemap[mode].geometry.pixelcount; - // must have loaded firmware // TODO implement a command to read the configuration - // // memory from Archon, in order to remove this restriction. - // - if (!this->firmwareloaded) { - this->camera.log_error(function, "firmware not loaded"); - return ERROR; - } + int hamps = this->modemap[mode].geometry.amps[0]; + int vamps = this->modemap[mode].geometry.amps[1]; - // VCPU requires a minimum backplane version - // - int ret = compare_versions(this->backplaneversion, REV_VCPU); - if (ret < 0) { - if (ret == -999) { - message << "comparing backplane version " << this->backplaneversion << " to " << REV_VCPU; - } else { - message << "requires backplane version " << REV_VCPU << " or newer. (" - << this->backplaneversion << " detected)"; - } - this->camera.log_error(function, message.str()); - return ERROR; - } + int x0=-1, x1, y0, y1; - Tokenize(args, tokens, " "); + for ( int y=0; y - // - if (tokens.size() != 3) { - this->camera.log_error(function, "expected three arguments: "); - return ERROR; + } else { + x0++; x1=x0+1; + y0 = 0; y1=1; + } + message.str(""); message << "[ampinfo] x0=" << x0 << " x1=" << x1 << " y0=" << y0 << " y1=" << y1 + << " | amp section (xrange, yrange) " << (x0*cols + 1) << ":" << (x1)*cols << ", " << (y0*rows + 1) << ":" << (y1)*rows; + logwrite( function, message.str() ); } + } + error = NO_ERROR; - // Now get the module and - // which will be common to everything. - // + } else if (testname == "busy") { + // ---------------------------------------------------- + // busy + // ---------------------------------------------------- + // Override the archon_busy flag to test system responsiveness + // when the Archon is busy. + if ( tokens.size() == 1 ) { + message.str(""); message << "archon_busy=" << this->archon_busy; + logwrite( function, message.str() ); + error = NO_ERROR; + + } else if ( tokens.size() == 2 ) { try { - module = std::stoi(tokens[0]); - reg = std::stoi(tokens[1]); - value = std::stoi(tokens[2]); - } catch (std::invalid_argument &) { - message.str(""); - message << "unable to convert one of \"" << args << "\" to integer"; - this->camera.log_error(function, message.str()); - return ERROR; - } - catch (std::out_of_range &) { - message.str(""); - message << "one of \"" << args << "\" is outside integer range"; - this->camera.log_error(function, message.str()); - return ERROR; - } + this->archon_busy = tokens.at(1) == "yes"; - // check that requested module is valid - // - switch (this->modtype[module - 1]) { - case 0: - message.str(""); - message << "requested module " << module << " not installed"; - this->camera.log_error(function, message.str()); - return ERROR; - case 3: // LVBias - case 5: // Heater - case 7: // HS - case 9: // LVXBias - case 10: // LVDS - case 11: // HeaterX - break; - default: - message.str(""); - message << "requested module " << module << " does not contain a VCPU"; - this->camera.log_error(function, message.str()); - return ERROR; + } catch ( std::out_of_range & ) { + this->camera.log_error( function, "tokens out of range" ); + error=ERROR; } + message.str(""); message << "archon_busy=" << this->archon_busy; + logwrite( function, message.str() ); + error = NO_ERROR; - // check that register number is valid - // - if (reg < 0 || reg > 15) { - message.str(""); - message << "requested register " << reg << " outside range {0:15}"; - this->camera.log_error(function, message.str()); - return ERROR; - } + } else { + message.str(""); message << "expected one argument, yes or no"; + this->camera.log_error( function, message.str() ); + error = ERROR; + } + retstring = this->archon_busy ? "true" : "false"; - // check that value is within range - // - if (value < 0 || value > 65535) { - message.str(""); - message << "requested value " << value << " outside range {0:65535}"; - this->camera.log_error(function, message.str()); - return ERROR; + } else if (testname == "fitsname") { + // ---------------------------------------------------- + // fitsname + // ---------------------------------------------------- + // Show what the fitsname will look like. + // This is a "test" rather than a regular command so that it doesn't get mistaken + // for returning a real, usable filename. When using fitsnaming=time, the filename + // has to be generated at the moment the file is opened. + std::string msg; + this->camera.set_fitstime( get_timestamp() ); // must set camera.fitstime first + error = this->camera.get_fitsname(msg); // get the fitsname (by reference) + retstring = msg; + message.str(""); message << "NOTICE:" << msg; + this->camera.async.enqueue( message.str() ); // queue the fitsname + logwrite(function, msg); // log the fitsname + if (error!=NO_ERROR) { + this->camera.log_error( function, "couldn't validate fits filename" ); + } + // end if (testname == fitsname) + + } else if ( testname == "builddate" ) { + // ---------------------------------------------------- + // builddate + // ---------------------------------------------------- + // log the build date + std::string build(BUILD_DATE); build.append(" "); build.append(BUILD_TIME); + retstring = build; + error = NO_ERROR; + logwrite( function, build ); + // end if ( testname == builddate ) + + } else if (testname == "async") { + // ---------------------------------------------------- + // async [message] + // ---------------------------------------------------- + // queue an asynchronous message + // The [message] param is optional. If not provided then "test" is queued. + if (tokens.size() > 1) { + if (tokens.size() > 2) { + logwrite(function, "NOTICE:received multiple strings -- only the first will be queued"); } + try { message.str(""); message << "NOTICE:" << tokens.at(1); + logwrite( function, message.str() ); + this->camera.async.enqueue( message.str() ); - std::stringstream inreg_key; - bool changed = false; - inreg_key << "MOD" << module << "/VCPU_INREG" << reg; - long error = this->write_config_key(inreg_key.str().c_str(), value, changed); - if (error != NO_ERROR) { - message.str(""); - message << "configuration " << inreg_key.str() << "=" << value; - logwrite(function, message.str()); - return ERROR; - } else { - std::stringstream applystr; - applystr << "APPLYDIO" - << std::setfill('0') - << std::setw(2) - << std::hex - << (module - 1); - error = this->archon_cmd(applystr.str()); - return error; + } catch ( std::out_of_range & ) { + this->camera.log_error( function, "tokens out of range" ); + error=ERROR; } - } - /**************** Archon::Interface::inreg **********************************/ + } else { // if no string passed then queue a simple test message + logwrite(function, "NOTICE:test"); + this->camera.async.enqueue("NOTICE:test"); + } + error = NO_ERROR; + // end if (testname == async) + } else if (testname == "modules") { + // ---------------------------------------------------- + // modules + // ---------------------------------------------------- + // Log all installed modules + logwrite( function, "installed module types: " ); + message.str(""); + for ( const auto &mod : this->modtype ) { + message << mod << " "; + } + logwrite( function, message.str() ); + retstring = message.str(); + error = NO_ERROR; - /**************** Archon::Interface::test ***********************************/ - /** - * @fn test - * @brief test routines - * @param string args contains test name and arguments - * @param reference to retstring for any return values - * @return ERROR or NO_ERROR - * - * This is the place to put various debugging and system testing tools. - * It is placed here, rather than in camera, to allow for controller- - * specific tests. This means some common tests may need to be duplicated - * for each controller. - * - * These are kept out of server.cpp so that I don't need a separate, - * potentially conflicting command for each test I come up with, and also - * reduces the chance of someone accidentally, inadvertently entering one - * of these test commands. - * - * The server command is "test", the next parameter is the test name, - * and any parameters needed for the particular test are extracted as - * tokens from the args string passed in. - * - * The input args string is tokenized and tests are separated by a simple - * series of if..else.. conditionals. - * - * current tests are: - * ampinfo - print what is known about the amplifiers - * busy - Override the archon_busy flag - * fitsname - show what the fitsname will look like - * builddate - log the build date - * async - queue an asynchronous status message - * modules - Log all installed modules and their types - * parammap - Log all parammap entries found in the ACF file - * configmap - Log all configmap entries found in the ACF file - * bw - tests the exposure sequence bandwidth by running a sequence of exposures - * timer - test Archon time against system time - */ - long Interface::test(std::string args, std::string &retstring) { - std::string function = "Archon::Interface::test"; - std::stringstream message; - std::vector tokens; - long error; + } else if (testname == "parammap") { + // ---------------------------------------------------- + // parammap + // ---------------------------------------------------- + // Log all parammap entries found in the ACF file + + // loop through the modes + // + logwrite(function, "parammap entries by mode section:"); + for (auto & mode_it : this->modemap) { + std::string mode = mode_it.first; + message.str(""); message << "found mode section " << mode; + logwrite(function, message.str()); + for (auto param_it = this->modemap[mode].parammap.begin(); param_it != this->modemap[mode].parammap.end(); ++param_it) { + message.str(""); message << "MODE_" << mode << ": " << param_it->first << "=" << param_it->second.value; + logwrite(function, message.str()); + } + } - Tokenize(args, tokens, " "); + logwrite(function, "ALL parammap entries in ACF:"); + int keycount=0; + for (auto & param_it : this->parammap) { + keycount++; + message.str(""); message << param_it.first << "=" << param_it.second.value; + logwrite(function, message.str()); + this->camera.async.enqueue( "NOTICE:"+message.str() ); + } + message.str(""); message << "found " << keycount << " parammap entries"; + logwrite(function, message.str()); + error = NO_ERROR; + // end if (testname == parammap) - if (tokens.empty()) { - this->camera.log_error(function, "no test name provided"); - return ERROR; + } else if (testname == "configmap") { + // ---------------------------------------------------- + // configmap + // ---------------------------------------------------- + // Log all configmap entries found in the ACF file + error = NO_ERROR; + logwrite(function, "configmap entries by mode section:"); + for (auto & mode_it : this->modemap) { + std::string mode = mode_it.first; + message.str(""); message << "found mode section " << mode; + logwrite(function, message.str()); + for (auto config_it = this->modemap[mode].configmap.begin(); config_it != this->modemap[mode].configmap.end(); ++config_it) { + message.str(""); message << "MODE_" << mode << ": " << config_it->first << "=" << config_it->second.value; + logwrite(function, message.str()); } + } - std::string testname; - /* the first token is the test name */ + // if a second argument was passed then this is a config key + // try to read it + // + if ( tokens.size() == 2 ) { try { - testname = tokens.at(0); - } catch (std::out_of_range &) { - this->camera.log_error(function, "testname token out of range"); - return ERROR; + std::string configkey = tokens.at(1); + error = this->get_configmap_value(configkey, retstring); + + } catch ( std::out_of_range & ) { + this->camera.log_error( function, "configkey token out of range" ); + error=ERROR; + } + } + + // if a third argument was passed then set this configkey + // + if ( tokens.size() == 3 ) { + try { std::string key = tokens.at(1); + std::string value = tokens.at(2); + bool configchanged; + error = this->write_config_key( key.c_str(), value.c_str(), configchanged ); + if (error == NO_ERROR) error = this->archon_cmd(APPLYCDS); + + } catch ( std::out_of_range & ) { + this->camera.log_error( function, "key,value tokens out of range" ); + error=ERROR; } + } + int keycount=0; + for (auto config_it = this->configmap.begin(); config_it != this->configmap.end(); ++config_it) { + keycount++; + } + message.str(""); message << "found " << keycount << " configmap entries"; + logwrite(function, message.str()); + // end if (testname == configmap) + + } else if (testname == "bw") { // ---------------------------------------------------- - // ampinfo + // bw // ---------------------------------------------------- - // print what is known about the amplifiers - // - if (testname == "ampinfo") { - std::string mode = this->camera_info.current_observing_mode; - int framemode = this->modemap[mode].geometry.framemode; + // Bandwidth test + // This tests the exposure sequence bandwidth by running a sequence + // of exposures, including reading the frame buffer -- everything except + // for the fits file writing. + + if ( ! this->modeselected ) { + this->camera.log_error( function, "no mode selected" ); + return ERROR; + } - message.str(""); - message << "[ampinfo] observing mode=" << mode; - logwrite(function, message.str()); - message.str(""); - message << "[ampinfo] FRAMEMODE=" << this->modemap[mode].geometry.framemode; - logwrite(function, message.str()); - message.str(""); - message << "[ampinfo] LINECOUNT=" << this->modemap[mode].geometry.linecount << " PIXELCOUNT=" << this-> - modemap[mode].geometry.pixelcount; - logwrite(function, message.str()); - message.str(""); - message << "[ampinfo] num_taps=" << this->modemap[mode].tapinfo.num_taps; - logwrite(function, message.str()); - message.str(""); - message << "[ampinfo] hori_amps=" << this->modemap[mode].geometry.amps[0] << " vert_amps=" << this->modemap[ - mode].geometry.amps[1]; - logwrite(function, message.str()); + std::string nseqstr; + int nseq; + bool ro=false; // read only + bool rw=false; // read and write - message.str(""); - message << "[ampinfo] gains ="; - for (auto gn: this->gain) { - message << " " << gn; - } - logwrite(function, message.str()); + if (tokens.size() > 1) { + try { + nseqstr = tokens.at(1); - int rows = this->modemap[mode].geometry.linecount; - int cols = this->modemap[mode].geometry.pixelcount; + } catch ( std::out_of_range & ) { + this->camera.log_error( function, "nseqstr token out of range" ); + return ERROR; + } - int hamps = this->modemap[mode].geometry.amps[0]; - int vamps = this->modemap[mode].geometry.amps[1]; + } else { + this->camera.log_error( function, "usage: test bw [ rw | ro ]"); + return ERROR; + } - int x0 = -1, x1, y0, y1; + if (tokens.size() > 2) { + try { if (tokens.at(2) == "rw") rw=true; else rw=false; + if (tokens.at(2) == "ro") ro=true; else ro=false; - for (int y = 0; y < vamps; y++) { - for (int x = 0; x < hamps; x++) { - if (framemode == 2) { - x0 = x; - x1 = x + 1; - y0 = y; - y1 = y + 1; - } else { - x0++; - x1 = x0 + 1; - y0 = 0; - y1 = 1; - } - message.str(""); - message << "[ampinfo] x0=" << x0 << " x1=" << x1 << " y0=" << y0 << " y1=" << y1 - << " | amp section (xrange, yrange) " << (x0 * cols + 1) << ":" << (x1) * cols << ", " << ( - y0 * rows + 1) << ":" << (y1) * rows; - logwrite(function, message.str()); - } - } - error = NO_ERROR; - } else if (testname == "busy") { - // ---------------------------------------------------- - // busy - // ---------------------------------------------------- - // Override the archon_busy flag to test system responsiveness - // when the Archon is busy. - if (tokens.size() == 1) { - message.str(""); - message << "archon_busy=" << this->archon_busy; - logwrite(function, message.str()); - error = NO_ERROR; - } else if (tokens.size() == 2) { - try { - this->archon_busy = tokens.at(1) == "yes"; - } catch (std::out_of_range &) { - this->camera.log_error(function, "tokens out of range"); - error = ERROR; - } - message.str(""); - message << "archon_busy=" << this->archon_busy; - logwrite(function, message.str()); - error = NO_ERROR; - } else { - message.str(""); - message << "expected one argument, yes or no"; - this->camera.log_error(function, message.str()); - error = ERROR; - } - retstring = this->archon_busy ? "true" : "false"; - } else if (testname == "fitsname") { - // ---------------------------------------------------- - // fitsname - // ---------------------------------------------------- - // Show what the fitsname will look like. - // This is a "test" rather than a regular command so that it doesn't get mistaken - // for returning a real, usable filename. When using fitsnaming=time, the filename - // has to be generated at the moment the file is opened. - std::string msg; - this->camera.set_fitstime(get_timestamp()); // must set camera.fitstime first - error = this->camera.get_fitsname(msg); // get the fitsname (by reference) - retstring = msg; - message.str(""); - message << "NOTICE:" << msg; - this->camera.async.enqueue(message.str()); // queue the fitsname - logwrite(function, msg); // log the fitsname - if (error != NO_ERROR) { - this->camera.log_error(function, "couldn't validate fits filename"); - } - // end if (testname == fitsname) - } else if (testname == "builddate") { - // ---------------------------------------------------- - // builddate - // ---------------------------------------------------- - // log the build date - std::string build(BUILD_DATE); - build.append(" "); - build.append(BUILD_TIME); - retstring = build; - error = NO_ERROR; - logwrite(function, build); - // end if ( testname == builddate ) - } else if (testname == "async") { - // ---------------------------------------------------- - // async [message] - // ---------------------------------------------------- - // queue an asynchronous message - // The [message] param is optional. If not provided then "test" is queued. - if (tokens.size() > 1) { - if (tokens.size() > 2) { - logwrite(function, "NOTICE:received multiple strings -- only the first will be queued"); - } - try { - message.str(""); - message << "NOTICE:" << tokens.at(1); - logwrite(function, message.str()); - this->camera.async.enqueue(message.str()); - } catch (std::out_of_range &) { - this->camera.log_error(function, "tokens out of range"); - error = ERROR; - } - } else { - // if no string passed then queue a simple test message - logwrite(function, "NOTICE:test"); - this->camera.async.enqueue("NOTICE:test"); - } - error = NO_ERROR; - // end if (testname == async) - } else if (testname == "modules") { - // ---------------------------------------------------- - // modules - // ---------------------------------------------------- - // Log all installed modules - logwrite(function, "installed module types: "); - message.str(""); - for (const auto &mod: this->modtype) { - message << mod << " "; - } - logwrite(function, message.str()); - retstring = message.str(); - error = NO_ERROR; - } else if (testname == "parammap") { - // ---------------------------------------------------- - // parammap - // ---------------------------------------------------- - // Log all parammap entries found in the ACF file - - // loop through the modes - // - logwrite(function, "parammap entries by mode section:"); - for (auto &mode_it: this->modemap) { - std::string mode = mode_it.first; - message.str(""); - message << "found mode section " << mode; - logwrite(function, message.str()); - for (auto param_it = this->modemap[mode].parammap.begin(); - param_it != this->modemap[mode].parammap.end(); ++param_it) { - message.str(""); - message << "MODE_" << mode << ": " << param_it->first << "=" << param_it->second.value; - logwrite(function, message.str()); - } - } + } catch ( std::out_of_range & ) { + this->camera.log_error( function, "rw tokens out of range" ); + error=ERROR; + } + } - logwrite(function, "ALL parammap entries in ACF:"); - int keycount = 0; - for (auto ¶m_it: this->parammap) { - keycount++; - message.str(""); - message << param_it.first << "=" << param_it.second.value; - logwrite(function, message.str()); - this->camera.async.enqueue("NOTICE:" + message.str()); - } - message.str(""); - message << "found " << keycount << " parammap entries"; - logwrite(function, message.str()); - error = NO_ERROR; - // end if (testname == parammap) - } else if (testname == "configmap") { - // ---------------------------------------------------- - // configmap - // ---------------------------------------------------- - // Log all configmap entries found in the ACF file - error = NO_ERROR; - logwrite(function, "configmap entries by mode section:"); - for (auto &mode_it: this->modemap) { - std::string mode = mode_it.first; - message.str(""); - message << "found mode section " << mode; - logwrite(function, message.str()); - for (auto config_it = this->modemap[mode].configmap.begin(); - config_it != this->modemap[mode].configmap.end(); ++config_it) { - message.str(""); - message << "MODE_" << mode << ": " << config_it->first << "=" << config_it->second.value; - logwrite(function, message.str()); - } - } + try { + nseq = std::stoi( nseqstr ); // test that nseqstr is an integer before trying to use it - // if a second argument was passed then this is a config key - // try to read it - // - if (tokens.size() == 2) { - try { - std::string configkey = tokens.at(1); - error = this->get_configmap_value(configkey, retstring); - } catch (std::out_of_range &) { - this->camera.log_error(function, "configkey token out of range"); - error = ERROR; - } - } + } catch (std::invalid_argument &) { + message.str(""); message << "unable to convert sequences: " << nseqstr << " to integer"; + this->camera.log_error( function, message.str() ); + return ERROR; - // if a third argument was passed then set this configkey - // - if (tokens.size() == 3) { - try { - std::string key = tokens.at(1); - std::string value = tokens.at(2); - bool configchanged; - error = this->write_config_key(key.c_str(), value.c_str(), configchanged); - if (error == NO_ERROR) error = this->archon_cmd(APPLYCDS); - } catch (std::out_of_range &) { - this->camera.log_error(function, "key,value tokens out of range"); - error = ERROR; - } - } + } catch (std::out_of_range &) { + message.str(""); message << "sequences " << nseqstr << " outside integer range"; + this->camera.log_error( function, message.str() ); + return ERROR; + } - int keycount = 0; - for (auto config_it = this->configmap.begin(); config_it != this->configmap.end(); ++config_it) { - keycount++; - } - message.str(""); - message << "found " << keycount << " configmap entries"; - logwrite(function, message.str()); - // end if (testname == configmap) - } else if (testname == "bw") { - // ---------------------------------------------------- - // bw - // ---------------------------------------------------- - // Bandwidth test - // This tests the exposure sequence bandwidth by running a sequence - // of exposures, including reading the frame buffer -- everything except - // for the fits file writing. - - if (!this->modeselected) { - this->camera.log_error(function, "no mode selected"); - return ERROR; - } + // exposeparam is set by the configuration file + // check to make sure it was set, or else expose won't work + // + if (this->exposeparam.empty()) { + message.str(""); message << "EXPOSE_PARAM not defined in configuration file " << this->config.filename; + this->camera.log_error( function, message.str() ); + return ERROR; + } + error = this->get_frame_status(); // TODO is this needed here? - std::string nseqstr; - int nseq; - bool ro = false; // read only - bool rw = false; // read and write + if (error != NO_ERROR) { + logwrite( function, "ERROR: unable to get frame status" ); + return ERROR; + } + this->lastframe = this->frame.bufframen[this->frame.index]; // save the last frame number acquired (wait_for_readout will need this) + + // initiate the exposure here + // + error = this->prep_parameter(this->exposeparam, nseqstr); + if (error == NO_ERROR) error = this->load_parameter(this->exposeparam, nseqstr); + + // get system time and Archon's timer after exposure starts + // start_timer is used to determine when the exposure has ended, in wait_for_exposure() + // + if (error == NO_ERROR) { + this->camera_info.start_time = get_timestamp(); // current system time formatted as YYYY-MM-DDTHH:MM:SS.sss + error = this->get_timer(&this->start_timer); // Archon internal timer (one tick=10 nsec) + if ( error != NO_ERROR ) { + logwrite( function, "ERROR: couldn't get start time" ); + return error; + } + this->camera.set_fitstime(this->camera_info.start_time); // sets camera.fitstime (YYYYMMDDHHMMSS) used for filename + // If read-write selected then need to do some FITS stuff + // + if ( rw ) { + this->camera_info.extension = 0; // always initialize extension + error = this->camera.get_fitsname( this->camera_info.fits_name ); // assemble the FITS filename if rw selected + if ( error != NO_ERROR ) { + logwrite( function, "ERROR: couldn't validate fits filename" ); + return error; + } + this->add_filename_key(); // add filename to system keys database + Common::FitsKeys::fits_key_t::iterator keyit; // add keys from the ACF file + for (keyit = this->modemap[this->camera_info.current_observing_mode].acfkeys.keydb.begin(); + keyit != this->modemap[this->camera_info.current_observing_mode].acfkeys.keydb.end(); + keyit++) { + this->camera_info.userkeys.keydb[keyit->second.keyword].keyword = keyit->second.keyword; + this->camera_info.userkeys.keydb[keyit->second.keyword].keytype = keyit->second.keytype; + this->camera_info.userkeys.keydb[keyit->second.keyword].keyvalue = keyit->second.keyvalue; + this->camera_info.userkeys.keydb[keyit->second.keyword].keycomment = keyit->second.keycomment; + } - if (tokens.size() > 1) { - try { - nseqstr = tokens.at(1); - } catch (std::out_of_range &) { - this->camera.log_error(function, "nseqstr token out of range"); - return ERROR; - } - } else { - this->camera.log_error(function, "usage: test bw [ rw | ro ]"); - return ERROR; - } + this->camera_info.iscube = this->camera.datacube(); - if (tokens.size() > 2) { - try { - if (tokens.at(2) == "rw") rw = true; - else rw = false; - if (tokens.at(2) == "ro") ro = true; - else ro = false; - } catch (std::out_of_range &) { - this->camera.log_error(function, "rw tokens out of range"); - error = ERROR; - } + // open the file now for datacubes + // + if ( this->camera.datacube() ) { + error = this->fits_file.open_file( + this->camera.writekeys_when == "before", this->camera_info ); + if ( error != NO_ERROR ) { + this->camera.log_error( function, "couldn't open fits file" ); + return error; } + } + } + } - try { - nseq = std::stoi(nseqstr); // test that nseqstr is an integer before trying to use it - } catch (std::invalid_argument &) { - message.str(""); - message << "unable to convert sequences: " << nseqstr << " to integer"; - this->camera.log_error(function, message.str()); - return ERROR; - } catch (std::out_of_range &) { - message.str(""); - message << "sequences " << nseqstr << " outside integer range"; - this->camera.log_error(function, message.str()); - return ERROR; - } + if (error == NO_ERROR) logwrite(function, "exposure started"); - // exposeparam is set by the configuration file - // check to make sure it was set, or else expose won't work - // - if (this->exposeparam.empty()) { - message.str(""); - message << "EXPOSE_PARAM not defined in configuration file " << this->config.filename; - this->camera.log_error(function, message.str()); - return ERROR; - } - error = this->get_frame_status(); // TODO is this needed here? + long frames_read = 0; - if (error != NO_ERROR) { - logwrite(function, "ERROR: unable to get frame status"); - return ERROR; - } - this->lastframe = this->frame.bufframen[this->frame.index]; - // save the last frame number acquired (wait_for_readout will need this) + // Wait for Archon frame buffer to be ready, then read the latest ready frame buffer to the host. + // Loop over all expected frames. + // + while (nseq-- > 0) { - // initiate the exposure here - // - error = this->prep_parameter(this->exposeparam, nseqstr); - if (error == NO_ERROR) error = this->load_parameter(this->exposeparam, nseqstr); + // If read-write selected, + // Open a new FITS file for each frame when not using datacubes + // + if ( rw && !this->camera.datacube() ) { + this->camera_info.start_time = get_timestamp(); // current system time formatted as YYYY-MM-DDTHH:MM:SS.sss + if ( this->get_timer(&this->start_timer) != NO_ERROR ) { // Archon internal timer (one tick=10 nsec) + logwrite( function, "ERROR: couldn't get start time" ); + return error; + } + this->camera.set_fitstime(this->camera_info.start_time); // sets camera.fitstime (YYYYMMDDHHMMSS) used for filename + error=this->camera.get_fitsname(this->camera_info.fits_name); // Assemble the FITS filename + if ( error != NO_ERROR ) { + logwrite( function, "ERROR: couldn't validate fits filename" ); + return error; + } + this->add_filename_key(); // add filename to system keys database - // get system time and Archon's timer after exposure starts - // start_timer is used to determine when the exposure has ended, in wait_for_exposure() - // - if (error == NO_ERROR) { - this->camera_info.start_time = get_timestamp(); - // current system time formatted as YYYY-MM-DDTHH:MM:SS.sss - error = this->get_timer(&this->start_timer); // Archon internal timer (one tick=10 nsec) - if (error != NO_ERROR) { - logwrite(function, "ERROR: couldn't get start time"); - return error; - } - this->camera.set_fitstime(this->camera_info.start_time); - // sets camera.fitstime (YYYYMMDDHHMMSS) used for filename - // If read-write selected then need to do some FITS stuff - // - if (rw) { - this->camera_info.extension = 0; // always initialize extension - error = this->camera.get_fitsname(this->camera_info.fits_name); - // assemble the FITS filename if rw selected - if (error != NO_ERROR) { - logwrite(function, "ERROR: couldn't validate fits filename"); - return error; - } - this->add_filename_key(); // add filename to system keys database - Common::FitsKeys::fits_key_t::iterator keyit; // add keys from the ACF file - for (keyit = this->modemap[this->camera_info.current_observing_mode].acfkeys.keydb.begin(); - keyit != this->modemap[this->camera_info.current_observing_mode].acfkeys.keydb.end(); - keyit++) { - this->camera_info.userkeys.keydb[keyit->second.keyword].keyword = keyit->second.keyword; - this->camera_info.userkeys.keydb[keyit->second.keyword].keytype = keyit->second.keytype; - this->camera_info.userkeys.keydb[keyit->second.keyword].keyvalue = keyit->second.keyvalue; - this->camera_info.userkeys.keydb[keyit->second.keyword].keycomment = keyit->second.keycomment; - } + error = this->fits_file.open_file( + this->camera.writekeys_when == "before", this->camera_info ); + if ( error != NO_ERROR ) { + this->camera.log_error( function, "couldn't open fits file" ); + return error; + } + } - this->camera_info.iscube = this->camera.datacube(); + if (this->camera_info.exposure_time != 0) { // wait for the exposure delay to complete (if there is one) + error = this->wait_for_exposure(); + if (error==ERROR) { + logwrite( function, "ERROR: exposure delay error" ); + break; - // open the file now for datacubes - // - if (this->camera.datacube()) { - error = this->fits_file.open_file( - this->camera.writekeys_when == "before", this->camera_info); - if (error != NO_ERROR) { - this->camera.log_error(function, "couldn't open fits file"); - return error; - } - } - } - } + } else { + logwrite(function, "exposure delay complete"); + } + } - if (error == NO_ERROR) logwrite(function, "exposure started"); + if (error==NO_ERROR) error = this->wait_for_readout(); // wait for the readout into frame buffer, + if (error==NO_ERROR && ro) error = this->read_frame(Camera::FRAME_IMAGE); // read image frame directly with no write + if (error==NO_ERROR && rw) error = this->read_frame(); // read image frame directly with no write + if (error==NO_ERROR && rw && !this->camera.datacube()) { + this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info ); + this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" + } + if (error==NO_ERROR) frames_read++; + } + retstring = std::to_string( frames_read ); + + // for cubes, close the FITS file now that they've all been written + // (or any time there is an error) + // + if ( rw && ( this->camera.datacube() || (error==ERROR) ) ) { + this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info ); + this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" + } - long frames_read = 0; + logwrite( function, (error==ERROR ? "ERROR" : "complete") ); - // Wait for Archon frame buffer to be ready, then read the latest ready frame buffer to the host. - // Loop over all expected frames. - // - while (nseq-- > 0) { - // If read-write selected, - // Open a new FITS file for each frame when not using datacubes - // - if (rw && !this->camera.datacube()) { - this->camera_info.start_time = get_timestamp(); - // current system time formatted as YYYY-MM-DDTHH:MM:SS.sss - if (this->get_timer(&this->start_timer) != NO_ERROR) { - // Archon internal timer (one tick=10 nsec) - logwrite(function, "ERROR: couldn't get start time"); - return error; - } - this->camera.set_fitstime(this->camera_info.start_time); - // sets camera.fitstime (YYYYMMDDHHMMSS) used for filename - error = this->camera.get_fitsname(this->camera_info.fits_name); // Assemble the FITS filename - if (error != NO_ERROR) { - logwrite(function, "ERROR: couldn't validate fits filename"); - return error; - } - this->add_filename_key(); // add filename to system keys database + message.str(""); message << "frames read = " << frames_read; + logwrite(function, message.str()); + // end if (testname==bw) - error = this->fits_file.open_file( - this->camera.writekeys_when == "before", this->camera_info); - if (error != NO_ERROR) { - this->camera.log_error(function, "couldn't open fits file"); - return error; - } - } + } else if (testname == "timer") { + // ---------------------------------------------------- + // timer + // ---------------------------------------------------- + // test Archon time against system time + + int nseq; + int sleepus; + double systime1, systime2; + unsigned long int archontime1, archontime2; + std::vector deltatime; + int delta_archon, delta_system; + + if (tokens.size() < 3) { + this->camera.log_error( function, "expected test timer " ); + return ERROR; + } - if (this->camera_info.exposure_time != 0) { - // wait for the exposure delay to complete (if there is one) - error = this->wait_for_exposure(); - if (error == ERROR) { - logwrite(function, "ERROR: exposure delay error"); - break; - } else { - logwrite(function, "exposure delay complete"); - } - } + try { + nseq = std::stoi( tokens.at(1) ); + sleepus = std::stoi( tokens.at(2) ); - if (error == NO_ERROR) error = this->wait_for_readout(); // wait for the readout into frame buffer, - if (error == NO_ERROR && ro) error = this->read_frame(Camera::FRAME_IMAGE); - // read image frame directly with no write - if (error == NO_ERROR && rw) error = this->read_frame(); // read image frame directly with no write - if (error == NO_ERROR && rw && !this->camera.datacube()) { - this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info); - this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" - } - if (error == NO_ERROR) frames_read++; - } - retstring = std::to_string(frames_read); + } catch (std::invalid_argument &) { + message.str(""); message << "unable to convert one or more args to an integer"; + this->camera.log_error( function, message.str() ); + return ERROR; - // for cubes, close the FITS file now that they've all been written - // (or any time there is an error) - // - if (rw && (this->camera.datacube() || (error == ERROR))) { - this->fits_file.close_file(this->camera.writekeys_when == "after", this->camera_info); - this->camera.increment_imnum(); // increment image_num when fitsnaming == "number" - } + } catch (std::out_of_range &) { + message.str(""); message << "nseq, sleepus tokens outside range"; + this->camera.log_error( function, message.str() ); + return ERROR; + } - logwrite(function, (error == ERROR ? "ERROR" : "complete")); + error = NO_ERROR; - message.str(""); - message << "frames read = " << frames_read; - logwrite(function, message.str()); - // end if (testname==bw) - } else if (testname == "timer") { - // ---------------------------------------------------- - // timer - // ---------------------------------------------------- - // test Archon time against system time - - int nseq; - int sleepus; - double systime1, systime2; - unsigned long int archontime1, archontime2; - std::vector deltatime; - int delta_archon, delta_system; - - if (tokens.size() < 3) { - this->camera.log_error(function, "expected test timer "); - return ERROR; - } + // turn off background polling while doing the timing test + // + if (error == NO_ERROR) error = this->archon_cmd(POLLOFF); - try { - nseq = std::stoi(tokens.at(1)); - sleepus = std::stoi(tokens.at(2)); - } catch (std::invalid_argument &) { - message.str(""); - message << "unable to convert one or more args to an integer"; - this->camera.log_error(function, message.str()); - return ERROR; - } catch (std::out_of_range &) { - message.str(""); - message << "nseq, sleepus tokens outside range"; - this->camera.log_error(function, message.str()); - return ERROR; - } + // send the Archon TIMER command here, nseq times, with sleepus delay between each + // + int nseqsave = nseq; + while ( error==NO_ERROR && nseq-- > 0 ) { + // get Archon timer [ns] and system time [s], twice + // + error = get_timer(&archontime1); + systime1 = get_clock_time(); + error = get_timer(&archontime2); + systime2 = get_clock_time(); - error = NO_ERROR; + // difference between two calls, converted to µsec + // + delta_archon = (int)((archontime2 - archontime1) / 100.); // archon time was in 10 nsec + delta_system = (int)((systime2 - systime1) * 1000000.); // system time was in sec - // turn off background polling while doing the timing test - // - if (error == NO_ERROR) error = this->archon_cmd(POLLOFF); + // enque each line to the async message port + // + message.str(""); message << "TEST_TIMER: " << nseqsave-nseq << ", " << delta_archon << ", " << delta_system; + this->camera.async.enqueue( message.str() ); - // send the Archon TIMER command here, nseq times, with sleepus delay between each - // - int nseqsave = nseq; - while (error == NO_ERROR && nseq-- > 0) { - // get Archon timer [ns] and system time [s], twice - // - error = get_timer(&archontime1); - systime1 = get_clock_time(); - error = get_timer(&archontime2); - systime2 = get_clock_time(); - - // difference between two calls, converted to µsec - // - delta_archon = (int) ((archontime2 - archontime1) / 100.); // archon time was in 10 nsec - delta_system = (int) ((systime2 - systime1) * 1000000.); // system time was in sec - - // enque each line to the async message port - // - message.str(""); - message << "TEST_TIMER: " << nseqsave - nseq << ", " << delta_archon << ", " << delta_system; - this->camera.async.enqueue(message.str()); - - // save the difference between - // - deltatime.push_back(abs(delta_archon - delta_system)); - - usleep(sleepus); - } + // save the difference between + // + deltatime.push_back( abs( delta_archon - delta_system ) ); - // background polling back on - // - if (error == NO_ERROR) error = this->archon_cmd(POLLON); + usleep( sleepus ); + } - // calculate the average and standard deviation of the difference - // between system and archon - // - double sum = std::accumulate(std::begin(deltatime), std::end(deltatime), 0.0); - double m = sum / deltatime.size(); + // background polling back on + // + if (error == NO_ERROR) error = this->archon_cmd(POLLON); - double accum = 0.0; - std::for_each(std::begin(deltatime), std::end(deltatime), [&](const double d) { - accum += (d - m) * (d - m); - }); + // calculate the average and standard deviation of the difference + // between system and archon + // + double sum = std::accumulate(std::begin(deltatime), std::end(deltatime), 0.0); + double m = sum / deltatime.size(); - double stdev = sqrt(accum / (deltatime.size() - 1)); + double accum = 0.0; + std::for_each (std::begin(deltatime), std::end(deltatime), [&](const double d) { + accum += (d - m) * (d - m); + }); - message.str(""); - message << "average delta=" << m << " stddev=" << stdev; - logwrite(function, message.str()); + double stdev = sqrt(accum / (deltatime.size()-1)); - retstring = "delta=" + std::to_string(m) + " stddev=" + std::to_string(stdev); - // end if (testname==timer) - } else { - // ---------------------------------------------------- - // invalid test name - // ---------------------------------------------------- - message.str(""); - message << "unknown test: " << testname; - this->camera.log_error(function, message.str()); - error = ERROR; - } + message.str(""); message << "average delta=" << m << " stddev=" << stdev; + logwrite(function, message.str()); - return error; + retstring = "delta=" + std::to_string( m ) + " stddev=" + std::to_string( stdev ); + // end if (testname==timer) + + } else { + // ---------------------------------------------------- + // invalid test name + // ---------------------------------------------------- + message.str(""); message << "unknown test: " << testname; + this->camera.log_error( function, message.str() ); + error = ERROR; } - /**************** Archon::Interface::test ***********************************/ + return error; + } + /**************** Archon::Interface::test ***********************************/ + } From d53595a1793f9c6dea108c1c157ce1be123a6ede Mon Sep 17 00:00:00 2001 From: Michael Langmayr Date: Tue, 6 Aug 2024 13:48:21 -0700 Subject: [PATCH 4/6] build tests optionally --- .github/workflows/cmake-workflow.yml | 3 ++- .github/workflows/install-deps.sh | 2 +- CMakeLists.txt | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cmake-workflow.yml b/.github/workflows/cmake-workflow.yml index d827414d..0bcec366 100644 --- a/.github/workflows/cmake-workflow.yml +++ b/.github/workflows/cmake-workflow.yml @@ -30,13 +30,14 @@ jobs: cd ${{github.workspace}}/build rm -rf * g++ --version - cmake -DINTERFACE_TYPE=${{env.INTERFACE_TYPE_ARCHON}} -DDETECTOR_TYPE=${{env.DETECTOR_TYPE_HXRG}} .. + cmake -DDETECTOR_TYPE=${{env.DETECTOR_TYPE_HXRG}} .. - name: Build run: | cd ${{github.workspace}}/build make + make run_unit_tests - name: Run Unit Tests run: | diff --git a/.github/workflows/install-deps.sh b/.github/workflows/install-deps.sh index a8b62e1c..07448368 100755 --- a/.github/workflows/install-deps.sh +++ b/.github/workflows/install-deps.sh @@ -2,4 +2,4 @@ sudo apt-get update && sudo apt-get install libccfits-dev && sudo apt-get install libcfitsio-dev && sudo apt-get install libcurl4-openssl-dev && -sudo apt-get install libgtest-dev \ No newline at end of file +sudo apt-get install libgtest-dev diff --git a/CMakeLists.txt b/CMakeLists.txt index 75c9e1a1..5623581b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,4 +51,4 @@ 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}/tests) +add_subdirectory(${PROJECT_BASE_DIR}/tests EXCLUDE_FROM_ALL) From 913febaa3f0b5e1c5bdc456c2cb856c42930c795 Mon Sep 17 00:00:00 2001 From: Michael Langmayr Date: Tue, 6 Aug 2024 16:29:28 -0700 Subject: [PATCH 5/6] Add pthread to unit_test target libraries --- tests/CMakeLists.txt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 39bf9403..64e9c9f4 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -5,10 +5,13 @@ set(PROJECT_TESTS_DIR ${PROJECT_BASE_DIR}/tests) # Find the Google Test library find_package(GTest REQUIRED) -add_executable(run_unit_tests utility_tests.cpp) # List all unit test source files here +add_executable( + run_unit_tests utility_tests.cpp) # List all unit test source files here # Link the Google Test library target_link_libraries(run_unit_tests - ${GTEST_LIBRARIES} + gtest + gtest_main + pthread utilities ) From e23e5fbb7c0d5f4e3ebadf7c1c5bf489752059f9 Mon Sep 17 00:00:00 2001 From: Michael Langmayr Date: Tue, 6 Aug 2024 16:40:45 -0700 Subject: [PATCH 6/6] add gtest requirement to README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 1f10bf5b..ff449e16 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ If you encounter any problems or have questions about this project, please open - **CMake** 3.12 or higher - **cfitsio** and **CCFits** libraries (expected in `/usr/local/lib`) +- **gtest** (Google Test) library (needed to run unit tests) ### Controller Compatibility