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 048c0ac4..5623581b 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 EXCLUDE_FROM_ALL) 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 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.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/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 ) 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 *****************************************************************/