diff --git a/Config/camerad.cfg.in b/Config/camerad.cfg.in index fe3e69bb..e90faefd 100644 --- a/Config/camerad.cfg.in +++ b/Config/camerad.cfg.in @@ -15,6 +15,13 @@ ASYNCGROUP=239.1.1.234 # or set to NONE if not using ASYNC messaging DAEMON=no # run as a daemon? {yes,no} PUBLISHER_PORT="tcp://127.0.0.1:@CAMERAD_PUB_PORT@" # my zeromq pub port +# Message pub/sub +# PUB_ENDPOINT=tcp://127.0.0.1: +# SUB_ENDPOINT=tcp://127.0.0.1: +# +PUB_ENDPOINT="tcp://127.0.0.1:@MESSAGE_BROKER_SUB_PORT@" +SUB_ENDPOINT="tcp://127.0.0.1:@MESSAGE_BROKER_PUB_PORT@" + # ----------------------------------------------------------------------------- # The following are for simulated ARC controllers ARCSIM_NUMDEV=0 @@ -136,6 +143,17 @@ CONSTKEY_EXT=(SPECTPART WHOLE) # fixed only because we're writing single-channe # USERKEYS_PERSIST=yes +# ACTIVATE_COMMANDS=(CHAN CMD [, CMD, CMD ...]) +# commands sent to activate channel after de-activation +# CHAN is space-delimited from a comma-delimited list of one or more commands +# and must have been first configured by CONTROLER= +# commands that contain a space must be enclosed by double quotes +# +ACTIVATE_COMMANDS=(I PON, ERS 1000 1000, EPG 500, CLR) +ACTIVATE_COMMANDS=(R PON, ERS 1000 1000, EPG 500, CLR) +ACTIVATE_COMMANDS=(G PON, ERS 1000 1000, EPG 500, CLR) +ACTIVATE_COMMANDS=(U PON, CLR) + # ----------------------------------------------------------------------------- # TELEM_PROVIDER=( ) # diff --git a/Config/sequencerd.cfg.in b/Config/sequencerd.cfg.in index 88fe2a0d..61d81707 100644 --- a/Config/sequencerd.cfg.in +++ b/Config/sequencerd.cfg.in @@ -162,26 +162,29 @@ ACQUIRE_MIN_REPEAT=2 # minimum number of sequential successful a ACQUIRE_TCS_MAX_OFFSET=60 # the maximum allowable offset sent to the TCS, in arcsec # Calibration Settings -# CAL_TARGET=(name caldoor calcover lampthar lampfear lampbluc lampredc lolamp hilamp mod1 mod2 ... mod6) +# CAL_TARGET=(name caldoor calcover U G R I lampthar lampfear lampbluc lampredc lolamp hilamp mod1 mod2 ... mod6) # # where name must be "DEFAULT" or start with "CAL_" # caldoor = open | close # calcover = open | close -# lamp* = on | off -# mod* = on | off -# for a total of 15 required parameters +# U,G,R,I = on | off # indicates which channels to enable/disable +# lamp* = on | off # lamp power +# mod* = on | off # lamp modulator +# for a total of 19 required parameters # name=SCIENCE defines science target operation # -# name door cover thar fear bluc redc llmp hlmp mod1 mod2 mod3 mod4 mod5 mod6 -CAL_TARGET=(CAL_THAR open close on off off off off off off off off off off on ) -CAL_TARGET=(CAL_FEAR open close off on off off off off on off off off off off) -CAL_TARGET=(CAL_REDCONT open close off off off on off off off off off on off off) -CAL_TARGET=(CAL_BLUCONT open close off off on off off off off off off off on off) -CAL_TARGET=(CAL_ETALON open close off off off on off off off off on off off off) -CAL_TARGET=(CAL_DOME close open off off off off off on off off off off off off) -CAL_TARGET=(CAL_BIAS close close off off off off off off off off off off off off) -CAL_TARGET=(CAL_DARK close close off off off off off off off off off off off off) -CAL_TARGET=(SCIENCE close open off off off off off off off off off off off off) +# name door cover U G R I thar fear bluc redc llmp hlmp mod1 mod2 mod3 mod4 mod5 mod6 +CAL_TARGET=(CAL_THAR open close on on on on on on on on off off off off off off off on ) +CAL_TARGET=(CAL_FEAR open close on on on on on on on on off off on off off off off off) +CAL_TARGET=(CAL_THAR_UG open close on on off off on on on on off off off off off off off on ) +CAL_TARGET=(CAL_FEAR_UG open close on on off off on on on on off off on off off off off off) +CAL_TARGET=(CAL_CONTR open close on on on on on on on on off off off off off on off off) +CAL_TARGET=(CAL_CONTB open close on on on on on on on on off off off off off off on off) +CAL_TARGET=(CAL_DOME close open on on on on off off off off off on off off off off off off) +CAL_TARGET=(CAL_DOME_UG close open on on off off off off off off off on off off off off off off) +CAL_TARGET=(CAL_BIAS close close on on on on off off off off off off off off off off off off) +CAL_TARGET=(CAL_DARK close close on on on on off off off off off off off off off off off off) +CAL_TARGET=(SCIENCE close open on on on on off off off off off off off off off off off off) # miscellaneous # diff --git a/DSP b/DSP index 6ac8315d..4f4c6801 160000 --- a/DSP +++ b/DSP @@ -1 +1 @@ -Subproject commit 6ac8315ddd249da7eac405a7914a7504c1ae84f6 +Subproject commit 4f4c6801322f52fb43347e8218552cb47829342a diff --git a/camerad/CMakeLists.txt b/camerad/CMakeLists.txt index 037051be..f4fcc0a0 100644 --- a/camerad/CMakeLists.txt +++ b/camerad/CMakeLists.txt @@ -99,6 +99,11 @@ target_include_directories(${INTERFACE_TARGET} PUBLIC ${INTERFACE_INCLUDES}) find_library(CCFITS_LIB CCfits NAMES libCCfits PATHS /usr/local/lib) find_library(CFITS_LIB cfitsio NAMES libcfitsio PATHS /usr/local/lib) +# ZeroMQ +# +find_library( ZMQPP_LIB zmqpp NAMES libzmqpp PATHS /usr/local/lib ) +find_library( ZMQ_LIB zmq NAMES libzmq PATHS /usr/local/lib ) + find_package(Threads) add_library(camera STATIC @@ -124,6 +129,8 @@ target_link_libraries(camerad ${CMAKE_THREAD_LIBS_INIT} ${CCFITS_LIB} ${CFITS_LIB} + ${ZMQPP_LIB} + ${ZMQ_LIB} ) # ---------------------------------------------------------------------------- diff --git a/camerad/astrocam.cpp b/camerad/astrocam.cpp index 594dbf67..2675ccdb 100644 --- a/camerad/astrocam.cpp +++ b/camerad/astrocam.cpp @@ -12,10 +12,44 @@ */ #include "camerad.h" +#include "message_keys.h" + extern Camera::Server server; namespace AstroCam { + /**** AstroCam::Interface::publish_snapshot *********************************/ + /** + * @brief publish a snapshot of my telemetry + * @param[out] retstring optional pointer to buffer for return string + * + */ + void Interface::publish_snapshot(std::string *retstring) { + const std::string function("AstroCam::Interface::publish_snapshot"); + nlohmann::json jmessage_out; + + // build JSON message with my telemetry + jmessage_out[Key::SOURCE] = "camerad"; + jmessage_out[Key::Camerad::READY] = this->can_expose.load(); + + // publish JSON message + try { + this->publisher->publish(jmessage_out); + } + catch (const std::exception &e) { + logwrite(function, "ERROR: "+std::string(e.what())); + return; + } + + // if a retstring buffer was supplied then return the JSON message + if (retstring) { + *retstring=jmessage_out.dump(); + retstring->append(JEOF); + } + } + /**** AstroCam::Interface::publish_snapshot *********************************/ + + long NewAstroCam::new_expose( std::string nseq_in ) { logwrite( "NewAstroCam::new_expose", nseq_in ); return( NO_ERROR ); @@ -159,25 +193,25 @@ namespace AstroCam { return; } - server.controller[devnum].in_frametransfer = false; + server.controller.at(devnum).in_frametransfer = false; server.exposure_pending( devnum, false ); // this also does the notify - server.controller[devnum].in_readout = true; + server.controller.at(devnum).in_readout = true; server.state_monitor_condition.notify_all(); // Trigger the readout waveforms here. // try { - server.controller[devnum].pArcDev->readout( expbuf, + server.controller.at(devnum).pArcDev->readout( expbuf, devnum, - server.controller[devnum].info.axes[_ROW_], - server.controller[devnum].info.axes[_COL_], + server.controller.at(devnum).info.axes[_ROW_], + server.controller.at(devnum).info.axes[_COL_], server.camera.abortstate, - server.controller[devnum].pCallback + server.controller.at(devnum).pCallback ); } catch ( const std::exception &e ) { // arc::gen3::CArcDevice::readout may throw an exception - message.str(""); message << "ERROR starting readout for " << server.controller[devnum].devname - << " channel " << server.controller[devnum].channel << ": " << e.what(); + message.str(""); message << "ERROR starting readout for " << server.controller.at(devnum).devname + << " channel " << server.controller.at(devnum).channel << ": " << e.what(); std::thread( std::ref(AstroCam::Interface::handle_queue), message.str() ).detach(); return; } @@ -187,52 +221,6 @@ namespace AstroCam { /***** AstroCam::Callback::ftCallback ***************************************/ - /***** AstroCam::Interface::Interface ***************************************/ - /** - * @brief AstroCam Interface class constructor - * - */ - Interface::Interface() { - this->state_monitor_thread_running = false; - this->modeselected = false; - this->pci_cmd_num.store(0); - this->nexp.store(1); - this->numdev = 0; - this->nframes = 1; - this->nfpseq = 1; - this->useframes = true; - this->framethreadcount = 0; - - this->pFits.resize( NUM_EXPBUF ); // pre-allocate FITS_file object pointers for each exposure buffer - this->fitsinfo.resize( NUM_EXPBUF ); // pre-allocate Camera Information object pointers for each exposure buffer - - this->writes_pending.resize( NUM_EXPBUF ); // pre-allocate writes_pending vector for each exposure buffer - - // Initialize STL map of Readout Amplifiers - // Indexed by amplifier name. - // The number is the argument for the Arc command to set this amplifier in the firmware. - // - // Format here is: { AMP_NAME, { ENUM_TYPE, ARC_ARG } } - // where AMP_NAME is the name of the readout amplifier, the index for this map - // ENUM_TYPE is an enum of type ReadoutType - // ARC_ARG is the ARC argument for the SOS command to select this readout source - // - this->readout_source.insert( { "U1", { U1, 0x5f5531 } } ); // "_U1" - this->readout_source.insert( { "L1", { L1, 0x5f4c31 } } ); // "_L1" - this->readout_source.insert( { "U2", { U2, 0x5f5532 } } ); // "_U2" - this->readout_source.insert( { "L2", { L2, 0x5f4c32 } } ); // "_L2" - this->readout_source.insert( { "SPLIT1", { SPLIT1, 0x5f5f31 } } ); // "__1" - this->readout_source.insert( { "SPLIT2", { SPLIT2, 0x5f5f32 } } ); // "__2" - this->readout_source.insert( { "QUAD", { QUAD, 0x414c4c } } ); // "ALL" - this->readout_source.insert( { "FT2", { FT2, 0x465432 } } ); // "FT2" -- frame transfer from 1->2, read split2 - this->readout_source.insert( { "FT1", { FT1, 0x465431 } } ); // "FT1" -- frame transfer from 2->1, read split1 -// this->readout_source.insert( { "hawaii1", { HAWAII_1CH, 0xffffff } } ); ///< TODO @todo implement HxRG 1 channel deinterlacing -// this->readout_source.insert( { "hawaii32", { HAWAII_32CH, 0xffffff } } ); ///< TODO @todo implement HxRG 32 channel deinterlacing -// this->readout_source.insert( { "hawaii32lr", { HAWAII_32CH_LR, 0xffffff } } ); ///< TODO @todo implement HxRG 32 channel alternate left/right deinterlacing - } - /***** AstroCam::Interface::Interface ***************************************/ - - /***** AstroCam::Interface::interface ***************************************/ /** * @brief returns the interface @@ -253,7 +241,7 @@ namespace AstroCam { void Interface::state_monitor_thread(Interface& interface) { std::string function = "AstroCam::Interface::state_monitor_thread"; std::stringstream message; - std::vector selectdev; + std::vector selectdev; // notify that the thread is running // @@ -272,11 +260,11 @@ namespace AstroCam { while ( interface.is_camera_idle() ) { selectdev.clear(); message.str(""); message << "enabling detector idling for channel(s)"; - for ( const auto &dev : interface.devnums ) { + for ( const auto &dev : interface.active_devnums ) { logwrite(function, std::to_string(dev)); - if ( interface.controller[dev].connected ) { + if ( interface.controller.at(dev).connected ) { selectdev.push_back(dev); - message << " " << interface.controller[dev].channel; + message << " " << interface.controller.at(dev).channel; } } if ( selectdev.size() > 0 ) { @@ -308,30 +296,30 @@ namespace AstroCam { std::string function = "AstroCam::Interface::make_image_keywords"; std::stringstream message; - auto chan = this->controller[dev].channel; + auto chan = this->controller.at(dev).channel; - auto rows = this->controller[dev].info.axes[_ROW_]; - auto cols = this->controller[dev].info.axes[_COL_]; - auto osrows = this->controller[dev].osrows; - auto oscols = this->controller[dev].oscols; - auto skiprows = this->controller[dev].skiprows; - auto skipcols = this->controller[dev].skipcols; + auto rows = this->controller.at(dev).info.axes[_ROW_]; + auto cols = this->controller.at(dev).info.axes[_COL_]; + auto osrows = this->controller.at(dev).osrows; + auto oscols = this->controller.at(dev).oscols; + auto skiprows = this->controller.at(dev).skiprows; + auto skipcols = this->controller.at(dev).skipcols; int binspec, binspat; - this->controller[dev].physical_to_logical(this->controller[dev].info.binning[_ROW_], - this->controller[dev].info.binning[_COL_], + this->controller.at(dev).physical_to_logical(this->controller.at(dev).info.binning[_ROW_], + this->controller.at(dev).info.binning[_COL_], binspat, binspec); - this->controller[dev].info.systemkeys.add_key( "AMP_ID", this->controller[dev].info.readout_name, "readout amplifier", EXT, chan ); - this->controller[dev].info.systemkeys.add_key( "FT", this->controller[dev].have_ft, "frame transfer used", EXT, chan ); + this->controller.at(dev).info.systemkeys.add_key( "AMP_ID", this->controller.at(dev).info.readout_name, "readout amplifier", EXT, chan ); + this->controller.at(dev).info.systemkeys.add_key( "FT", this->controller.at(dev).have_ft, "frame transfer used", EXT, chan ); - this->controller[dev].info.systemkeys.add_key( "IMG_ROWS", this->controller[dev].info.axes[_ROW_], "image rows", EXT, chan ); - this->controller[dev].info.systemkeys.add_key( "IMG_COLS", this->controller[dev].info.axes[_COL_]-oscols, "image cols", EXT, chan ); + this->controller.at(dev).info.systemkeys.add_key( "IMG_ROWS", this->controller.at(dev).info.axes[_ROW_], "image rows", EXT, chan ); + this->controller.at(dev).info.systemkeys.add_key( "IMG_COLS", this->controller.at(dev).info.axes[_COL_]-oscols, "image cols", EXT, chan ); - this->controller[dev].info.systemkeys.add_key( "OS_ROWS", osrows, "overscan rows", EXT, chan ); - this->controller[dev].info.systemkeys.add_key( "OS_COLS", oscols, "overscan cols", EXT, chan ); - this->controller[dev].info.systemkeys.add_key( "SKIPROWS", skiprows, "skipped rows", EXT, chan ); - this->controller[dev].info.systemkeys.add_key( "SKIPCOLS", skipcols, "skipped cols", EXT, chan ); + this->controller.at(dev).info.systemkeys.add_key( "OS_ROWS", osrows, "overscan rows", EXT, chan ); + this->controller.at(dev).info.systemkeys.add_key( "OS_COLS", oscols, "overscan cols", EXT, chan ); + this->controller.at(dev).info.systemkeys.add_key( "SKIPROWS", skiprows, "skipped rows", EXT, chan ); + this->controller.at(dev).info.systemkeys.add_key( "SKIPCOLS", skipcols, "skipped cols", EXT, chan ); int L=0, B=0; switch ( this->controller[ dev ].info.readout_type ) { @@ -351,57 +339,57 @@ namespace AstroCam { // << " ltv2=" << ltv2 << " ltv1=" << ltv1; //logwrite(function,message.str() ); - this->controller[dev].info.systemkeys.add_key( "LTV2", ltv2, "", EXT, chan ); - this->controller[dev].info.systemkeys.add_key( "LTV1", ltv1, "", EXT, chan ); - this->controller[dev].info.systemkeys.add_key( "CRPIX1A", ltv1+1, "", EXT, chan ); - this->controller[dev].info.systemkeys.add_key( "CRPIX2A", ltv2+1, "", EXT, chan ); + this->controller.at(dev).info.systemkeys.add_key( "LTV2", ltv2, "", EXT, chan ); + this->controller.at(dev).info.systemkeys.add_key( "LTV1", ltv1, "", EXT, chan ); + this->controller.at(dev).info.systemkeys.add_key( "CRPIX1A", ltv1+1, "", EXT, chan ); + this->controller.at(dev).info.systemkeys.add_key( "CRPIX2A", ltv2+1, "", EXT, chan ); - this->controller[dev].info.systemkeys.add_key( "BINSPEC", binspec, "binning in spectral direction", EXT, chan ); - this->controller[dev].info.systemkeys.add_key( "BINSPAT", binspat, "binning in spatial direction", EXT, chan ); + this->controller.at(dev).info.systemkeys.add_key( "BINSPEC", binspec, "binning in spectral direction", EXT, chan ); + this->controller.at(dev).info.systemkeys.add_key( "BINSPAT", binspat, "binning in spatial direction", EXT, chan ); - this->controller[dev].info.systemkeys.add_key( "CDELT1A", - this->controller[dev].info.dispersion*binspec, + this->controller.at(dev).info.systemkeys.add_key( "CDELT1A", + this->controller.at(dev).info.dispersion*binspec, "Dispersion in Angstrom/pixel", EXT, chan ); - this->controller[dev].info.systemkeys.add_key( "CRVAL1A", - this->controller[dev].info.minwavel, + this->controller.at(dev).info.systemkeys.add_key( "CRVAL1A", + this->controller.at(dev).info.minwavel, "Reference value in Angstrom", EXT, chan ); // These keys are for proper mosaic display. // Adjust GAPY to taste. // int GAPY=20; - int crval2 = ( this->controller[dev].info.axes[_ROW_] / binspat + GAPY ) * dev; + int crval2 = ( this->controller.at(dev).info.axes[_ROW_] / binspat + GAPY ) * dev; - this->controller[dev].info.systemkeys.add_key( "CRPIX1", 0, "", EXT, chan ); - this->controller[dev].info.systemkeys.add_key( "CRPIX2", 0, "", EXT, chan ); - this->controller[dev].info.systemkeys.add_key( "CRVAL1", 0, "", EXT, chan ); - this->controller[dev].info.systemkeys.add_key( "CRVAL2", crval2, "", EXT, chan ); + this->controller.at(dev).info.systemkeys.add_key( "CRPIX1", 0, "", EXT, chan ); + this->controller.at(dev).info.systemkeys.add_key( "CRPIX2", 0, "", EXT, chan ); + this->controller.at(dev).info.systemkeys.add_key( "CRVAL1", 0, "", EXT, chan ); + this->controller.at(dev).info.systemkeys.add_key( "CRVAL2", crval2, "", EXT, chan ); // Add ___SEC keywords to the extension header for this channel // std::stringstream sec; /* 01-24-2025 *** - sec.str(""); sec << "[" << this->controller[dev].info.region_of_interest[0] << ":" << this->controller[dev].info.region_of_interest[1] - << "," << this->controller[dev].info.region_of_interest[2] << ":" << this->controller[dev].info.region_of_interest[3] << "]"; - this->controller[dev].info.systemkeys.add_key( "CCDSEC", sec.str(), "physical format of CCD", EXT, chan ); + sec.str(""); sec << "[" << this->controller.at(dev).info.region_of_interest[0] << ":" << this->controller.at(dev).info.region_of_interest[1] + << "," << this->controller.at(dev).info.region_of_interest[2] << ":" << this->controller.at(dev).info.region_of_interest[3] << "]"; + this->controller.at(dev).info.systemkeys.add_key( "CCDSEC", sec.str(), "physical format of CCD", EXT, chan ); - sec.str(""); sec << "[" << this->controller[dev].info.region_of_interest[0] + skipcols << ":" << cols - << "," << this->controller[dev].info.region_of_interest[2] + skiprows << ":" << rows << "]"; - this->controller[dev].info.systemkeys.add_key( "DATASEC", sec.str(), "section containing the CCD data", EXT, chan ); + sec.str(""); sec << "[" << this->controller.at(dev).info.region_of_interest[0] + skipcols << ":" << cols + << "," << this->controller.at(dev).info.region_of_interest[2] + skiprows << ":" << rows << "]"; + this->controller.at(dev).info.systemkeys.add_key( "DATASEC", sec.str(), "section containing the CCD data", EXT, chan ); sec.str(""); sec << '[' << cols << ":" << cols+oscols - << "," << this->controller[dev].info.region_of_interest[2] + skiprows << ":" << rows+osrows << "]"; - this->controller[dev].info.systemkeys.add_key( "BIASSEC", sec.str(), "overscan section", EXT, chan ); + << "," << this->controller.at(dev).info.region_of_interest[2] + skiprows << ":" << rows+osrows << "]"; + this->controller.at(dev).info.systemkeys.add_key( "BIASSEC", sec.str(), "overscan section", EXT, chan ); *** */ sec.str(""); sec << "[" << oscols+1-2 // -2 is KLUDGE FACTOR << ":" << cols-2 // -2 is KLUDGE FACTOR - << "," << this->controller[dev].info.region_of_interest[2] + skiprows << ":" << rows << "]"; - this->controller[dev].info.systemkeys.add_key( "DATASEC", sec.str(), "section containing the CCD data", EXT, chan ); + << "," << this->controller.at(dev).info.region_of_interest[2] + skiprows << ":" << rows << "]"; + this->controller.at(dev).info.systemkeys.add_key( "DATASEC", sec.str(), "section containing the CCD data", EXT, chan ); - sec.str(""); sec << "[" << this->controller[dev].info.region_of_interest[0] << ":" << oscols-2 // -2 is KLUDGE FACTOR - << "," << this->controller[dev].info.region_of_interest[2] + skiprows << ":" << rows << "]"; - this->controller[dev].info.systemkeys.add_key( "BIASSEC", sec.str(), "overscan section", EXT, chan ); + sec.str(""); sec << "[" << this->controller.at(dev).info.region_of_interest[0] << ":" << oscols-2 // -2 is KLUDGE FACTOR + << "," << this->controller.at(dev).info.region_of_interest[2] + skiprows << ":" << rows << "]"; + this->controller.at(dev).info.systemkeys.add_key( "BIASSEC", sec.str(), "overscan section", EXT, chan ); return; } @@ -428,8 +416,8 @@ namespace AstroCam { // Parse the three values from the args string try { int dev = devnum_from_chan(tokens.at(0)); - this->controller[dev].info.dispersion = std::stod(tokens.at(1)); - this->controller[dev].info.minwavel = std::stod(tokens.at(2)); + this->controller.at(dev).info.dispersion = std::stod(tokens.at(1)); + this->controller.at(dev).info.minwavel = std::stod(tokens.at(2)); } catch(const std::exception &e) { logwrite(function, "ERROR parsing SPEC_INFO config: "+std::string(e.what())); @@ -473,8 +461,8 @@ namespace AstroCam { } if (spec==spat) throw std::runtime_error("PHYSSPAT/PHYSSPEC must be unique"); - this->controller[dev].spat_axis = (spat=="ROW" ? Controller::ROW : Controller::COL); - this->controller[dev].spec_axis = (spec=="ROW" ? Controller::ROW : Controller::COL); + this->controller.at(dev).spat_axis = (spat=="ROW" ? Controller::ROW : Controller::COL); + this->controller.at(dev).spec_axis = (spec=="ROW" ? Controller::ROW : Controller::COL); } catch(const std::exception &e) { logwrite(function, "ERROR parsing DETECTOR_GEOMETRY config: "+std::string(e.what())); @@ -518,55 +506,44 @@ namespace AstroCam { * */ long Interface::parse_controller_config( std::string args ) { - std::string function = "AstroCam::Interface::parse_controller_config"; - std::stringstream message; - std::vector tokens; + const std::string function("AstroCam::Interface::parse_controller_config"); + std::ostringstream message; logwrite( function, args ); - int dev, readout_type=-1; - uint32_t readout_arg=0xBAD; - std::string chan, id, firm, amp; - bool ft, readout_valid=false; + std::istringstream iss(args); - Tokenize( args, tokens, " " ); + int readout_type=-1; + uint32_t readout_arg=0xBAD; + bool readout_valid=false; - if ( tokens.size() != 6 ) { - message.str(""); message << "ERROR: bad value \"" << args << "\". expected { PCIDEV CHAN ID FT FIRMWARE READOUT }"; - logwrite( function, message.str() ); - return( ERROR ); + int dev; + std::string chan, id, firm, amp, ft; + bool have_ft; + + if (!(iss >> dev + >> chan + >> id + >> ft + >> firm + >> amp)) { + logwrite(function, "ERROR bad config. expected { PCIDEV CHAN ID FT FIRMWARE READOUT }"); + return ERROR; } - try { - dev = std::stoi( tokens.at(0) ); - chan = tokens.at(1); - id = tokens.at(2); - firm = tokens.at(4); - amp = tokens.at(5); - if ( tokens.at(3) == "yes" ) ft = true; - else - if ( tokens.at(3) == "no" ) ft = false; - else { - message.str(""); message << "unrecognized value for FT: " << tokens.at(2) << ". Expected { yes | no }"; - this->camera.log_error( function, message.str() ); - return( ERROR ); - } - } - catch (std::invalid_argument &) { - this->camera.log_error( function, "invalid number: unable to convert to integer" ); - return(ERROR); - } - catch (std::out_of_range &) { - this->camera.log_error( function, "value out of integer range" ); - return(ERROR); + if (ft=="yes") have_ft=true; + else if (ft=="no") have_ft=false; + else { + logwrite(function, "ERROR. FT expected { yes | no }"); + return ERROR; } // Check the PCIDEV number is in expected range // if ( dev < 0 || dev > 3 ) { - message.str(""); message << "ERROR: bad PCIDEV " << dev << ". Expected {0,1,2,3}"; + message << "ERROR: bad PCIDEV " << dev << ". Expected {0,1,2,3}"; this->camera.log_error( function, message.str() ); - return( ERROR ); + return ERROR; } // Check that READOUT has a match in the list of known readout amps. @@ -595,11 +572,15 @@ namespace AstroCam { // // The first four come from the config file, the rest are defaults // - this->controller[dev].devnum = dev; // device number - this->controller[dev].channel = chan; // spectrographic channel - this->controller[dev].ccd_id = id; // CCD identifier - this->controller[dev].have_ft = ft; // frame transfer supported? - this->controller[dev].firmware = firm; // firmware file + + // create a local reference indexed by dev + Controller &con = this->controller[dev]; + + con.devnum = dev; // device number + con.channel = chan; // spectrographic channel + con.ccd_id = id; // CCD identifier + con.have_ft = have_ft; // frame transfer supported? + con.firmware = firm; // firmware file /* arc::gen3::CArcDevice* pArcDev = NULL; // create a generic CArcDevice pointer @@ -609,39 +590,45 @@ namespace AstroCam { this->controller[dev].pArcDev = pArcDev; // set the pointer to this object in the public vector this->controller[dev].pCallback = pCB; // set the pointer to this object in the public vector */ - this->controller[dev].pArcDev = ( new arc::gen3::CArcPCI() ); // set the pointer to this object in the public vector - this->controller[dev].pCallback = ( new Callback() ); // set the pointer to this object in the public vector - this->controller[dev].devname = ""; // device name - this->controller[dev].connected = false; // not yet connected - this->controller[dev].is_imsize_set = false; // need to set image_size - this->controller[dev].firmwareloaded = false; // no firmware loaded - this->controller[dev].inactive = false; // assume active unless shown otherwise - - this->controller[dev].info.readout_name = amp; - this->controller[dev].info.readout_type = readout_type; - this->controller[dev].readout_arg = readout_arg; + con.pArcDev = ( new arc::gen3::CArcPCI() ); // set the pointer to this object in the public vector + con.pCallback = ( new Callback() ); // set the pointer to this object in the public vector + con.devname = ""; // device name + con.connected = false; // not yet connected + con.is_imsize_set = false; // need to set image_size + con.firmwareloaded = false; // no firmware loaded + + con.info.readout_name = amp; + con.info.readout_type = readout_type; + con.readout_arg = readout_arg; this->exposure_pending( dev, false ); - this->controller[dev].in_readout = false; - this->controller[dev].in_frametransfer = false; + con.in_readout = false; + con.in_frametransfer = false; this->state_monitor_condition.notify_all(); + // configured by config file, can never be reversed unless it is removed from the config file + con.configured = true; + + // configured and active. this state can be reversed on command or failure to connect + // active alone isn't connected, but if not connected then it's not active + con.active = true; + // Header keys specific to this controller are stored in the controller's extension // - this->controller[dev].info.systemkeys.add_key( "CCD_ID", id, "CCD identifier parse", EXT, chan ); - this->controller[dev].info.systemkeys.add_key( "FT", ft, "frame transfer used", EXT, chan ); - this->controller[dev].info.systemkeys.add_key( "AMP_ID", amp, "CCD readout amplifier ID", EXT, chan ); - this->controller[dev].info.systemkeys.add_key( "SPEC_ID", chan, "spectrograph channel", EXT, chan ); - this->controller[dev].info.systemkeys.add_key( "DEV_ID", dev, "detector controller PCI device ID", EXT, chan ); + con.info.systemkeys.add_key( "CCD_ID", id, "CCD identifier parse", EXT, chan ); + con.info.systemkeys.add_key( "FT", ft, "frame transfer used", EXT, chan ); + con.info.systemkeys.add_key( "AMP_ID", amp, "CCD readout amplifier ID", EXT, chan ); + con.info.systemkeys.add_key( "SPEC_ID", chan, "spectrograph channel", EXT, chan ); + con.info.systemkeys.add_key( "DEV_ID", dev, "detector controller PCI device ID", EXT, chan ); // FITS_file* pFits = new FITS_file(); // create a pointer to a FITS_file class object -// this->controller[dev].pFits = pFits; // set the pointer to this object in the public vector +// this->controller.at(dev).pFits = pFits; // set the pointer to this object in the public vector #ifdef LOGLEVEL_DEBUG message.str(""); message << "[DEBUG] pointers for dev " << dev << ": " - << " pArcDev=" << std::hex << std::uppercase << this->controller[dev].pArcDev - << " pCB=" << std::hex << std::uppercase << this->controller[dev].pCallback; + << " pArcDev=" << std::hex << std::uppercase << this->controller.at(dev).pArcDev + << " pCB=" << std::hex << std::uppercase << this->controller.at(dev).pCallback; logwrite(function, message.str()); #endif return( NO_ERROR ); @@ -649,6 +636,59 @@ namespace AstroCam { /***** AstroCam::Interface::parse_controller_config *************************/ + /***** AstroCam::Interface::parse_activate_commands *************************/ + /** + * @brief parses the ACTIVATE_COMMANDS keywords from config file + * @details This gets the list of native commands needed to send when + * (de)activating a controller channel. + * @param[in] args expected format is "CHAN CMD [, CMD, CMD, ...]" + * + */ + long Interface::parse_activate_commands(std::string args) { + const std::string function("AstroCam::Interface::parse_activate_commands"); + logwrite(function, args); + + std::istringstream iss(args); + + // get the channel + std::string chan; + if (!std::getline(iss, chan, ' ')) { + logwrite(function, "ERROR bad config. expected , , ..."); + return ERROR; + } + + // get device number for that channel + int dev; + try { + dev = devnum_from_chan(chan); + } + catch(const std::exception &e) { + logwrite(function, "ERROR: "+std::string(e.what())); + return ERROR; + } + + // get the list of commands + std::string cmdlist; + if (!std::getline(iss, cmdlist)) { + logwrite(function, "ERROR bad config. expected , , ..."); + return ERROR; + } + + // get a pointer to this configured controller + auto pcontroller = this->get_controller(dev); + if (!pcontroller) { + logwrite(function, "ERROR bad controller for channel "+chan); + return ERROR; + } + + // tokenize inserts each command into a vector element + Tokenize(cmdlist, pcontroller->activate_commands, ","); + + return NO_ERROR; + } + /***** AstroCam::Interface::parse_activestate_commands **********************/ + + /***** AstroCam::Interface::devnum_from_chan ********************************/ /** * @brief return the devnum associated with a channel name @@ -660,7 +700,7 @@ namespace AstroCam { int Interface::devnum_from_chan( const std::string &chan ) { int devnum=-1; for ( const auto &con : this->controller ) { - if ( con.second.inactive ) continue; // skip controllers flagged as inactive + if ( !con.second.configured ) continue; // skip controllers not configured if ( con.second.channel == chan ) { // check to see if it matches a configured channel. devnum = con.second.devnum; break; @@ -745,11 +785,14 @@ namespace AstroCam { // for ( const auto &con : this->controller ) { #ifdef LOGLEVEL_DEBUG - message.str(""); message << "[DEBUG] con.first=" << con.first << " con.second.channel=" << con.second.channel - << " .devnum=" << con.second.devnum << " .inactive=" << (con.second.inactive?"T":"F"); + message.str(""); message << "[DEBUG] con.first=" << con.first + << " con.second.channel=" << con.second.channel + << " .devnum=" << con.second.devnum + << " .configured=" << (con.second.configured?"T":"F") + << " .active=" << (con.second.active?"T":"F"); logwrite( function, message.str() ); #endif - if ( con.second.inactive ) continue; // skip controllers flagged as inactive + if (!con.second.configured) continue; // skip controllers not configured if ( con.second.channel == tryme ) { // check to see if it matches a configured channel. dev = con.second.devnum; chan = tryme; @@ -784,7 +827,7 @@ namespace AstroCam { std::string function = "AstroCam::Interface::do_abort"; std::stringstream message; // int this_expbuf = this->get_expbuf(); - for ( const auto &dev : this->devnums ) { + for ( const auto &dev : this->active_devnums ) { this->exposure_pending( dev, false ); for ( int buf=0; buf < NUM_EXPBUF; ++buf ) this->write_pending( buf, dev, false ); } @@ -838,7 +881,7 @@ namespace AstroCam { std::vector pending = this->exposure_pending_list(); message.str(""); message << "ERROR: cannot change binning while exposure is pending for chan"; message << ( pending.size() > 1 ? "s " : " " ); - for ( const auto &dev : pending ) message << this->controller[dev].channel << " "; + for ( const auto &dev : pending ) message << this->controller.at(dev).channel << " "; this->camera.async.enqueue_and_log( "CAMERAD", function, message.str() ); retstring="exposure_in_progress"; return(ERROR); @@ -877,8 +920,10 @@ namespace AstroCam { // This uses the existing image size parameters and the new binning. // The requested overscans are sent here, which can be modified by binning. // - for ( const auto &dev : this->devnums ) { - Controller* pcontroller = &this->controller[dev]; + for ( const auto &dev : this->active_devnums ) { + auto pcontroller = this->get_active_controller(dev); + if (!pcontroller) continue; + // determine which physical axis corresponds to the requested logical axis int physical_axis; if (logical_axis == "spec") { @@ -925,10 +970,10 @@ namespace AstroCam { // return binning for the requested logical axis if (this->numdev>0) { - int dev = this->devnums[0]; - int physical_axis = (logical_axis=="spec") ? this->controller[dev].spec_physical_axis() : - this->controller[dev].spat_physical_axis(); - message.str(""); message << this->controller[dev].info.binning[physical_axis]; + int dev = this->active_devnums[0]; + int physical_axis = (logical_axis=="spec") ? this->controller.at(dev).spec_physical_axis() : + this->controller.at(dev).spat_physical_axis(); + message.str(""); message << this->controller.at(dev).info.binning[physical_axis]; if ( error == NO_ERROR ) retstring = message.str(); } } @@ -957,7 +1002,7 @@ namespace AstroCam { * connect to all detected devices. * * If devices_in is specified (and not empty) then it must contain a space-delimited - * list of device numbers to open. A public vector devnums will hold these device + * list of device numbers to open. A public vector connected_devnums will hold these device * numbers. This vector will be updated here to represent only the devices that * are actually connected. * @@ -966,6 +1011,8 @@ namespace AstroCam { * user wishes to connect to only the device(s) available then the user must * call with the specific device(s). In other words, it's all (requested) or nothing. * + * This will override the controller active flag; all opened devices become active. + * */ long Interface::do_connect_controller( const std::string devices_in, std::string &retstring ) { std::string function = "AstroCam::Interface::do_connect_controller"; @@ -1027,25 +1074,25 @@ namespace AstroCam { // Look at the requested device(s) to open, which are in the // space-delimited string devices_in. The devices to open - // are stored in a public vector "devnums". + // are stored in a public vector "connected_devnums". // // If no string is given then use vector of configured devices. The configured_devnums // vector contains a list of devices defined in the config file with the // keyword CONTROLLER=( ). // - if ( devices_in.empty() ) this->devnums = this->configured_devnums; + if ( devices_in.empty() ) this->connected_devnums = this->configured_devnums; else { - // Otherwise, tokenize the device list string and build devnums from the tokens + // Otherwise, tokenize the device list string and build connected_devnums from the tokens // - this->devnums.clear(); // empty devnums vector since it's being built here + this->connected_devnums.clear(); // empty connected_devnums vector since it's being built here std::vector tokens; Tokenize(devices_in, tokens, " "); for ( const auto &n : tokens ) { // For each token in the devices_in string, try { int dev = std::stoi( n ); // convert to int - if ( std::find( this->devnums.begin(), this->devnums.end(), dev ) == this->devnums.end() ) { // If it's not already in the vector, - this->devnums.push_back( dev ); // then push into devnums vector. + if ( std::find( this->connected_devnums.begin(), this->connected_devnums.end(), dev ) == this->connected_devnums.end() ) { // If it's not already in the vector, + this->connected_devnums.push_back( dev ); // then push into connected_devnums vector. } } catch (const std::exception &e) { @@ -1062,130 +1109,109 @@ namespace AstroCam { } } - // For each requested dev in devnums, if there is a matching controller in the config file, + // For each requested dev in connected_devnums, if there is a matching controller in the config file, // then get the devname and store it in the controller map. // - for ( const auto &dev : this->devnums ) { + for ( const auto &dev : this->connected_devnums ) { if ( this->controller.find( dev ) != this->controller.end() ) { - this->controller[ dev ].devname = devNames[dev]; + this->controller.at( dev ).devname = devNames[dev]; } } - // The size of devnums at this point is the number of devices that will + // The size of connected_devnums at this point is the number of devices that will // be _requested_ to be opened. This should match the number of opened // devices at the end of this function. // - size_t requested_device_count = this->devnums.size(); + size_t requested_device_count = this->connected_devnums.size(); - // Open only the devices specified by the devnums vector + // Open only the devices specified by the connected_devnums vector // - for ( size_t i = 0; i < this->devnums.size(); ) { - int dev = this->devnums[i]; + for ( size_t i = 0; i < this->connected_devnums.size(); ) { + int dev = this->connected_devnums[i]; auto dev_found = this->controller.find( dev ); if ( dev_found == this->controller.end() ) { message.str(""); message << "ERROR: devnum " << dev << " not found in controller definition. check config file"; logwrite( function, message.str() ); - this->controller[dev].inactive=true; // flag the non-connected controller as inactive + this->controller.at(dev).configured=false; // flag as no longer configured + this->controller.at(dev).active=false; // flag as no longer active this->do_disconnect_controller(dev); retstring="unknown_device"; error = ERROR; break; } - else this->controller[dev].inactive=false; + else this->controller.at(dev).configured=true; try { // Open the PCI device if not already open // (otherwise just reset and test connection) // - if ( ! this->controller[dev].connected ) { - message.str(""); message << "opening " << this->controller[dev].devname; + if ( ! this->controller.at(dev).connected ) { + message.str(""); message << "opening " << this->controller.at(dev).devname; logwrite(function, message.str()); - this->controller[dev].pArcDev->open(dev); + this->controller.at(dev).pArcDev->open(dev); } else { - message.str(""); message << this->controller[dev].devname << " already open"; + message.str(""); message << this->controller.at(dev).devname << " already open"; logwrite(function, message.str()); } // Reset the PCI device // - message.str(""); message << "resetting " << this->controller[dev].devname; + message.str(""); message << "resetting " << this->controller.at(dev).devname; logwrite(function, message.str()); try { - this->controller[dev].pArcDev->reset(); + this->controller.at(dev).pArcDev->reset(); } catch (const std::exception &e) { - message.str(""); message << "ERROR resetting " << this->controller[dev].devname << ": " << e.what(); + message.str(""); message << "ERROR resetting " << this->controller.at(dev).devname << ": " << e.what(); logwrite(function, message.str()); error = ERROR; } // Is Controller Connected? (tested with a TDL command) // - this->controller[dev].connected = this->controller[dev].pArcDev->isControllerConnected(); - message.str(""); message << this->controller[dev].devname << (this->controller[dev].connected ? "" : " not" ) << " connected to ARC controller" - << (this->controller[dev].connected ? " for channel " : "" ) - << (this->controller[dev].connected ? this->controller[dev].channel : "" ); + this->controller.at(dev).connected = this->controller.at(dev).pArcDev->isControllerConnected(); + message.str(""); message << this->controller.at(dev).devname << (this->controller.at(dev).connected ? "" : " not" ) << " connected to ARC controller" + << (this->controller.at(dev).connected ? " for channel " : "" ) + << (this->controller.at(dev).connected ? this->controller.at(dev).channel : "" ); logwrite(function, message.str()); - // If not connected then this should remove it from the devnums list - // - if ( !this->controller[dev].connected ) this->do_disconnect_controller(dev); - -/****** YOU CAN'T DO THIS - // Now that controller is open, update it with the current image size - // that has been stored in the class. Create an arg string in the same - // format as that found in the config file. - // - std::stringstream args; - std::string retstring; - args << dev << " " - << this->controller[dev].detrows << " " - << this->controller[dev].detcols << " " - << this->controller[dev].osrows << " " - << this->controller[dev].oscols << " " - << this->camera_info.binning[_ROW_] << " " - << this->camera_info.binning[_COL_]; - - // If image_size fails then close only this controller, - // which allows operating without this one if needed. - // - if ( this->image_size( args.str(), retstring ) != NO_ERROR ) { // set IMAGE_SIZE here after opening - message.str(""); message << "ERROR setting image size for " << this->controller[dev].devname << ": " << retstring; - this->camera.async.enqueue_and_log( function, message.str() ); - this->controller[dev].inactive=true; // flag the non-connected controller as inactive + // If connected then it is active + if ( this->controller.at(dev).connected ) { + this->controller.at(dev).active=true; + } + // otherwise disconnect, which removes it from the connected_devnums list and clears active + else { this->do_disconnect_controller(dev); - error = ERROR; } - ******/ } catch ( const std::exception &e ) { // arc::gen3::CArcPCI::open and reset may throw exceptions - message.str(""); message << "ERROR opening " << this->controller[dev].devname - << " channel " << this->controller[dev].channel << ": " << e.what(); + message.str(""); message << "ERROR opening " << this->controller.at(dev).devname + << " channel " << this->controller.at(dev).channel << ": " << e.what(); this->camera.async.enqueue_and_log( function, message.str() ); - this->controller[dev].inactive=true; // flag the non-connected controller as inactive this->do_disconnect_controller(dev); retstring="exception"; error = ERROR; } - // A call to do_disconnect_controller() can modify the size of devnums, + // A call to do_disconnect_controller() can modify the size of connected_devnums, // so only if the loop index i is still valid with respect to the current - // size of devnums should it be incremented. + // size of connected_devnums should it be incremented. // - if ( i < devnums.size() ) ++i; + if ( i < connected_devnums.size() ) ++i; } // Log the list of connected devices // message.str(""); message << "connected devices { "; - for (const auto &devcheck : this->devnums) { message << devcheck << " "; } message << "}"; + for (const auto &devcheck : this->connected_devnums) { message << devcheck << " "; } message << "}"; logwrite(function, message.str()); - // check the size of the devnums now, against the size requested + // if the size of the connected_devnums now is not the size requested + // then close them all // - if ( this->devnums.size() != requested_device_count ) { - message.str(""); message << "ERROR: " << this->devnums.size() <<" connected device(s) but " + if ( this->connected_devnums.size() != requested_device_count ) { + message.str(""); message << "ERROR: " << this->connected_devnums.size() <<" connected device(s) but " << requested_device_count << " requested"; logwrite( function, message.str() ); @@ -1201,6 +1227,10 @@ namespace AstroCam { error = ERROR; } + // all connected devnums are active devnums at this stage + // + this->active_devnums = this->connected_devnums; + // Start a thread to monitor the state of things (if not already running) // if ( !this->state_monitor_thread_running.load() ) { @@ -1223,6 +1253,8 @@ namespace AstroCam { error = ERROR; } + this->publish_snapshot(); + return( error ); } /***** AstroCam::Interface::do_connect_controller ***************************/ @@ -1233,6 +1265,8 @@ namespace AstroCam { * @brief closes the connection to the specified PCI/e device * @return ERROR or NO_ERROR * + * This will override the controller active flag; all closed devices are inactive. + * * This function is overloaded * */ @@ -1245,7 +1279,7 @@ namespace AstroCam { return ERROR; } - // close indicated PCI device and remove dev from devnums + // close indicated PCI device and remove dev from connected_devnums // try { if ( this->controller.at(dev).pArcDev == nullptr ) { @@ -1257,12 +1291,11 @@ namespace AstroCam { logwrite(function, message.str()); this->controller.at(dev).pArcDev->close(); // throws nothing, no error handling this->controller.at(dev).connected=false; - // remove dev from devnums - // - auto it = std::find( this->devnums.begin(), this->devnums.end(), dev ); - if ( it != this->devnums.end() ) { - this->devnums.erase(it); - } + this->controller.at(dev).active=false; + + // remove dev from connected and active devnums + remove_dev(dev, this->connected_devnums); + remove_dev(dev, this->active_devnums); } catch ( std::out_of_range &e ) { message.str(""); message << "dev " << dev << " not found: " << e.what(); @@ -1282,6 +1315,8 @@ namespace AstroCam { * * no error handling. can only fail if the camera is busy. * + * This will override the controller active flag; all closed devices are inactive. + * * This function is overloaded * */ @@ -1295,17 +1330,19 @@ namespace AstroCam { return( ERROR ); } - // close all of the PCI devices + // close all of the PCI devices regardless of active status // for ( auto &con : this->controller ) { message.str(""); message << "closing " << con.second.devname; logwrite(function, message.str()); if ( con.second.pArcDev != nullptr ) con.second.pArcDev->close(); // throws nothing con.second.connected=false; + con.second.active=false; } - this->devnums.clear(); // no devices open - this->numdev = 0; // no devices open + this->connected_devnums.clear(); // no devices open + this->active_devnums.clear(); // no devices open + this->numdev = 0; // no devices open return error; } /***** AstroCam::Interface::do_disconnect_controller ************************/ @@ -1322,21 +1359,21 @@ namespace AstroCam { std::string function = "AstroCam::Interface::is_connected"; std::stringstream message; - size_t ndev = this->devnums.size(); /// number of connected devices - size_t nopen=0; /// number of open devices (should be equal to ndev if all are open) + size_t ndev = this->connected_devnums.size(); /// number of connected devices + size_t nopen=0; /// number of open devices (should be equal to ndev if all are open) // look through all connected devices // - for ( const auto &dev : this->devnums ) { + for ( const auto &dev : this->connected_devnums ) { if ( this->controller.find( dev ) != this->controller.end() ) - if ( this->controller[dev].connected ) nopen++; + if ( this->controller.at(dev).connected ) nopen++; #ifdef LOGLEVEL_DEBUG - message.str(""); message << "[DEBUG] " << this->controller[dev].devname << " is " << ( this->controller[dev].connected ? "connected" : "disconnected" ); + message.str(""); message << "[DEBUG] " << this->controller.at(dev).devname << " is " << ( this->controller.at(dev).connected ? "connected" : "disconnected" ); logwrite( function, message.str() ); #endif } - // If all devices in (non-empty) devnums are connected then return true, + // If all devices in (non-empty) connected_devnums are connected then return true, // otherwise return false. // if ( ndev !=0 && ndev == nopen ) { @@ -1368,6 +1405,12 @@ namespace AstroCam { // which will get built up from parse_controller_config() below. // this->configured_devnums.clear(); + this->active_devnums.clear(); + this->connected_devnums.clear(); + + // initialize the controller map + // + this->controller.clear(); // loop through the entries in the configuration file, stored in config class // @@ -1390,6 +1433,14 @@ namespace AstroCam { } else + // ACTIVATE_COMMANDS + if (this->config.param[entry]=="ACTIVATE_COMMANDS") { + if (this->parse_activate_commands(this->config.arg[entry]) != ERROR) { + numapplied++; + } + } + else + if ( this->config.param[entry].find( "IMDIR" ) == 0 ) { this->camera.imdir( config.arg[entry] ); numapplied++; @@ -1542,10 +1593,10 @@ namespace AstroCam { return this->do_native( dev, cmdstr, dontcare ); } else { - // didn't find a dev in args so build vector of all open controllers - std::vector selectdev; - for ( const auto &dev : this->devnums ) { - if ( this->controller[dev].connected ) selectdev.push_back( dev ); + // didn't find a dev in args so build vector of all active controllers + std::vector selectdev; + for ( const auto &dev : this->active_devnums ) { + if ( this->controller.at(dev).connected ) selectdev.push_back( dev ); } // this will send the native command to all controllers in that vector return this->do_native( selectdev, args, retstring ); @@ -1562,11 +1613,12 @@ namespace AstroCam { * @return NO_ERROR on success, ERROR on error * */ - long Interface::do_native(std::vector selectdev, std::string cmdstr) { - // Use the erase-remove idiom to remove disconnected devices from selectdev + long Interface::do_native(std::vector selectdev, std::string cmdstr) { + // Use the erase-remove idiom to remove disconnected/inactive devices from selectdev // selectdev.erase( std::remove_if( selectdev.begin(), selectdev.end(), - [this](uint32_t dev) { return !this->controller[dev].connected; } ), + [this](int dev) { return !this->controller.at(dev).connected || + !this->controller.at(dev).active; } ), selectdev.end() ); std::string retstring; @@ -1585,8 +1637,8 @@ namespace AstroCam { * */ long Interface::do_native( int dev, std::string cmdstr, std::string &retstring ) { - std::vector selectdev; - if ( this->controller[dev].connected ) selectdev.push_back( dev ); + std::vector selectdev; + if ( this->controller.at(dev).active ) selectdev.push_back( dev ); return this->do_native( selectdev, cmdstr, retstring ); } /***** AstroCam::Interface::do_native ***************************************/ @@ -1601,7 +1653,7 @@ namespace AstroCam { * @return NO_ERROR | ERROR | HELP * */ - long Interface::do_native( std::vector selectdev, std::string cmdstr, std::string &retstring ) { + long Interface::do_native( std::vector selectdev, std::string cmdstr, std::string &retstring ) { std::string function = "AstroCam::Interface::do_native"; std::stringstream message; std::vector tokens; @@ -1641,6 +1693,15 @@ namespace AstroCam { return( ERROR ); } + // purge selectdev of any inactive devnums to prevent sending this command + // to an inactive controller + // + for (const auto &dev : selectdev) { + if (!this->controller.at(dev).active) { + remove_dev(dev, selectdev); + } + } + std::vector cmd; // this vector will contain the cmd and any arguments uint32_t c0, c1, c2; @@ -1711,7 +1772,7 @@ namespace AstroCam { { // start local scope for this stuff std::vector threads; // local scope vector stores all of the threads created here for ( const auto &dev : selectdev ) { // spawn a thread for each device in selectdev -// std::thread thr( std::ref(AstroCam::Interface::dothread_native), std::ref(this->controller[dev]), cmd ); +// std::thread thr( std::ref(AstroCam::Interface::dothread_native), std::ref(this->controller.at(dev)), cmd ); std::thread thr( &AstroCam::Interface::dothread_native, std::ref(*this), dev, cmd ); threads.push_back(std::move(thr)); // push the thread into a vector } @@ -1734,7 +1795,7 @@ namespace AstroCam { // std::uint32_t check_retval; try { - check_retval = this->controller[selectdev.at(0)].retval; // save the first one in the controller vector + check_retval = this->controller.at(selectdev.at(0)).retval; // save the first one in the controller vector } catch(std::out_of_range &) { logwrite(function, "ERROR: no device found. Is the controller connected?"); @@ -1743,7 +1804,7 @@ namespace AstroCam { } bool allsame = true; - for ( const auto &dev : selectdev ) { if (this->controller[dev].retval != check_retval) { allsame = false; } } + for ( const auto &dev : selectdev ) { if (this->controller.at(dev).retval != check_retval) { allsame = false; } } // If all the return values are equal then return only one value... // @@ -1756,8 +1817,8 @@ namespace AstroCam { else { std::stringstream rs; for ( const auto &dev : selectdev ) { - this->retval_to_string( this->controller[dev].retval, retstring ); // this sets retstring = to_string( retval ) - rs << std::dec << this->controller[dev].devnum << ":" << retstring << " "; // build up a stringstream of each controller's reply + this->retval_to_string( this->controller.at(dev).retval, retstring ); // this sets retstring = to_string( retval ) + rs << std::dec << this->controller.at(dev).devnum << ":" << retstring << " "; // build up a stringstream of each controller's reply } retstring = rs.str(); // re-use retstring to contain all of the replies } @@ -1769,15 +1830,15 @@ namespace AstroCam { /*** for ( const auto &dev : selectdev ) { // any command that doesn't return DON sets error flag - if ( this->controller[dev].retval != 0x00444F4E ) { + if ( this->controller.at(dev).retval != 0x00444F4E ) { error = ERROR; } // std::string retvalstring; -// this->retval_to_string( this->controller[dev].retval, retvalstring ); -// message.str(""); message << this->controller[dev].devname << " \"" << cmdstr << "\"" +// this->retval_to_string( this->controller.at(dev).retval, retvalstring ); +// message.str(""); message << this->controller.at(dev).devname << " \"" << cmdstr << "\"" // << " returns " << retvalstring -// << " (0x" << std::hex << std::uppercase << this->controller[dev].retval << ")"; +// << " (0x" << std::hex << std::uppercase << this->controller.at(dev).retval << ")"; // logwrite(function, message.str()); } ***/ @@ -2242,12 +2303,12 @@ namespace AstroCam { std::stringstream message; uint32_t command; - std::lock_guard lock(this->controller[dev].pcimtx); + std::lock_guard lock(this->controller.at(dev).pcimtx); ++this->pci_cmd_num; message << "sending command (" << std::dec << this->pci_cmd_num << ") to chan " - << this->controller[dev].channel << " dev " << dev << ":" + << this->controller.at(dev).channel << " dev " << dev << ":" << std::setfill('0') << std::setw(2) << std::hex << std::uppercase; for (const auto &arg : cmd) message << " 0x" << arg; logwrite(function, message.str()); @@ -2259,46 +2320,46 @@ namespace AstroCam { // ARC_API now uses an initialized_list object for the TIM_ID, command, and arguments. // The list object must be instantiated with a fixed size at compile time. // - if (cmd.size() == 1) this->controller[dev].retval = this->controller[dev].pArcDev->command( { TIM_ID, cmd.at(0) } ); + if (cmd.size() == 1) this->controller.at(dev).retval = this->controller.at(dev).pArcDev->command( { TIM_ID, cmd.at(0) } ); else - if (cmd.size() == 2) this->controller[dev].retval = this->controller[dev].pArcDev->command( { TIM_ID, cmd.at(0), cmd.at(1) } ); + if (cmd.size() == 2) this->controller.at(dev).retval = this->controller.at(dev).pArcDev->command( { TIM_ID, cmd.at(0), cmd.at(1) } ); else - if (cmd.size() == 3) this->controller[dev].retval = this->controller[dev].pArcDev->command( { TIM_ID, cmd.at(0), cmd.at(1), cmd.at(2) } ); + if (cmd.size() == 3) this->controller.at(dev).retval = this->controller.at(dev).pArcDev->command( { TIM_ID, cmd.at(0), cmd.at(1), cmd.at(2) } ); else - if (cmd.size() == 4) this->controller[dev].retval = this->controller[dev].pArcDev->command( { TIM_ID, cmd.at(0), cmd.at(1), cmd.at(2), cmd.at(3) } ); + if (cmd.size() == 4) this->controller.at(dev).retval = this->controller.at(dev).pArcDev->command( { TIM_ID, cmd.at(0), cmd.at(1), cmd.at(2), cmd.at(3) } ); else - if (cmd.size() == 5) this->controller[dev].retval = this->controller[dev].pArcDev->command( { TIM_ID, cmd.at(0), cmd.at(1), cmd.at(2), cmd.at(3), cmd.at(4) } ); + if (cmd.size() == 5) this->controller.at(dev).retval = this->controller.at(dev).pArcDev->command( { TIM_ID, cmd.at(0), cmd.at(1), cmd.at(2), cmd.at(3), cmd.at(4) } ); else { message.str(""); message << "ERROR: invalid number of command arguments: " << cmd.size() << " (expecting 1,2,3,4,5)"; logwrite(function, message.str()); - this->controller[dev].retval = 0x455252; + this->controller.at(dev).retval = 0x455252; } } catch(const std::runtime_error &e) { message.str(""); message << "ERROR sending (" << this->pci_cmd_num << ") 0x" << std::setfill('0') << std::setw(2) << std::hex << std::uppercase - << command << " to " << this->controller[dev].devname << ": " << e.what(); + << command << " to " << this->controller.at(dev).devname << ": " << e.what(); logwrite(function, message.str()); - this->controller[dev].retval = 0x455252; + this->controller.at(dev).retval = 0x455252; return; } catch(std::out_of_range &) { // impossible logwrite(function, "ERROR: indexing command argument ("+std::to_string(this->pci_cmd_num)+")"); - this->controller[dev].retval = 0x455252; + this->controller.at(dev).retval = 0x455252; return; } catch(...) { message.str(""); message << "ERROR sending (" << std::dec << this->pci_cmd_num << ") 0x" << std::setfill('0') << std::setw(2) << std::hex << std::uppercase - << command << " to " << this->controller[dev].devname << ": unknown"; + << command << " to " << this->controller.at(dev).devname << ": unknown"; logwrite(function, message.str()); - this->controller[dev].retval = 0x455252; + this->controller.at(dev).retval = 0x455252; return; } std::string retvalstring; - this->retval_to_string( this->controller[dev].retval, retvalstring ); - message.str(""); message << this->controller[dev].devname << std::dec << " (" << this->pci_cmd_num << ")" + this->retval_to_string( this->controller.at(dev).retval, retvalstring ); + message.str(""); message << this->controller.at(dev).devname << std::dec << " (" << this->pci_cmd_num << ")" << " returns " << retvalstring; logwrite( function, message.str() ); @@ -2396,10 +2457,10 @@ namespace AstroCam { logwrite(function, message.str()); return(ERROR); } - for (const auto &dev : this->devnums) { // spawn a thread for each device in devnums + for (const auto &dev : this->connected_devnums) { // spawn a thread for each device in connected_devnums try { - int rows = this->controller[dev].rows; - int cols = this->controller[dev].cols; + int rows = this->controller.at(dev).rows; + int cols = this->controller.at(dev).cols; this->nfpseq = parse_val(tokens.at(1)); // requested nframes is nframes/sequence this->nframes = this->nfpseq * this->nsequences; // number of frames is (frames/sequence) x (sequences) @@ -2460,7 +2521,7 @@ namespace AstroCam { } catch( std::out_of_range & ) { message.str(""); message << "ERROR: unable to find device " << dev << " in list: { "; - for ( const auto &check : this->devnums ) message << check << " "; + for ( const auto &check : this->connected_devnums ) message << check << " "; message << "}"; logwrite( function, message.str() ); return( ERROR ); @@ -2491,6 +2552,8 @@ namespace AstroCam { // Log this message once only // if ( interface.exposure_pending() ) { + interface.can_expose.store(false); + interface.publish_snapshot(); interface.camera.async.enqueue_and_log( function, "NOTICE:exposure pending" ); interface.camera.async.enqueue( "CAMERAD:READY:false" ); } @@ -2522,6 +2585,8 @@ namespace AstroCam { interface.do_expose(interface.nexp); } else { + interface.can_expose.store(true); + interface.publish_snapshot(); interface.camera.async.enqueue_and_log( function, "NOTICE:ready for next exposure" ); interface.camera.async.enqueue( "CAMERAD:READY:true" ); } @@ -2544,16 +2609,16 @@ namespace AstroCam { std::string _start_time; long error; - if (this->devnums.empty()) { - logwrite(function, "ERROR no connected controllers"); + if (this->active_devnums.empty()) { + logwrite(function, "ERROR no active controllers"); return ERROR; } - for (const auto &dev : this->devnums) { + for (const auto &dev : this->active_devnums) { std::string naughtylist; - if (!this->controller[dev].is_imsize_set) { + if (!this->controller.at(dev).is_imsize_set) { if (!naughtylist.empty()) naughtylist += ' '; - naughtylist += this->controller[dev].channel; + naughtylist += this->controller.at(dev).channel; } if (!naughtylist.empty()) { logwrite(function, "ERROR image_size not set for channel(s): "+naughtylist); @@ -2571,7 +2636,7 @@ namespace AstroCam { std::vector pending = this->exposure_pending_list(); message.str(""); message << "ERROR: cannot start new exposure while exposure is pending for chan"; message << ( pending.size() > 1 ? "s " : " " ); - for ( const auto &dev : pending ) message << this->controller[dev].channel << " "; + for ( const auto &dev : pending ) message << this->controller.at(dev).channel << " "; this->camera.async.enqueue_and_log( "CAMERAD", function, message.str() ); return(ERROR); } @@ -2616,7 +2681,7 @@ namespace AstroCam { // check readout type // - for ( const auto &dev : this->devnums ) { + for ( const auto &dev : this->active_devnums ) { if ( this->controller[ dev ].info.readout_name.empty() ) { message.str(""); message << "ERROR: readout undefined"; this->camera.async.enqueue_and_log( "CAMERAD", function, message.str() ); @@ -2654,7 +2719,7 @@ namespace AstroCam { // Each thread gets the exposure buffer number for the current exposure, // and a reference to "this" Interface object. // - for ( const auto &dev : this->devnums ) this->write_pending( this_expbuf, dev, true ); + for ( const auto &dev : this->active_devnums ) this->write_pending( this_expbuf, dev, true ); std::thread( std::ref(AstroCam::Interface::FITS_handler), this_expbuf, std::ref(*this) ).detach(); { @@ -2663,23 +2728,23 @@ namespace AstroCam { // If it IS in frame transfer then only clear the CCD if the cameras are idle. // std::string retstr; - for ( const auto &dev : this->devnums ) { + for ( const auto &dev : this->active_devnums ) { if ( this->is_camera_idle( dev ) ) { error = this->do_native( dev, "CLR", retstr ); // send the clear command here to this dev if ( error != NO_ERROR ) { - message.str(""); message << "ERROR clearing chan " << this->controller[dev].channel << " CCD: " << retstr; + message.str(""); message << "ERROR clearing chan " << this->controller.at(dev).channel << " CCD: " << retstr; logwrite( function, message.str() ); return( error ); } - message.str(""); message << "cleared chan " << this->controller[dev].channel << " CCD"; + message.str(""); message << "cleared chan " << this->controller.at(dev).channel << " CCD"; logwrite( function, message.str() ); } #ifdef LOGLEVEL_DEBUG else { - message.str(""); message << "[DEBUG] chan " << this->controller[dev].channel << " CCD was *not* cleared:" + message.str(""); message << "[DEBUG] chan " << this->controller.at(dev).channel << " CCD was *not* cleared:" << " exposure_pending=" << this->exposure_pending() - << " in_readout=" << this->controller[dev].in_readout - << " in_frametransfer=" << this->controller[dev].in_frametransfer; + << " in_readout=" << this->controller.at(dev).in_readout + << " in_frametransfer=" << this->controller.at(dev).in_frametransfer; logwrite( function, message.str() ); } #endif @@ -2730,22 +2795,22 @@ namespace AstroCam { // and spawn a thread to monitor it, which will provide a notification // when ready for the next exposure. // - for ( const auto &dev : this->devnums ) this->exposure_pending( dev, true ); + for ( const auto &dev : this->active_devnums ) this->exposure_pending( dev, true ); this->state_monitor_condition.notify_all(); std::thread( std::ref(AstroCam::Interface::dothread_monitor_exposure_pending), std::ref(*this) ).detach(); - // prepare the camera info class object for each controller + // prepare the camera info class object for each active controller // - for (const auto &dev : this->devnums) { // spawn a thread for each device in devnums + for (const auto &dev : this->active_devnums) { // spawn a thread for each device in active_devnums try { // Initialize a frame counter for each device. // - this->controller[dev].init_framecount(); + this->controller.at(dev).init_framecount(); // Allocate workspace memory for deinterlacing (each dev has its own workbuf) // - if ( ( error = this->controller[dev].alloc_workbuf( ) ) != NO_ERROR ) { + if ( ( error = this->controller.at(dev).alloc_workbuf( ) ) != NO_ERROR ) { this->camera.async.enqueue_and_log( "CAMERAD", function, "ERROR: allocating memory for deinterlacing" ); return( error ); } @@ -2754,16 +2819,16 @@ namespace AstroCam { // then set the filename for this specific dev // Assemble the FITS filename. // If naming type = "time" then this will use this->fitstime so that must be set first. - // If there are multiple devices in the devnums then force the fitsname to include the dev number + // If there are multiple devices in the active_devnums then force the fitsname to include the dev number // in order to make it unique for each device. // - if ( this->devnums.size() > 1 ) { + if ( this->active_devnums.size() > 1 ) { devstr = std::to_string( dev ); // passing a non-empty devstr will put that in the fitsname } else { devstr = ""; } - if ( ( error = this->camera.get_fitsname( devstr, this->controller[dev].info.fits_name ) ) != NO_ERROR ) { + if ( ( error = this->camera.get_fitsname( devstr, this->controller.at(dev).info.fits_name ) ) != NO_ERROR ) { this->camera.async.enqueue_and_log( "CAMERAD", function, "ERROR: assembling fitsname" ); return( error ); } @@ -2772,15 +2837,15 @@ namespace AstroCam { #ifdef LOGLEVEL_DEBUG message.str(""); message << "[DEBUG] pointers for dev " << dev << ": " - << " pArcDev=" << std::hex << this->controller[dev].pArcDev - << " pCB=" << std::hex << this->controller[dev].pCallback; -// << " pFits=" << std::hex << this->controller[dev].pFits; + << " pArcDev=" << std::hex << this->controller.at(dev).pArcDev + << " pCB=" << std::hex << this->controller.at(dev).pCallback; +// << " pFits=" << std::hex << this->controller.at(dev).pFits; logwrite(function, message.str()); #endif } catch(std::out_of_range &) { message.str(""); message << "ERROR: unable to find device " << dev << " in list: { "; - for (const auto &check : this->devnums) message << check << " "; + for (const auto &check : this->active_devnums) message << check << " "; message << "}"; this->camera.async.enqueue_and_log( "CAMERAD", function, message.str() ); return(ERROR); @@ -2812,7 +2877,7 @@ namespace AstroCam { this->camera_info.systemkeys.add_key("CDELT2A", 0.25*this->camera_info.binspat, "Spatial scale in arcsec/pixel", EXT, "all"); this->camera_info.systemkeys.add_key("CRVAL2A", 0.0, "Reference value in arcsec", EXT, "all"); - for (const auto &dev : this->devnums) { // spawn a thread for each device in devnums + for (const auto &dev : this->active_devnums) { // spawn a thread for each device in active_devnums this->make_image_keywords(dev); @@ -2820,27 +2885,27 @@ namespace AstroCam { // copy the info class from controller[dev] to controller[dev].expinfo[expbuf] // - this->controller[dev].expinfo[this_expbuf] = this->controller[dev].info; + this->controller.at(dev).expinfo[this_expbuf] = this->controller.at(dev).info; // copy the info class from controller[dev] to controller[dev].expinfo[expbuf] // create handy references to the Common::Header objects for expinfo // - auto &_systemkeys = this->controller[dev].expinfo[this_expbuf].systemkeys; - auto &_telemkeys = this->controller[dev].expinfo[this_expbuf].telemkeys; - auto &_userkeys = this->controller[dev].expinfo[this_expbuf].userkeys; + auto &_systemkeys = this->controller.at(dev).expinfo[this_expbuf].systemkeys; + auto &_telemkeys = this->controller.at(dev).expinfo[this_expbuf].telemkeys; + auto &_userkeys = this->controller.at(dev).expinfo[this_expbuf].userkeys; // store BOI in this local _systemkeys so that it's overwritten each exposure // int nboi=0; int lastrowread=0; int stop=0; - for ( const auto &[nskip,nread] : this->controller[dev].info.interest_bands ) { + for ( const auto &[nskip,nread] : this->controller.at(dev).info.interest_bands ) { nboi++; std::string boikey = "BOI"+std::to_string(nboi); lastrowread += nskip; stop = lastrowread+nread; std::string boival = std::to_string(lastrowread)+":"+std::to_string(stop); - _systemkeys.add_key( boikey, boival, "band of interest "+std::to_string(nboi), EXT, this->controller[dev].channel ); + _systemkeys.add_key( boikey, boival, "band of interest "+std::to_string(nboi), EXT, this->controller.at(dev).channel ); lastrowread = stop; } @@ -2861,7 +2926,7 @@ namespace AstroCam { // to reference keywords to be written to all extensions and only the // extension for this channel // - auto channel = this->controller[dev].channel; + auto channel = this->controller.at(dev).channel; std::vector channels = { "all", channel }; // Loop through both "channels" and merge the Header objects from camera_info @@ -2880,10 +2945,10 @@ namespace AstroCam { this->fitsinfo[this_expbuf]->telemkeys.primary() = _telemkeys.primary(); this->fitsinfo[this_expbuf]->userkeys.primary() = _userkeys.primary(); - this->controller[dev].expinfo[this_expbuf].fits_name="not_needed"; + this->controller.at(dev).expinfo[this_expbuf].fits_name="not_needed"; std::string hash; - md5_file( this->controller[dev].firmware, hash ); // compute the md5 hash + md5_file( this->controller.at(dev).firmware, hash ); // compute the md5 hash // erase the per-exposure keyword databases. // @@ -2905,13 +2970,13 @@ namespace AstroCam { // std::thread( std::ref(AstroCam::Interface::dothread_read), std::ref(this->camera), - std::ref(this->controller[dev]), + std::ref(this->controller.at(dev)), this_expbuf ).detach(); } catch(std::out_of_range &) { message.str(""); message << "ERROR: unable to find device " << dev << " in list: { "; - for (const auto &check : this->devnums) message << check << " "; + for (const auto &check : this->active_devnums) message << check << " "; message << "}"; this->camera.async.enqueue_and_log( "CAMERAD", function, message.str() ); return(ERROR); @@ -2930,36 +2995,36 @@ namespace AstroCam { logwrite( function, "[DEBUG] expose is done now!" ); - for (const auto &dev : this->devnums) { + for (const auto &dev : this->active_devnums) { message.str(""); message << std::dec - << "** dev=" << dev << " type_set=" << this->controller[dev].info.type_set << " frame_type=" << this->controller[dev].info.frame_type - << " detector_pixels[]=" << this->controller[dev].info.detector_pixels[0] << " " << this->controller[dev].info.detector_pixels[1] - << " section_size=" << this->controller[dev].info.section_size << " image_memory=" << this->controller[dev].info.image_memory - << " readout_name=" << this->controller[dev].info.readout_name - << " readout_name2=" << this->controller[dev].expinfo[this_expbuf].readout_name - << " readout_type=" << this->controller[dev].info.readout_type - << " axes[]=" << this->controller[dev].info.axes[0] << " " << this->controller[dev].info.axes[1] << " " << this->controller[dev].info.axes[2] - << " cubedepth=" << this->controller[dev].info.cubedepth << " fitscubed=" << this->controller[dev].info.fitscubed - << " phys binning=" << this->controller[dev].info.binning[0] << " " << this->controller[dev].info.binning[1] - << " axis_pixels[]=" << this->controller[dev].info.axis_pixels[0] << " " << this->controller[dev].info.axis_pixels[1] - << " ismex=" << this->controller[dev].info.ismex << " extension=" << this->controller[dev].info.extension; + << "** dev=" << dev << " type_set=" << this->controller.at(dev).info.type_set << " frame_type=" << this->controller.at(dev).info.frame_type + << " detector_pixels[]=" << this->controller.at(dev).info.detector_pixels[0] << " " << this->controller.at(dev).info.detector_pixels[1] + << " section_size=" << this->controller.at(dev).info.section_size << " image_memory=" << this->controller.at(dev).info.image_memory + << " readout_name=" << this->controller.at(dev).info.readout_name + << " readout_name2=" << this->controller.at(dev).expinfo[this_expbuf].readout_name + << " readout_type=" << this->controller.at(dev).info.readout_type + << " axes[]=" << this->controller.at(dev).info.axes[0] << " " << this->controller.at(dev).info.axes[1] << " " << this->controller.at(dev).info.axes[2] + << " cubedepth=" << this->controller.at(dev).info.cubedepth << " fitscubed=" << this->controller.at(dev).info.fitscubed + << " phys binning=" << this->controller.at(dev).info.binning[0] << " " << this->controller.at(dev).info.binning[1] + << " axis_pixels[]=" << this->controller.at(dev).info.axis_pixels[0] << " " << this->controller.at(dev).info.axis_pixels[1] + << " ismex=" << this->controller.at(dev).info.ismex << " extension=" << this->controller.at(dev).info.extension; logwrite( function, message.str() ); } - for (const auto &dev : this->devnums) { + for (const auto &dev : this->active_devnums) { for ( int ii=0; iicontroller[dev].expinfo.at(ii).detector_pixels[1] - << " section_size=" << this->controller[dev].expinfo.at(ii).section_size << " image_memory=" << this->controller[dev].expinfo.at(ii).image_memory - << " readout_name=" << this->controller[dev].expinfo.at(ii).readout_name << " readout_type=" << this->controller[dev].expinfo.at(ii).readout_type - << " axes[]=" << this->controller[dev].expinfo.at(ii).axes[0] << " " << this->controller[dev].expinfo.at(ii).axes[1] << " " << this->controller[dev].expinfo.at(ii).axes[2] - << " cubedepth=" << this->controller[dev].expinfo.at(ii).cubedepth << " fitscubed=" << this->controller[dev].expinfo.at(ii).fitscubed - << " phys binning=" << this->controller[dev].expinfo.at(ii).binning[0] << " " << this->controller[dev].expinfo.at(ii).binning[1] - << " axis_pixels[]=" << this->controller[dev].expinfo.at(ii).axis_pixels[0] << " " << this->controller[dev].expinfo.at(ii).axis_pixels[1] - << " ismex=" << this->controller[dev].expinfo.at(ii).ismex << " extension=" << this->controller[dev].expinfo.at(ii).extension; + << "** dev=" << dev << " expbuf=" << ii << " type_set=" << this->controller.at(dev).expinfo.at(ii).type_set << " frame_type=" << this->controller.at(dev).expinfo.at(ii).frame_type + << " detector_pixels[]=" << this->controller.at(dev).expinfo.at(ii).detector_pixels[0] << " " << this->controller.at(dev).expinfo.at(ii).detector_pixels[1] + << " section_size=" << this->controller.at(dev).expinfo.at(ii).section_size << " image_memory=" << this->controller.at(dev).expinfo.at(ii).image_memory + << " readout_name=" << this->controller.at(dev).expinfo.at(ii).readout_name << " readout_type=" << this->controller.at(dev).expinfo.at(ii).readout_type + << " axes[]=" << this->controller.at(dev).expinfo.at(ii).axes[0] << " " << this->controller.at(dev).expinfo.at(ii).axes[1] << " " << this->controller.at(dev).expinfo.at(ii).axes[2] + << " cubedepth=" << this->controller.at(dev).expinfo.at(ii).cubedepth << " fitscubed=" << this->controller.at(dev).expinfo.at(ii).fitscubed + << " phys binning=" << this->controller.at(dev).expinfo.at(ii).binning[0] << " " << this->controller.at(dev).expinfo.at(ii).binning[1] + << " axis_pixels[]=" << this->controller.at(dev).expinfo.at(ii).axis_pixels[0] << " " << this->controller.at(dev).expinfo.at(ii).axis_pixels[1] + << " ismex=" << this->controller.at(dev).expinfo.at(ii).ismex << " extension=" << this->controller.at(dev).expinfo.at(ii).extension; logwrite( function, message.str() ); } } @@ -3357,10 +3422,10 @@ namespace AstroCam { // to load each controller with the specified file. // for ( const auto &con : this->controller ) { - if ( con.second.inactive ) continue; // skip controllers flagged as inactive - // But only use it if the device is open + if (!con.second.configured) continue; // skip controllers not configured + // But only use it if the device is open and active // - if ( con.second.connected ) { + if ( con.second.connected && con.second.active ) { std::stringstream lodfilestream; lodfilestream << con.second.devnum << " " << con.second.firmware; @@ -3399,7 +3464,7 @@ namespace AstroCam { std::string function = "AstroCam::Interface::do_load_firmware"; std::stringstream message; std::vector tokens; - std::vector selectdev; + std::vector selectdev; struct stat st; long error = ERROR; @@ -3441,10 +3506,10 @@ namespace AstroCam { } // If there's only one token then it's the lodfile and load - // into all controllers in the devnums. + // into all controllers in the active_devnums. // if (tokens.size() == 1) { - for (const auto &dev : this->devnums) { + for (const auto &dev : this->active_devnums) { selectdev.push_back( dev ); // build selectdev vector from all connected controllers } } @@ -3455,7 +3520,7 @@ namespace AstroCam { // if (tokens.size() > 1) { for (uint32_t n = 0; n < (tokens.size()-1); n++) { // tokens.size() - 1 because the last token must be the filename - selectdev.push_back( (uint32_t)parse_val( tokens.at(n) ) ); + selectdev.push_back( (int)parse_val( tokens.at(n) ) ); } timlodfile = tokens.at( tokens.size() - 1 ); // the last token must be the filename } @@ -3474,8 +3539,8 @@ namespace AstroCam { for (const auto &dev : selectdev) { // spawn a thread for each device in the selectdev list if ( firstdev == -1 ) firstdev = dev; // save the first device from the list of connected controllers try { - if ( this->controller[dev].connected ) { // but only if connected - std::thread thr( std::ref(AstroCam::Interface::dothread_load), std::ref(this->controller[dev]), timlodfile ); + if ( this->controller.at(dev).connected ) { // but only if connected + std::thread thr( std::ref(AstroCam::Interface::dothread_load), std::ref(this->controller.at(dev)), timlodfile ); threads.push_back ( std::move(thr) ); // push the thread into the local vector } } @@ -3517,7 +3582,7 @@ namespace AstroCam { check_retval = this->controller[firstdev].retval; // save the first one in the controller vector bool allsame = true; - for ( const auto &dev : selectdev ) { if ( this->controller[dev].retval != check_retval ) { allsame = false; } } + for ( const auto &dev : selectdev ) { if ( this->controller.at(dev).retval != check_retval ) { allsame = false; } } // If all the return values are equal then report only NO_ERROR (if "DON") or ERROR (anything else) // @@ -3534,8 +3599,8 @@ namespace AstroCam { std::stringstream rss; std::string rs; for (const auto &dev : selectdev) { - this->retval_to_string( this->controller[dev].retval, rs ); // convert the retval to string (DON, ERR, etc.) - rss << this->controller[dev].devnum << ":" << rs << " "; + this->retval_to_string( this->controller.at(dev).retval, rs ); // convert the retval to string (DON, ERR, etc.) + rss << this->controller.at(dev).devnum << ":" << rs << " "; } retstring = rss.str(); error = ERROR; @@ -3545,8 +3610,8 @@ namespace AstroCam { /*** logwrite( function, "NOTICE: firmware loaded" ); for ( const auto &dev : selectdev ) { - for ( auto it = this->controller[dev].extkeys.keydb.begin(); - it != this->controller[dev].extkeys.keydb.end(); it++ ) { + for ( auto it = this->controller.at(dev).extkeys.keydb.begin(); + it != this->controller.at(dev).extkeys.keydb.end(); it++ ) { message.str(""); message << "NOTICE: dev=" << dev << "key=" << it->second.keyword << " val=" << it->second.keyvalue; logwrite( function, message.str() ); } @@ -3554,7 +3619,7 @@ for ( const auto &dev : selectdev ) { ***/ for (const auto &dev: selectdev) { - std::string init=this->controller[dev].channel+" init"; + std::string init=this->controller.at(dev).channel+" init"; std::string retstring; this->image_size(init, retstring); } @@ -3661,7 +3726,8 @@ for ( const auto &dev : selectdev ) { retstring.append( " Specify from { " ); message.str(""); for ( const auto &con : this->controller ) { - if ( con.second.inactive ) continue; // skip controllers flagged as inactive + // skip unconfigured and inactive controllers + if (!con.second.configured || !con.second.active) continue; message << con.second.channel << " "; } message << "}\n"; @@ -3669,7 +3735,8 @@ for ( const auto &dev : selectdev ) { retstring.append( " or from { " ); message.str(""); for ( const auto &con : this->controller ) { - if ( con.second.inactive ) continue; // skip controllers flagged as inactive + // skip unconfigured and inactive controllers + if (!con.second.configured || !con.second.active) continue; message << con.second.devnum << " "; } message << "}\n"; @@ -3791,7 +3858,8 @@ for ( const auto &dev : selectdev ) { retstring.append( " Specify from { " ); message.str(""); for ( const auto &con : this->controller ) { - if ( con.second.inactive ) continue; // skip controllers flagged as inactive + // skip unconfigured and inactive controllers + if (!con.second.configured || !con.second.active) continue; message << con.second.channel << " "; } message << "}\n"; @@ -3799,7 +3867,8 @@ for ( const auto &dev : selectdev ) { retstring.append( " or from { " ); message.str(""); for ( const auto &con : this->controller ) { - if ( con.second.inactive ) continue; // skip controllers flagged as inactive + // skip unconfigured and inactive controllers + if (!con.second.configured || !con.second.active) continue; message << con.second.devnum << " "; } message << "}\n"; @@ -3872,7 +3941,7 @@ for ( const auto &dev : selectdev ) { // In any case, set or not, get the current type // - retstring = this->controller[dev].info.readout_name; + retstring = this->controller.at(dev).info.readout_name; return( NO_ERROR ); } @@ -3911,7 +3980,8 @@ for ( const auto &dev : selectdev ) { retstring.append( " Specify from { " ); message.str(""); for ( const auto &con : this->controller ) { - if ( con.second.inactive ) continue; // skip controllers flagged as inactive + // skip unconfigured and inactive controllers + if (!con.second.configured || !con.second.active) continue; message << con.second.channel << " "; } message << "}\n"; @@ -3919,7 +3989,8 @@ for ( const auto &dev : selectdev ) { retstring.append( " or from { " ); message.str(""); for ( const auto &con : this->controller ) { - if ( con.second.inactive ) continue; // skip controllers flagged as inactive + // skip unconfigured and inactive controllers + if (!con.second.configured || !con.second.active) continue; message << con.second.devnum << " "; } message << "}\n"; @@ -3957,13 +4028,10 @@ for ( const auto &dev : selectdev ) { std::string chan; if ( this->extract_dev_chan( args, dev, chan, retstring ) != NO_ERROR ) return ERROR; - Controller* pcontroller = &this->controller[dev]; + Controller* pcontroller = this->get_active_controller(dev); - // don't continue if that controller is not connected now - // - if ( !pcontroller->connected ) { - logwrite( function, "ERROR controller channel "+chan+" not connected" ); - retstring="not_connected"; + if (!pcontroller) { + logwrite(function, "ERROR: controller not available for channel "+chan); return ERROR; } @@ -4168,11 +4236,11 @@ for ( const auto &dev : selectdev ) { // and fpbcount to index the frameinfo STL map on that devnum, // and assign the pointer to that buffer to a local variable. // - void* imbuf = this->controller[devnum].frameinfo[fpbcount].buf; + void* imbuf = this->controller.at(devnum).frameinfo[fpbcount].buf; - message << this->controller[devnum].devname << " received exposure " - << this->controller[devnum].frameinfo[fpbcount].framenum << " into image buffer " - << std::hex << this->controller[devnum].frameinfo[fpbcount].buf; + message << this->controller.at(devnum).devname << " received exposure " + << this->controller.at(devnum).frameinfo[fpbcount].framenum << " into image buffer " + << std::hex << this->controller.at(devnum).frameinfo[fpbcount].buf; logwrite(function, message.str()); // Call the class' deinterlace and write functions. @@ -4182,12 +4250,12 @@ for ( const auto &dev : selectdev ) { // that buffer is already known. // try { - switch (this->controller[devnum].info.datatype) { + switch (this->controller.at(devnum).info.datatype) { case USHORT_IMG: { - this->controller[devnum].deinterlace( expbuf, (uint16_t *)imbuf ); -message.str(""); message << this->controller[devnum].devname << " exposure buffer " << expbuf << " deinterlaced " << std::hex << imbuf; + this->controller.at(devnum).deinterlace( expbuf, (uint16_t *)imbuf ); +message.str(""); message << this->controller.at(devnum).devname << " exposure buffer " << expbuf << " deinterlaced " << std::hex << imbuf; logwrite(function, message.str()); -message.str(""); message << "about to write section size " << this->controller[devnum].expinfo[expbuf].section_size ; // << " to file \"" << this->pFits[expbuf]->fits_name << "\""; +message.str(""); message << "about to write section size " << this->controller.at(devnum).expinfo[expbuf].section_size ; // << " to file \"" << this->pFits[expbuf]->fits_name << "\""; logwrite(function, message.str()); // Call write_image(), @@ -4198,27 +4266,27 @@ logwrite(function, message.str()); this->pFits[ expbuf ]->extension++; -message.str(""); message << this->controller[devnum].devname << " exposure buffer " << expbuf << " wrote " << std::hex << this->controller[devnum].workbuf; +message.str(""); message << this->controller.at(devnum).devname << " exposure buffer " << expbuf << " wrote " << std::hex << this->controller.at(devnum).workbuf; logwrite(function, message.str()); -// error = this->controller[devnum].write( ); 10/30/23 BOB -- the write is above. .write() called ->write_image(), skip that extra function +// error = this->controller.at(devnum).write( ); 10/30/23 BOB -- the write is above. .write() called ->write_image(), skip that extra function break; } /******* case SHORT_IMG: { - this->controller[devnum].deinterlace( expbuf, (int16_t *)imbuf ); - error = this->controller[devnum].write( ); + this->controller.at(devnum).deinterlace( expbuf, (int16_t *)imbuf ); + error = this->controller.at(devnum).write( ); break; } case FLOAT_IMG: { - this->controller[devnum].deinterlace( expbuf, (uint32_t *)imbuf ); - error = this->controller[devnum].write( ); + this->controller.at(devnum).deinterlace( expbuf, (uint32_t *)imbuf ); + error = this->controller.at(devnum).write( ); break; } ********/ default: message.str(""); - message << "ERROR: unknown datatype: " << this->controller[devnum].info.datatype; + message << "ERROR: unknown datatype: " << this->controller.at(devnum).info.datatype; logwrite(function, message.str()); error = ERROR; break; @@ -4226,16 +4294,16 @@ logwrite(function, message.str()); // A frame has been written for this device, // so increment the framecounter for devnum. // - if (error == NO_ERROR) this->controller[devnum].increment_framecount(); + if (error == NO_ERROR) this->controller.at(devnum).increment_framecount(); #ifdef LOGLEVEL_DEBUG message.str(""); message << "[DEBUG] framecount(" << devnum << ")=" - << this->controller[devnum].get_framecount() << " written"; + << this->controller.at(devnum).get_framecount() << " written"; logwrite( function, message.str() ); #endif } catch (std::out_of_range &) { message.str(""); message << "ERROR: unable to find device " << devnum << " in list: { "; - for (const auto &check : this->devnums) message << check << " "; + for (const auto &check : this->active_devnums) message << check << " "; message << "}"; logwrite(function, message.str()); error = ERROR; @@ -4245,9 +4313,9 @@ logwrite(function, message.str()); message.str(""); message << "[DEBUG] completed " << (error != NO_ERROR ? "with error. " : "ok. ") << "devnum=" << devnum << " " << "fpbcount=" << fpbcount << " " - << this->controller[devnum].devname << " received exposure " - << this->controller[devnum].frameinfo[fpbcount].framenum << " into buffer " - << std::hex << std::uppercase << this->controller[devnum].frameinfo[fpbcount].buf; + << this->controller.at(devnum).devname << " received exposure " + << this->controller.at(devnum).frameinfo[fpbcount].framenum << " into buffer " + << std::hex << std::uppercase << this->controller.at(devnum).frameinfo[fpbcount].buf; logwrite(function, message.str()); #endif return( error ); @@ -4285,6 +4353,7 @@ logwrite(function, message.str()); if ( this->in_readout() ) { message.str(""); message << "ERROR: cannot change exposure time while reading out chan "; for ( const auto &con : this->controller ) { + if (!con.second.active) continue; // skip inactive controllers if ( con.second.in_readout || con.second.in_frametransfer ) message << con.second.channel << " "; } this->camera.async.enqueue_and_log( "CAMERAD", function, message.str() ); @@ -4578,16 +4647,16 @@ logwrite(function, message.str()); // Set shutterenable the same for all devices // - if ( error==NO_ERROR && this->camera.ext_shutter ) for ( const auto &dev : this->devnums ) { - this->controller[dev].info.shutterenable = this->camera.shutter.is_enabled; + if ( error==NO_ERROR && this->camera.ext_shutter ) for ( const auto &dev : this->active_devnums ) { + this->controller.at(dev).info.shutterenable = this->camera.shutter.is_enabled; } } // For external shutter (i.e. triggered by Leach controller) // read the shutterenable state back from the controller class. // - if ( this->camera.ext_shutter ) for ( const auto &dev : this->devnums ) { - this->camera.shutter.is_enabled = this->controller[dev].info.shutterenable; + if ( this->camera.ext_shutter ) for ( const auto &dev : this->active_devnums ) { + this->camera.shutter.is_enabled = this->controller.at(dev).info.shutterenable; break; // just need one since they're all the same } // otherwise shutterenable state is whatever is in the camera_info class @@ -4652,7 +4721,8 @@ logwrite(function, message.str()); retstring.append( " Specify from { " ); message.str(""); for ( const auto &con : this->controller ) { - if ( con.second.inactive ) continue; // skip controllers flagged as inactive + // skip unconfigured and inactive controllers + if (!con.second.configured || !con.second.active) continue; message << con.second.channel << " "; } message << "}\n"; @@ -4660,7 +4730,8 @@ logwrite(function, message.str()); retstring.append( " or from { " ); message.str(""); for ( const auto &con : this->controller ) { - if ( con.second.inactive ) continue; // skip controllers flagged as inactive + // skip unconfigured and inactive controllers + if (!con.second.configured || !con.second.active) continue; message << con.second.devnum << " "; } message << "}\n"; @@ -4673,8 +4744,8 @@ logwrite(function, message.str()); // if (args=="all") { bool all_true = true; - for ( const auto &dev : this->devnums ) { - if ( !this->controller[dev].have_ft ) { + for ( const auto &dev : this->active_devnums ) { + if ( !this->controller.at(dev).have_ft ) { all_true=false; break; } @@ -4705,14 +4776,14 @@ logwrite(function, message.str()); // If a state was provided then set it // if ( ! retstring.empty() ) { - this->controller[dev].have_ft = ( retstring == "yes" ? true : false ); + this->controller.at(dev).have_ft = ( retstring == "yes" ? true : false ); // add keyword to the extension for this channel -//TCB this->controller[dev].info.systemkeys.add_key( "FT", this->controller[dev].have_ft, "frame transfer used", EXT, chan ); +//TCB this->controller.at(dev).info.systemkeys.add_key( "FT", this->controller.at(dev).have_ft, "frame transfer used", EXT, chan ); } // In any case, return the current state // - retstring = ( this->controller[dev].have_ft ? "yes" : "no" ); + retstring = ( this->controller.at(dev).have_ft ? "yes" : "no" ); return( NO_ERROR ); } @@ -4751,7 +4822,8 @@ logwrite(function, message.str()); retstring.append( " Specify from { " ); message.str(""); for ( const auto &con : this->controller ) { - if ( con.second.inactive ) continue; // skip controllers flagged as inactive + // skip unconfigured and inactive controllers + if (!con.second.configured || !con.second.active) continue; message << con.second.channel << " "; } message << "}\n"; @@ -4759,7 +4831,8 @@ logwrite(function, message.str()); retstring.append( " or from { " ); message.str(""); for ( const auto &con : this->controller ) { - if ( con.second.inactive ) continue; // skip controllers flagged as inactive + // skip unconfigured and inactive controllers + if (!con.second.configured || !con.second.active) continue; message << con.second.devnum << " "; } message << "}\n"; @@ -4789,7 +4862,17 @@ logwrite(function, message.str()); std::vector tokens; Tokenize( retstring, tokens, " " ); - Controller* pcontroller = &this->controller[dev]; + // Just need to get a configured controller here, + // it doesn't need to be active or connected at this stage. + // This allows setting up image size prior to connecting, which is done + // when the config file is read. + // + Controller* pcontroller = this->get_controller(dev); + + if (!pcontroller) { + logwrite(function, "ERROR: controller not available for channel "+chan); + return ERROR; + } int spat=-1, spec=-1, osspat=-1, osspec=-1, binspat=-1, binspec=-1; // start by loading the values in the class @@ -4887,7 +4970,7 @@ logwrite(function, message.str()); pcontroller->skipcols = cols % bincols; pcontroller->skiprows = rows % binrows; -// message.str(""); message << "[DEBUG] skipcols=" << this->controller[dev].skipcols << " skiprows=" << this->controller[dev].skiprows; +// message.str(""); message << "[DEBUG] skipcols=" << this->controller.at(dev).skipcols << " skiprows=" << this->controller.at(dev).skiprows; // logwrite( function, message.str() ); cols -= pcontroller->skipcols; @@ -4923,8 +5006,8 @@ logwrite(function, message.str()); // message.str(""); // message << "[DEBUG] new binned values before set_axes() to re-calculate:" -// << " detector_pixels[" << _COL_ << "]=" << this->controller[dev].info.detector_pixels[_COL_] -// << " detector_pixels[" << _ROW_ << "]=" << this->controller[dev].info.detector_pixels[_ROW_]; +// << " detector_pixels[" << _COL_ << "]=" << this->controller.at(dev).info.detector_pixels[_COL_] +// << " detector_pixels[" << _ROW_ << "]=" << this->controller.at(dev).info.detector_pixels[_ROW_]; // logwrite(function, message.str()); // *** This is where the binned-image dimensions are re-calculated *** @@ -5068,7 +5151,8 @@ logwrite(function, message.str()); retstring.append( " Specify from { " ); message.str(""); for ( const auto &con : this->controller ) { - if ( con.second.inactive ) continue; // skip controllers flagged as inactive + // skip unconfigured and inactive controllers + if (!con.second.configured || !con.second.active) continue; message << con.second.channel << " "; } message << "}\n"; @@ -5076,7 +5160,8 @@ logwrite(function, message.str()); retstring.append( " or from { " ); message.str(""); for ( const auto &con : this->controller ) { - if ( con.second.inactive ) continue; // skip controllers flagged as inactive + // skip unconfigured and inactive controllers + if (!con.second.configured || !con.second.active) continue; message << con.second.devnum << " "; } message << "}\n"; @@ -5125,8 +5210,8 @@ logwrite(function, message.str()); return( ERROR ); } -// message.str(""); message << "[DEBUG] " << this->controller[dev].devname -// << " chan " << this->controller[dev].channel << " rows:" << setrows << " cols:" << setcols; +// message.str(""); message << "[DEBUG] " << this->controller.at(dev).devname +// << " chan " << this->controller.at(dev).channel << " rows:" << setrows << " cols:" << setcols; // logwrite( function, message.str() ); // Write the geometry to the selected controllers @@ -5157,13 +5242,13 @@ logwrite(function, message.str()); // cmd.str(""); cmd << "RDM 0x400001 "; if ( this->do_native( dev, cmd.str(), getcols ) != NO_ERROR ) return ERROR; - this->controller[dev].cols = (uint32_t)parse_val( getcols.substr( getcols.find(":")+1 ) ); + this->controller.at(dev).cols = (uint32_t)parse_val( getcols.substr( getcols.find(":")+1 ) ); cmd.str(""); cmd << "RDM 0x400002 "; if ( this->do_native( dev, cmd.str(), getrows ) != NO_ERROR ) return ERROR; - this->controller[dev].rows = (uint32_t)parse_val( getrows.substr( getrows.find(":")+1 ) ); + this->controller.at(dev).rows = (uint32_t)parse_val( getrows.substr( getrows.find(":")+1 ) ); - rs << this->controller[dev].rows << " " << this->controller[dev].cols; + rs << this->controller.at(dev).rows << " " << this->controller.at(dev).cols; retstring = rs.str(); // Form the return string from the read-back rows cols @@ -5248,36 +5333,36 @@ logwrite(function, message.str()); // // When useframes is false, fpbcount=0, fcount=0, framenum=0 // - if ( ! server.controller[devnum].have_ft ) { + if ( ! server.controller.at(devnum).have_ft ) { server.exposure_pending( devnum, false ); // this also does the notify server.state_monitor_condition.notify_all(); #ifdef LOGLEVEL_DEBUG - message.str(""); message << "[DEBUG] dev " << devnum << " chan " << server.controller[devnum].channel << " exposure_pending=false"; + message.str(""); message << "[DEBUG] dev " << devnum << " chan " << server.controller.at(devnum).channel << " exposure_pending=false"; server.camera.async.enqueue_and_log( "CAMERAD", function, message.str() ); #endif } - server.controller[devnum].in_readout = false; + server.controller.at(devnum).in_readout = false; server.state_monitor_condition.notify_all(); #ifdef LOGLEVEL_DEBUG - message.str(""); message << "[DEBUG] dev " << devnum << " chan " << server.controller[devnum].channel << " in_readout=false"; + message.str(""); message << "[DEBUG] dev " << devnum << " chan " << server.controller.at(devnum).channel << " in_readout=false"; server.camera.async.enqueue_and_log( "CAMERAD", function, message.str() ); #endif - server.controller[devnum].frameinfo[fpbcount].tid = fpbcount; // create this index in the .frameinfo[] map - server.controller[devnum].frameinfo[fpbcount].buf = buffer; + server.controller.at(devnum).frameinfo[fpbcount].tid = fpbcount; // create this index in the .frameinfo[] map + server.controller.at(devnum).frameinfo[fpbcount].buf = buffer; /*** - if ( server.controller[devnum].frameinfo.count( fpbcount ) == 0 ) { // searches .frameinfo[] map for an index of fpbcount (none) - server.controller[devnum].frameinfo[ fpbcount ].tid = fpbcount; // create this index in the .frameinfo[] map - server.controller[devnum].frameinfo[ fpbcount ].buf = buffer; + if ( server.controller.at(devnum).frameinfo.count( fpbcount ) == 0 ) { // searches .frameinfo[] map for an index of fpbcount (none) + server.controller.at(devnum).frameinfo[ fpbcount ].tid = fpbcount; // create this index in the .frameinfo[] map + server.controller.at(devnum).frameinfo[ fpbcount ].buf = buffer; // If useframes is false then set framenum=0 because it doesn't mean anything, // otherwise set it to the fcount received from the API. // - server.controller[devnum].frameinfo[ fpbcount ].framenum = server.useframes ? fcount : 0; + server.controller.at(devnum).frameinfo[ fpbcount ].framenum = server.useframes ? fcount : 0; } else { // already have this fpbcount in .frameinfo[] map message.str(""); message << "ERROR: frame buffer overrun! Try allocating a larger buffer." - << " chan " << server.controller[devnum].channel; + << " chan " << server.controller.at(devnum).channel; logwrite( function, message.str() ); server.frameinfo_mutex.unlock(); return; @@ -5293,7 +5378,7 @@ logwrite(function, message.str()); double start_time = get_clock_time(); do { int this_frame = fcount; // the current frame - int last_frame = server.controller[devnum].get_framecount(); // the last frame that has been written by this device + int last_frame = server.controller.at(devnum).get_framecount(); // the last frame that has been written by this device int next_frame = last_frame + 1; // the next frame in line if (this_frame != next_frame) { // if the current frame is NOT the next in line then keep waiting usleep(5); @@ -5323,7 +5408,7 @@ logwrite(function, message.str()); message.str(""); message << "[DEBUG] calling server.write_frame for devnum=" << devnum << " fpbcount=" << fpbcount; logwrite(function, message.str()); #endif - error = server.write_frame( expbuf, devnum, server.controller[devnum].channel, fpbcount ); + error = server.write_frame( expbuf, devnum, server.controller.at(devnum).channel, fpbcount ); } else { logwrite(function, "aborted!"); @@ -5338,10 +5423,10 @@ logwrite(function, message.str()); // Erase it from the STL map so it's not seen again. // server.frameinfo_mutex.lock(); // protect access to frameinfo structure -// server.controller[devnum].frameinfo.erase( fpbcount ); +// server.controller.at(devnum).frameinfo.erase( fpbcount ); /*** 10/30/23 BOB - server.controller[devnum].close_file( server.camera.writekeys_when ); + server.controller.at(devnum).close_file( server.camera.writekeys_when ); ***/ server.frameinfo_mutex.unlock(); @@ -5404,7 +5489,7 @@ logwrite(function, message.str()); message.str(""); message << "NOTICE:exposure buffer " << expbuf << " waiting for frames from "; std::vector pending = interface.writes_pending[ expbuf ]; - for ( const auto &dev : pending ) message << interface.controller[dev].channel << " "; + for ( const auto &dev : pending ) message << interface.controller.at(dev).channel << " "; logwrite( function, message.str() ); // wait() will repeatedly call this lambda function before actually entering @@ -5430,6 +5515,176 @@ logwrite(function, message.str()); /***** AstroCam::Interface::FITS_handler ************************************/ + /***** AstroCam::Interface::camera_active_state *****************************/ + /** + * @brief set/get camera active state + * @details De-activating a configured channel turns off the biases and + * flagging it for non-use. This allows keeping a controller in + * a sort of standby condition, without having to reload waveforms + * and reconfigure. + * @param[in] args space-delimited list of one or more channel names {U G R I} + * @param[out] retstring activated|deactivated|error + * @param[in] cmd AstroCam::ActiveState:: {Activate|DeActivate|Query} + * @return ERROR|NO_ERROR + * + */ + long Interface::camera_active_state(const std::string &args, std::string &retstring, + AstroCam::ActiveState cmd) { + const std::string function("AstroCam::Interface::camera_active_state"); + std::vector _devnums; // local list of devnum(s) associated with chan(s) + std::string chan; // current channel + std::istringstream iss(args); + + // get channel name(s) from args and + // convert to a vector of devnum(s) + // + while (iss >> chan) { + // validate device number for that channel + int dev; + try { + dev = devnum_from_chan(chan); + } + // exceptions are not fatal, just don't add dev to the vector + catch(const std::exception &e) { + logwrite(function, "channel "+chan+": "+std::string(e.what())); + continue; + } + // push it into a vector + _devnums.push_back(dev); + } + + long error = NO_ERROR; + + retstring.clear(); + + // activate/deactivate each dev + // + for (const auto &dev : _devnums) { + // get pointer to the Controller object for this device + // it only needs to exist and be connected + auto pcontroller = this->get_controller(dev); + + // unavailable channels are not fatal, they just don't get used + if (!pcontroller) { + logwrite(function, "channel "+pcontroller->channel+" not configured"); + continue; + } + if (!pcontroller->configured || !pcontroller->connected) { + logwrite(function, "channel "+pcontroller->channel+" not connected"); + continue; + } + + // set or get active state as specified by cmd + switch (cmd) { + + // first set active flag, then send activation commands + case AstroCam::ActiveState::Activate: + if (pcontroller->active) break; // nothing to do if already activated + pcontroller->active = true; + // add this devnum to the active_devnums list + add_dev(dev, this->active_devnums); + // send the activation commands + for (const auto &cmd : pcontroller->activate_commands) { + error |= this->do_native(dev, std::string(cmd), retstring); + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + } + break; + + // first turn off power, then clear active flag + case AstroCam::ActiveState::DeActivate: + if (!pcontroller->active) break; // nothing to do if already deactivated + if ( (error=this->do_native(dev, std::string("POF"), retstring))==NO_ERROR ) { + pcontroller->active = false; + // remove this devnum from the active_devnums list + remove_dev(dev, this->active_devnums); + } + break; + + // do nothing + case AstroCam::ActiveState::Query: + default: + break; + } + + // build up return string + retstring += (pcontroller->channel+":"+(pcontroller->active ? "activated " : "deactivated ")); + } + + return error; + } + /***** AstroCam::Interface::camera_active_state *****************************/ + + + /***** AstroCam::Interface::get_controller **********************************/ + /** + * @brief helper function returns pointer to element of Controller map + * @details This will return a pointer if the requested device has been + * configured. + * @param[in] dev integer device number for map indexing + * @return pointer to Controller | nullptr + * + */ + Interface::Controller* Interface::get_controller(const int dev) { + const std::string function("AstroCam::Interface::get_controller"); + + auto it = this->controller.find(dev); + + if (it==this->controller.end()) { + logwrite(function, "controller for dev "+std::to_string(dev)+" not found"); + return nullptr; + } + + return &it->second; + } + /***** AstroCam::Interface::get_controller **********************************/ + + + /***** AstroCam::Interface::get_active_controller ***************************/ + /** + * @brief helper function returns pointer to element of Controller map + * @details This will return a pointer only if the requested device is + * active. It must be configured, connected, and active. + * @param[in] dev integer device number for map indexing + * @return pointer to Controller | nullptr + * + */ + Interface::Controller* Interface::get_active_controller(const int dev) { + const std::string function("AstroCam::Interface::get_active_controller"); + std::ostringstream oss; + + auto it = this->controller.find(dev); + + if (it==this->controller.end()) { + oss << "controller for dev " << dev << " not found"; + logwrite(function, oss.str()); + return nullptr; + } + + Controller &con = it->second; + + if (!con.configured) { + oss << "controller for dev " << dev << " not configured"; + logwrite(function, oss.str()); + return nullptr; + } + + if (!con.connected) { + oss << "controller for dev " << dev << " not connected"; + logwrite(function, oss.str()); + return nullptr; + } + + if (!con.active) { + oss << "controller for dev " << dev << " not active"; + logwrite(function, oss.str()); + return nullptr; + } + + return &con; + } + /***** AstroCam::Interface::get_active_controller ***************************/ + + /***** AstroCam::Interface::add_framethread *********************************/ /** * @brief call on thread creation to increment framethreadcount @@ -5481,34 +5736,6 @@ logwrite(function, message.str()); /***** AstroCam::Interface::init_framethread_count **************************/ - /***** AstroCam::Interface::Controller::Controller **************************/ - /** - * @brief class constructor - * - */ - Interface::Controller::Controller() { - this->workbuf = NULL; - this->workbuf_size = 0; - this->bufsize = 0; - this->rows=0; - this->cols=0; - this->devnum = 0; - this->framecount = 0; - this->pArcDev = NULL; - this->pCallback = NULL; - this->connected = false; - this->is_imsize_set = false; - this->firmwareloaded = false; - this->firmware = ""; - this->info.readout_name = ""; - this->info.readout_type = -1; - this->readout_arg = 0xBAD; - this->expinfo.resize( NUM_EXPBUF ); // vector of Camera::Information, one for each exposure buffer - this->info.exposure_unit = "msec"; // chaning unit not currently supported in ARC - } - /***** AstroCam::Interface::Controller::Controller **************************/ - - /***** AstroCam::Interface::Controller::logical_to_physical *****************/ /** * @brief translates logical (spat,spec) to physical (rows,cols) @@ -5820,8 +6047,10 @@ logwrite(function, message.str()); retstring.append( " shdelay ? | | test\n" ); retstring.append( " shutter ? | init | open | close | get | time | expose \n" ); retstring.append( " telem ? | collect | test | calibd | flexured | focusd | tcsd\n" ); + retstring.append( " canexpose\n" ); retstring.append( " isreadout\n" ); retstring.append( " pixelcount\n" ); + retstring.append( " devnums\n" ); return HELP; } @@ -5856,8 +6085,8 @@ logwrite(function, message.str()); } std::string msg; this->camera.set_fitstime( get_timestamp( ) ); // must set camera.fitstime first - if ( this->devnums.size() > 1 ) { - for (const auto &dev : this->devnums) { + if ( this->active_devnums.size() > 1 ) { + for (const auto &dev : this->active_devnums) { this->camera.get_fitsname( std::to_string(dev), msg ); // get the fitsname (by reference) this->camera.async.enqueue( msg ); // queue the fitsname logwrite( function, msg ); // log ths fitsname @@ -6174,22 +6403,26 @@ logwrite(function, message.str()); logwrite( function, message.str() ); retstring.append( message.str() ); retstring.append( "\n" ); + message.str(""); message << "can_expose=" << ( this->can_expose.load() ? "true" : "false" ); + logwrite( function, message.str() ); + retstring.append( message.str() ); retstring.append( "\n" ); + // this shows which channels have an exposure pending { std::vector pending = this->exposure_pending_list(); message.str(""); message << "exposures pending: "; - for ( const auto &dev : pending ) message << this->controller[dev].channel << " "; + for ( const auto &dev : pending ) message << this->controller.at(dev).channel << " "; logwrite( function, message.str() ); } retstring.append( message.str() ); retstring.append( "\n" ); message.str(""); message << "in readout: "; - for ( const auto &dev : this->devnums ) if ( this->controller[dev].in_readout ) message << this->controller[dev].channel << " "; + for ( const auto &dev : this->active_devnums ) if ( this->controller.at(dev).in_readout ) message << this->controller.at(dev).channel << " "; logwrite( function, message.str() ); retstring.append( message.str() ); retstring.append( "\n" ); message.str(""); message << "in frametransfer: "; - for ( const auto &dev : this->devnums ) if ( this->controller[dev].in_frametransfer ) message << this->controller[dev].channel << " "; + for ( const auto &dev : this->active_devnums ) if ( this->controller.at(dev).in_frametransfer ) message << this->controller.at(dev).channel << " "; logwrite( function, message.str() ); retstring.append( message.str() ); retstring.append( "\n" ); @@ -6225,27 +6458,27 @@ logwrite(function, message.str()); retstring.append( " Initiate the frame transfer waveforms on the indicated device.\n" ); retstring.append( " Supply dev# or chan from { " ); message.str(""); - for ( const auto &dd : this->devnums ) { + for ( const auto &dd : this->active_devnums ) { message << dd << " " << this->controller[dd].channel << " "; } - if ( this->devnums.empty() ) message << "no_devices_open "; + if ( this->active_devnums.empty() ) message << "no_active_devices "; message << "}"; retstring.append( message.str() ); return HELP; } - // must have at least one device open + // must have at least one active device open // - if ( this->devnums.empty() ) { + if ( this->active_devnums.empty() ) { logwrite( function, "ERROR: no open devices" ); retstring="no_devices"; return( ERROR ); } - // check if arg is a channel by comparing to all the defined channels in the devnums + // check if arg is a channel by comparing to all the defined channels in the active_devnums // int dev=-1; - for ( const auto &dd : this->devnums ) { + for ( const auto &dd : this->active_devnums ) { if ( this->controller[dd].channel == tokens[1] ) { dev = dd; break; @@ -6271,13 +6504,13 @@ logwrite(function, message.str()); // initiate the frame transfer waveforms // - this->controller[dev].pArcDev->frame_transfer( 0, - this->controller[dev].devnum, - this->controller[dev].info.axes[_ROW_], - this->controller[dev].info.axes[_COL_], - this->controller[dev].pCallback + this->controller.at(dev).pArcDev->frame_transfer( 0, + this->controller.at(dev).devnum, + this->controller.at(dev).info.axes[_ROW_], + this->controller.at(dev).info.axes[_COL_], + this->controller.at(dev).pCallback ); - retstring=this->controller[dev].channel; + retstring=this->controller.at(dev).channel; return( NO_ERROR ); } else @@ -6289,8 +6522,10 @@ logwrite(function, message.str()); if ( testname == "controller" ) { for ( auto &con : this->controller ) { - if ( con.second.inactive ) continue; // skip controllers flagged as inactive - message.str(""); message << "controller[" << con.second.devnum << "] connected:" << ( con.second.connected ? "T" : "F" ) + if (!con.second.configured) continue; // skip controllers not configured + message.str(""); message << "controller[" << con.second.devnum << "]" + << " connected:" << ( con.second.connected ? "T" : "F" ) + << " active:" << ( con.second.active ? "T" : "F" ) << " bufsize:" << con.second.get_bufsize() << " rows:" << con.second.rows << " cols:" << con.second.cols << " in_readout:" << ( con.second.in_readout ? "T" : "F" ) @@ -6388,6 +6623,16 @@ logwrite(function, message.str()); } else // ---------------------------------------------------- + // isready + // ---------------------------------------------------- + // am I ready for an exposure? + if (testname=="canexpose") { + retstring=(this->can_expose?"yes":"no"); + logwrite(function, retstring); + return NO_ERROR; + } + else + // ---------------------------------------------------- // isreadout // ---------------------------------------------------- // call ARC API isReadout() function directly @@ -6397,7 +6642,7 @@ logwrite(function, message.str()); retstring.clear(); try { for ( auto &con : this->controller ) { - if ( con.second.pArcDev != nullptr && con.second.connected ) { + if ( con.second.pArcDev != nullptr && con.second.connected && con.second.active ) { bool isreadout = con.second.pArcDev->isReadout(); error=NO_ERROR; retstring += (isreadout ? "T " : "F "); @@ -6423,7 +6668,7 @@ logwrite(function, message.str()); retstring="no_controllers"; try { for ( auto &con : this->controller ) { - if ( con.second.pArcDev != nullptr && con.second.connected ) { + if ( con.second.pArcDev != nullptr && con.second.connected && con.second.active ) { uint32_t pixelcount = con.second.pArcDev->getPixelCount(); error=NO_ERROR; retstring = std::to_string(pixelcount); @@ -6437,6 +6682,24 @@ logwrite(function, message.str()); return ERROR; } } + else + // ---------------------------------------------------- + // devnums + // ---------------------------------------------------- + // print the *_devnums vectors + // + if ( testname == "devnums" ) { + std::ostringstream oss; + oss << "configured="; + for (const auto &dev : this->configured_devnums) oss << dev << " "; + oss << " active="; + for (const auto &dev : this->active_devnums) oss << dev << " "; + oss << " connected="; + for (const auto &dev : this->connected_devnums) oss << dev << " "; + logwrite(function, oss.str()); + retstring=oss.str(); + return NO_ERROR; + } else { // ---------------------------------------------------- // invalid test name diff --git a/camerad/astrocam.h b/camerad/astrocam.h index 93e8cfe8..05999020 100644 --- a/camerad/astrocam.h +++ b/camerad/astrocam.h @@ -19,6 +19,9 @@ #include #include #include +#include +#include +#include #include "utilities.h" #include "common.h" @@ -46,6 +49,12 @@ namespace AstroCam { const int NUM_EXPBUF = 3; // number of exposure buffers + enum class ActiveState { + Activate, + DeActivate, + Query + }; + /** * ENUM list for each readout type */ @@ -556,6 +565,7 @@ namespace AstroCam { */ class Interface : public Camera::InterfaceBase { private: + zmqpp::context context; // int bufsize; int FITS_STRING_KEY; int FITS_DOUBLE_KEY; @@ -590,7 +600,8 @@ namespace AstroCam { int num_deinter_thr; //!< number of threads that can de-interlace an image int numdev; //!< total number of Arc devices detected in system std::vector configured_devnums; //!< vector of configured Arc devices (from camerad.cfg file) - std::vector devnums; //!< vector of all opened and connected devices + std::vector active_devnums; //!< vector of active Arc devices + std::vector connected_devnums; //!< vector of all open and connected devices std::mutex epend_mutex; std::vector exposures_pending; //!< vector of devnums that have a pending exposure (which needs to be stored) @@ -600,8 +611,54 @@ namespace AstroCam { void retval_to_string( std::uint32_t check_retval, std::string& retstring ); + inline void remove_dev(const int dev, std::vector &vec) { + auto it = std::find(vec.begin(), vec.end(), dev); + if ( it != vec.end() ) vec.erase(it); + } + + inline void add_dev(const int dev, std::vector &vec) { + auto it = std::find(vec.begin(), vec.end(), dev); + if ( it == vec.end() ) vec.push_back(dev); + } + public: - Interface(); + Interface() + : context(), + pci_cmd_num(0), + nexp(1), + nfpseq(1), + nframes(1), + numdev(0), + is_subscriber_thread_running(false), + should_subscriber_thread_run(false), + framethreadcount(0), + state_monitor_thread_running(false), + can_expose(true), // am I ready for the next exposure? + modeselected(false), + useframes(true) { + this->pFits.resize( NUM_EXPBUF ); // pre-allocate FITS_file object pointers for each exposure buffer + this->fitsinfo.resize( NUM_EXPBUF ); // pre-allocate Camera Information object pointers for each exposure buffer + this->writes_pending.resize( NUM_EXPBUF ); // pre-allocate writes_pending vector for each exposure buffer + + // Initialize STL map of Readout Amplifiers + // Indexed by amplifier name. + // The number is the argument for the Arc command to set this amplifier in the firmware. + // + // Format here is: { AMP_NAME, { ENUM_TYPE, ARC_ARG } } + // where AMP_NAME is the name of the readout amplifier, the index for this map + // ENUM_TYPE is an enum of type ReadoutType + // ARC_ARG is the ARC argument for the SOS command to select this readout source + // + this->readout_source.insert( { "U1", { U1, 0x5f5531 } } ); // "_U1" + this->readout_source.insert( { "L1", { L1, 0x5f4c31 } } ); // "_L1" + this->readout_source.insert( { "U2", { U2, 0x5f5532 } } ); // "_U2" + this->readout_source.insert( { "L2", { L2, 0x5f4c32 } } ); // "_L2" + this->readout_source.insert( { "SPLIT1", { SPLIT1, 0x5f5f31 } } ); // "__1" + this->readout_source.insert( { "SPLIT2", { SPLIT2, 0x5f5f32 } } ); // "__2" + this->readout_source.insert( { "QUAD", { QUAD, 0x414c4c } } ); // "ALL" + this->readout_source.insert( { "FT2", { FT2, 0x465432 } } ); // "FT2" -- frame transfer from 1->2, read split2 + this->readout_source.insert( { "FT1", { FT1, 0x465431 } } ); // "FT1" -- frame transfer from 2->1, read split1 + }; // Class Objects // @@ -609,6 +666,28 @@ namespace AstroCam { Camera::Camera camera; /// instantiate a Camera object Camera::Information camera_info; /// this is the main camera_info object + std::unique_ptr publisher; ///< publisher object + std::string publisher_address; ///< publish socket endpoint + std::string publisher_topic; ///< my default topic for publishing + std::unique_ptr subscriber; ///< subscriber object + std::string subscriber_address; ///< subscribe socket endpoint + std::vector subscriber_topics; ///< list of topics I subscribe to + std::atomic is_subscriber_thread_running; ///< is my subscriber thread running? + std::atomic should_subscriber_thread_run; ///< should my subscriber thread run? + std::unordered_map> topic_handlers; + ///< maps a handler function to each topic + + long init_pubsub(const std::initializer_list &topics={}) { + if (!subscriber) { + subscriber = std::make_unique(context, Common::PubSub::Mode::SUB); + } + return Common::PubSubHandler::init_pubsub(context, *this, topics); + } + void start_subscriber_thread() { Common::PubSubHandler::start_subscriber_thread(*this); } + void stop_subscriber_thread() { Common::PubSubHandler::stop_subscriber_thread(*this); } + void publish_snapshot(std::string* retstring=nullptr); + // vector of pointers to Camera Information containers, one for each exposure number // std::vector> fitsinfo; @@ -650,8 +729,8 @@ std::vector> fitsinfo; */ inline bool is_camera_idle( int dev ) { int num=0; - num += ( this->controller[dev].in_readout ? 1 : 0 ); - num += ( this->controller[dev].in_frametransfer ? 1 : 0 ); + num += ( this->controller.at(dev).in_readout ? 1 : 0 ); + num += ( this->controller.at(dev).in_frametransfer ? 1 : 0 ); std::lock_guard lock( this->epend_mutex ); num += this->exposures_pending.size(); return ( num>0 ? false : true ); @@ -659,9 +738,9 @@ std::vector> fitsinfo; inline bool is_camera_idle() { int num=0; - for ( auto dev : this->devnums ) { - num += ( this->controller[dev].in_readout ? 1 : 0 ); - num += ( this->controller[dev].in_frametransfer ? 1 : 0 ); + for ( auto dev : this->connected_devnums ) { + num += ( this->controller.at(dev).in_readout ? 1 : 0 ); + num += ( this->controller.at(dev).in_frametransfer ? 1 : 0 ); } std::lock_guard lock( this->epend_mutex ); num += this->exposures_pending.size(); @@ -670,7 +749,7 @@ std::vector> fitsinfo; inline bool in_readout() const { int num=0; - for ( auto dev : this->devnums ) { + for ( auto dev : this->connected_devnums ) { num += ( this->controller.at(dev).in_readout ? 1 : 0 ); num += ( this->controller.at(dev).in_frametransfer ? 1 : 0 ); } @@ -679,7 +758,7 @@ std::vector> fitsinfo; inline bool in_frametransfer() const { int num=0; - for ( auto dev : this->devnums ) { + for ( auto dev : this->connected_devnums ) { num += ( this->controller.at(dev).in_frametransfer ? 1 : 0 ); } return( num==0 ? false : true ); @@ -706,6 +785,7 @@ std::vector> fitsinfo; * exposure pending stuff * */ + std::atomic can_expose; std::condition_variable exposure_condition; std::mutex exposure_lock; static void dothread_monitor_exposure_pending( Interface &interface ); @@ -850,7 +930,28 @@ std::vector> fitsinfo; long workbuf_size; public: - Controller(); //!< class constructor + Controller() + : bufsize(0), + framecount(0), + workbuf_size(0), + info(), + workbuf(nullptr), + cols(0), + rows(0), + pArcDev(nullptr), + pCallback(nullptr), + connected(false), + configured(false), + active(false), + is_imsize_set(false), + firmwareloaded(false) + { + info.readout_type = -1; + readout_arg = 0xBAD; + expinfo.resize( NUM_EXPBUF ); // vector of Camera::Information, one for each exposure buffer + info.exposure_unit = "msec"; // chaning unit not currently supported in ARC + } + ~Controller() { }; //!< no deconstructor Camera::Information info; //!< camera info object for this controller @@ -910,7 +1011,8 @@ std::vector> fitsinfo; arc::gen3::CArcDevice* pArcDev; //!< arc::CController object pointer -- things pointed to by this are in the ARC API Callback* pCallback; //!< Callback class object must be pointer because the API functions are virtual bool connected; //!< true if controller connected (requires successful TDL command) - bool inactive; //!< set true to skip future use of controllers when unable to connect + bool configured; //!< set false to skip future use of controllers when unable to connect + bool active; //!< used to disable an otherwise-configured controller bool is_imsize_set; //!< has image_size been called after controller connected? bool firmwareloaded; //!< true if firmware is loaded, false otherwise std::string firmware; //!< name of firmware (.lod) file @@ -926,6 +1028,8 @@ std::vector> fitsinfo; std::atomic in_readout; //!< Is the controller currently reading out/transmitting pixels? std::atomic in_frametransfer; //!< Is the controller currently performing a frame transfer? + std::vector activate_commands; + // Functions // inline uint32_t get_bufsize() { return this->bufsize; }; @@ -978,12 +1082,16 @@ std::vector> fitsinfo; // Functions // + long camera_active_state(const std::string &args, std::string &retstring, AstroCam::ActiveState cmd); + Controller* get_controller(const int dev); + Controller* get_active_controller(const int dev); void exposure_progress(); void make_image_keywords( int dev ); long handle_json_message( std::string message_in ); long parse_spec_info( std::string args ); long parse_det_geometry( std::string args ); long parse_controller_config( std::string args ); + long parse_activate_commands(std::string args); int devnum_from_chan( const std::string &chan ); long extract_dev_chan( std::string args, int &dev, std::string &chan, std::string &retstring ); long test(std::string args, std::string &retstring); ///< test routines @@ -1044,8 +1152,8 @@ std::vector> fitsinfo; */ long do_native(std::string cmdstr); ///< selected or all open controllers long do_native(std::string cmdstr, std::string &retstring); ///< selected or all open controllers, return reply - long do_native(std::vector selectdev, std::string cmdstr); ///< specified by vector - long do_native(std::vector selectdev, std::string cmdstr, std::string &retstring); ///< specified by vector + long do_native(std::vector selectdev, std::string cmdstr); ///< specified by vector + long do_native(std::vector selectdev, std::string cmdstr, std::string &retstring); ///< specified by vector long do_native(int dev, std::string cmdstr, std::string &retstring); ///< specified by devnum long write_frame( int expbuf, int devnum, const std::string chan, int fpbcount ); diff --git a/camerad/camerad.cpp b/camerad/camerad.cpp index 23dda522..d995bf9a 100644 --- a/camerad/camerad.cpp +++ b/camerad/camerad.cpp @@ -178,6 +178,14 @@ int main(int argc, char **argv) { server.exit_cleanly(); } + if (server.init_pubsub()==ERROR) { + logwrite(function, "ERROR initializing publisher-subscriber handler"); + server.exit_cleanly(); + } + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + server.publish_snapshot(); + // 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. @@ -598,6 +606,18 @@ void doit(Network::TcpSocket &sock) { } #ifdef ASTROCAM else + if ( cmd == CAMERAD_ACTIVATE ) { + ret=server.camera_active_state(args, retstring, AstroCam::ActiveState::Activate); + } + else + if ( cmd == CAMERAD_DEACTIVATE ) { + ret=server.camera_active_state(args, retstring, AstroCam::ActiveState::DeActivate); + } + else + if ( cmd == CAMERAD_ISACTIVE ) { + ret=server.camera_active_state(args, retstring, AstroCam::ActiveState::Query); + } + else if ( cmd == CAMERAD_MODEXPTIME ) { ret = server.modify_exptime(args, retstring); } @@ -769,6 +789,21 @@ void doit(Network::TcpSocket &sock) { if ( cmd == CAMERAD_TEST ) { ret = server.test(args, retstring); } + else + if ( cmd == SNAPSHOT || cmd == TELEMREQUEST ) { + if ( args=="?" || args=="help" ) { + retstring=TELEMREQUEST+"\n"; + retstring.append( " Returns a serialized JSON message containing telemetry\n" ); + retstring.append( " information, terminated with \"EOF\\n\".\n" ); + ret=HELP; + } + else { + server.publish_snapshot( &retstring ); + if (retstring.empty()) retstring="(empty)"; + ret = JSON; + } + } + // Unknown commands generate an error // else { diff --git a/camerad/camerad.h b/camerad/camerad.h index f63fc4b8..d38dcde8 100644 --- a/camerad/camerad.h +++ b/camerad/camerad.h @@ -223,6 +223,23 @@ namespace Camera { applied++; } + // PUB_ENDPOINT + // + if (config.param[entry]=="PUB_ENDPOINT") { + this->publisher_address=config.arg[entry]; + this->publisher_topic=DAEMON_NAME; + this->camera.async.enqueue_and_log("CAMERAD", function, "CAMERAD:config:"+config.param[entry]+"="+config.arg[entry]); + applied++; + } + + // SUB_ENDPOINT + // + if (config.param[entry]=="SUB_ENDPOINT") { + this->subscriber_address=config.arg[entry]; + this->camera.async.enqueue_and_log("CAMERAD", function, "CAMERAD:config:"+config.param[entry]+"="+config.arg[entry]); + applied++; + } + // USERKEYS_PERSIST: should userkeys persist or be cleared after each exposure // if ( config.param[entry] == "USERKEYS_PERSIST" ) { diff --git a/common/camerad_commands.h b/common/camerad_commands.h index 2411bdc7..97ad2ce3 100644 --- a/common/camerad_commands.h +++ b/common/camerad_commands.h @@ -9,6 +9,7 @@ #pragma once const std::string CAMERAD_ABORT = "abort"; +const std::string CAMERAD_ACTIVATE = "activate"; const std::string CAMERAD_AUTODIR = "autodir"; const std::string CAMERAD_BASENAME = "basename"; const std::string CAMERAD_BIAS = "bias"; @@ -17,6 +18,7 @@ const std::string CAMERAD_BOI = "boi"; const std::string CAMERAD_BUFFER = "buffer"; const std::string CAMERAD_CLOSE = "close"; const std::string CAMERAD_CONFIG = "config"; +const std::string CAMERAD_DEACTIVATE = "deactivate"; const std::string CAMERAD_ECHO = "echo"; const std::string CAMERAD_EXPOSE = "expose"; const std::string CAMERAD_EXPTIME = "exptime"; @@ -28,6 +30,7 @@ const std::string CAMERAD_IMDIR = "imdir"; const std::string CAMERAD_IMNUM = "imnum"; const std::string CAMERAD_IMSIZE = "imsize"; const std::string CAMERAD_INTERFACE = "interface"; +const std::string CAMERAD_ISACTIVE = "isactive"; const std::string CAMERAD_ISOPEN = "isopen"; const std::string CAMERAD_KEY = "key"; const std::string CAMERAD_LOAD = "load"; @@ -48,6 +51,7 @@ const std::string CAMERAD_USEFRAMES = "useframes"; const std::string CAMERAD_WRITEKEYS = "writekeys"; const std::vector CAMERAD_SYNTAX = { CAMERAD_ABORT, + CAMERAD_ACTIVATE, CAMERAD_AUTODIR, CAMERAD_BASENAME, CAMERAD_BIAS, @@ -56,6 +60,7 @@ const std::vector CAMERAD_SYNTAX = { CAMERAD_BUFFER+" ? | | [ | ]", CAMERAD_CLOSE, CAMERAD_CONFIG, + CAMERAD_DEACTIVATE, CAMERAD_ECHO, CAMERAD_EXPOSE, CAMERAD_EXPTIME+" [ ]", @@ -67,6 +72,7 @@ const std::vector CAMERAD_SYNTAX = { CAMERAD_IMNUM, CAMERAD_IMSIZE+" ? | | [ ]", CAMERAD_INTERFACE, + CAMERAD_ISACTIVE, CAMERAD_ISOPEN, CAMERAD_KEY, CAMERAD_LOAD, diff --git a/common/message_keys.h b/common/message_keys.h new file mode 100644 index 00000000..0155cc8c --- /dev/null +++ b/common/message_keys.h @@ -0,0 +1,26 @@ +/** + * @file message_keys.h + * @brief contains keys for JSON messages + * @author David Hale + * + */ +#pragma once + +#include + +namespace Topic { + inline const std::string SNAPSHOT = "_snapshot"; + inline const std::string TCSD = "tcsd"; + inline const std::string TARGETINFO = "tcsd"; + inline const std::string SLITD = "slitd"; + inline const std::string CAMERAD = "camerad"; +} + +namespace Key { + + inline const std::string SOURCE = "source"; + + namespace Camerad { + inline const std::string READY = "ready"; + } +} diff --git a/sequencerd/sequence.cpp b/sequencerd/sequence.cpp index f321c6a7..2e2f9410 100644 --- a/sequencerd/sequence.cpp +++ b/sequencerd/sequence.cpp @@ -12,6 +12,7 @@ */ #include "sequence.h" +#include "message_keys.h" namespace Sequencer { @@ -40,6 +41,23 @@ namespace Sequencer { /***** Sequencer::Sequence::handletopic_snapshot ***************************/ + /***** Sequencer::Sequence::handletopic_camerad ****************************/ + /** + * @brief handles camerad telemetry + * @param[in] jmessage subscribed-received JSON message + * + */ + void Sequence::handletopic_camerad(const nlohmann::json &jmessage) { + if (jmessage.contains(Key::Camerad::READY)) { + int isready = jmessage[Key::Camerad::READY].get(); + this->can_expose.store(isready, std::memory_order_relaxed); + std::lock_guard lock(camerad_mtx); + this->camerad_cv.notify_all(); + } + } + /***** Sequencer::Sequence::handletopic_camerad ****************************/ + + /***** Sequencer::Sequence::publish_snapshot *******************************/ /** * @brief publishes snapshot of my telemetry @@ -723,6 +741,23 @@ namespace Sequencer { std::stringstream camcmd; long error=NO_ERROR; + // wait until camera is ready to expose + // + std::unique_lock lock(this->camerad_mtx); + if (!this->can_expose.load()) { + + this->async.enqueue_and_log(function, "NOTICE: waiting for camera to be ready to expose"); + + this->camerad_cv.wait( lock, [this]() { + return( this->can_expose.load() || this->cancel_flag.load() ); + } ); + + if (this->cancel_flag.load()) { + logwrite(function, "sequence cancelled"); + return NO_ERROR; + } + } + logwrite( function, "setting camera parameters"); ScopedState thr_state( thread_state_manager, Sequencer::THR_CAMERA_SET ); @@ -730,6 +765,35 @@ namespace Sequencer { this->thread_error_manager.set( THR_CAMERA_SET ); // assume the worse, clear on success + // Controller activate states stored in Sequencer::CalibrationTarget::calinfo map, + // indexed by name. Calibration targets use target.name for the index, or + // use "SCIENCE" index for all science targets. + // + std::ostringstream activechans, deactivechans; + const std::string calname = std::string(this->target.iscal ? this->target.name : "SCIENCE"); + const auto &calinfo = this->caltarget.get_info(calname); + + // build up lists of (de)activate chans + for (const auto &[chan,active] : calinfo.channel_active) { + (active ? activechans : deactivechans) << " " << chan; + } + + // send two commands, one for each + if (!activechans.str().empty()) { + std::string cmd = CAMERAD_ACTIVATE + activechans.str(); + if (this->camerad.send(cmd, reply)!=NO_ERROR) { + this->async.enqueue_and_log(function, "ERROR sending \""+cmd+"\": "+reply); + throw std::runtime_error("camera returned "+reply); + } + } + if (!deactivechans.str().empty()) { + std::string cmd = CAMERAD_DEACTIVATE + deactivechans.str(); + if (this->camerad.send(cmd, reply)!=NO_ERROR) { + this->async.enqueue_and_log(function, "ERROR sending \""+cmd+"\": "+reply); + throw std::runtime_error("camera returned "+reply); + } + } + // send the EXPTIME command to camerad // // Everywhere is maintained that exptime is specified in sec except @@ -2111,34 +2175,21 @@ namespace Sequencer { this->thread_error_manager.set( THR_CALIBRATOR_SET ); // assume the worse, clear on success - // name will index the caltarget map - // - std::string name(this->target.name); - - if ( this->target.iscal ) { - name = this->target.name; - this->async.enqueue_and_log( function, "NOTICE: configuring calibrator for "+name ); - } - else { - this->async.enqueue_and_log( function, "NOTICE: disabling calibrator for science target "+name ); - name="SCIENCE"; // override for indexing the map - } + const std::string calname = std::string(this->target.iscal ? this->target.name : "SCIENCE"); // Get the calibration target map. // This contains a map of all the required settings, indexed by target name. // - auto calinfo = this->caltarget.get_info(name); - if (!calinfo) { - logwrite( function, "ERROR unrecognized calibration target: "+name ); - throw std::runtime_error("unrecognized calibration target: "+name); - } + const auto &calinfo = this->caltarget.get_info(calname); + + this->async.enqueue_and_log(function, "NOTICE: configuring calibrator for "+calname); // set the calib door and cover // std::stringstream cmd; cmd.str(""); cmd << CALIBD_SET - << " door=" << ( calinfo->caldoor ? "open" : "close" ) - << " cover=" << ( calinfo->calcover ? "open" : "close" ); + << " door=" << ( calinfo.caldoor ? "open" : "close" ) + << " cover=" << ( calinfo.calcover ? "open" : "close" ); logwrite( function, "calib: "+cmd.str() ); if ( !this->cancel_flag.load() && @@ -2149,7 +2200,7 @@ namespace Sequencer { // set the internal calibration lamps // - for ( const auto &[lamp,state] : calinfo->lamp ) { + for ( const auto &[lamp,state] : calinfo.lamp ) { if ( this->cancel_flag.load() ) break; cmd.str(""); cmd << lamp << " " << (state?"on":"off"); message.str(""); message << "power " << cmd.str(); @@ -2165,7 +2216,7 @@ namespace Sequencer { // // // set the dome lamps // // -// for ( const auto &[lamp,state] : calinfo->domelamp ) { +// for ( const auto &[lamp,state] : calinfo.domelamp ) { // if ( this->cancel_flag.load() ) break; // cmd.str(""); cmd << TCSD_NATIVE << " NPS " << lamp << " " << (state?1:0); // if ( this->tcsd.command( cmd.str() ) != NO_ERROR ) { @@ -2176,7 +2227,7 @@ namespace Sequencer { // set the lamp modulators // - for ( const auto &[mod,state] : calinfo->lampmod ) { + for ( const auto &[mod,state] : calinfo.lampmod ) { if ( this->cancel_flag.load() ) break; cmd.str(""); cmd << CALIBD_LAMPMOD << " " << mod << " " << (state?1:0) << " 1000"; if ( this->calibd.command( cmd.str() ) != NO_ERROR ) { @@ -3976,6 +4027,10 @@ namespace Sequencer { message.str(""); message << "NOTICE: daemons not ready: " << this->daemon_manager.get_cleared_states(); this->async.enqueue_and_log( function, message.str() ); + retstring.append( message.str() ); retstring.append( "\n" ); + + message.str(""); message << "NOTICE: camera ready to expose: " << (this->can_expose.load() ? "yes" : "no"); + this->async.enqueue_and_log( function, message.str() ); retstring.append( message.str() ); error = NO_ERROR; diff --git a/sequencerd/sequence.h b/sequencerd/sequence.h index 8703c1d6..c931cce2 100644 --- a/sequencerd/sequence.h +++ b/sequencerd/sequence.h @@ -33,6 +33,7 @@ #include "slitd_commands.h" #include "tcsd_commands.h" #include "sequencerd_commands.h" +#include "message_keys.h" #include "tcs_constants.h" #include "acam_interface_shared.h" @@ -280,6 +281,7 @@ namespace Sequencer { private: zmqpp::context context; bool ready_to_start; ///< set on nightly startup success, used to return seqstate to READY after an abort + std::atomic can_expose; std::atomic is_science_frame_transfer; ///< is frame transfer enabled for science cameras std::atomic notify_tcs_next_target; ///< notify TCS of next target when remaining time within TCS_PREAUTH_TIME std::atomic arm_readout_flag; ///< @@ -307,6 +309,7 @@ namespace Sequencer { Sequence() : context(), ready_to_start(false), + can_expose(false), is_science_frame_transfer(false), notify_tcs_next_target(false), arm_readout_flag(false), @@ -334,8 +337,10 @@ namespace Sequencer { daemon_manager.set_callback([this](const std::bitset& states) { broadcast_daemonstate(); }); topic_handlers = { - { "_snapshot", std::function( - [this](const nlohmann::json &msg) { handletopic_snapshot(msg); } ) } + { Topic::SNAPSHOT, std::function( + [this](const nlohmann::json &msg) { handletopic_snapshot(msg); } ) }, + { Topic::CAMERAD, std::function( + [this](const nlohmann::json &msg) { handletopic_camerad(msg); } ) } }; } @@ -379,6 +384,8 @@ namespace Sequencer { /// std::mutex tcs_ontarget_mtx; /// std::condition_variable tcs_ontarget_cv; + std::mutex camerad_mtx; + std::condition_variable camerad_cv; std::mutex wait_mtx; std::condition_variable cv; std::mutex cv_mutex; @@ -448,6 +455,7 @@ namespace Sequencer { void stop_subscriber_thread() { Common::PubSubHandler::stop_subscriber_thread(*this); } void handletopic_snapshot( const nlohmann::json &jmessage ); + void handletopic_camerad( const nlohmann::json &jmessage ); void publish_snapshot(); void publish_snapshot(std::string &retstring); void publish_seqstate(); diff --git a/sequencerd/sequencer_interface.cpp b/sequencerd/sequencer_interface.cpp index e85740d9..810feb63 100644 --- a/sequencerd/sequencer_interface.cpp +++ b/sequencerd/sequencer_interface.cpp @@ -689,7 +689,7 @@ namespace Sequencer { // if ( ( this->ra_hms.empty() && ! this->dec_dms.empty() ) || ( ! this->ra_hms.empty() && this->dec_dms.empty() ) ) { - message.str(""); message << "ERROR cannot have only RA or only DEC empty. both must be empty or filled"; + message << "ERROR cannot have only RA or only DEC empty. both must be empty or filled"; status = message.str(); logwrite( function, message.str() ); return ERROR; @@ -700,7 +700,7 @@ namespace Sequencer { if ( ! this->ra_hms.empty() ) { double _rah = radec_to_decimal( this->ra_hms ); // convert RA from HH:MM:SS.s to decimal hours if ( _rah < 0 ) { - message.str(""); message << "ERROR cannot have negative RA " << this->ra_hms; + message << "ERROR cannot have negative RA " << this->ra_hms; status = message.str(); logwrite( function, message.str() ); return ERROR; @@ -712,7 +712,7 @@ namespace Sequencer { if ( ! this->dec_dms.empty() ) { double _dec = radec_to_decimal( this->dec_dms ); // convert DEC from DD:MM:SS.s to decimal degrees if ( _dec < -90.0 || _dec > 90.0 ) { - message.str(""); message << "ERROR declination " << this->dec_dms << " outside range {-90:+90}"; + message << "ERROR declination " << this->dec_dms << " outside range {-90:+90}"; status = message.str(); logwrite( function, message.str() ); return ERROR; @@ -727,14 +727,18 @@ namespace Sequencer { else { if ( ! caseCompareString( this->pointmode, Acam::POINTMODE_ACAM ) && ! caseCompareString( this->pointmode, Acam::POINTMODE_SLIT ) ) { - message.str(""); message << "ERROR invalid pointmode \"" << this->pointmode << "\": must be { " - << Acam::POINTMODE_ACAM << " " << Acam::POINTMODE_SLIT << " }"; + message << "ERROR invalid pointmode \"" << this->pointmode << "\": must be { " + << Acam::POINTMODE_ACAM << " " << Acam::POINTMODE_SLIT << " }"; status = message.str(); logwrite( function, message.str() ); return ERROR; } } + // number of exposures must be >= 1 + // + if (this->nexp <= 0) this->nexp=1; + return NO_ERROR; } /***** Sequencer::TargetInfo::target_qc_check *******************************/ @@ -939,15 +943,26 @@ namespace Sequencer { */ long CalibrationTarget::configure( const std::string &args ) { const std::string function("Sequencer::CalibrationTarget::configure"); - std::stringstream message; std::vector tokens; + // helpers + auto on_off = [](const std::string &s) { + if (s=="on") return true; + if (s=="off") return false; + throw std::runtime_error("expected on|off but got '"+s+"'"); + }; + auto open_close = [](const std::string &s) { + if (s=="open") return true; + if (s=="close") return false; + throw std::runtime_error("expected open|close but got '"+s+"'"); + }; + auto size = Tokenize( args, tokens, " \t" ); - // there must be 15 args. see cfg file for complete description - if ( size != 15 ) { - message << "ERROR expected 15 but received " << size << " parameters"; - logwrite( function, message.str() ); + // there must be 19 args. see cfg file for complete description + if ( size != 19 ) { + logwrite(function, "ERROR bad config file. expected 19 but received " + +std::to_string(size)+" parameters"); return ERROR; } @@ -956,64 +971,43 @@ namespace Sequencer { std::string name(tokens[0]); if ( name.empty() || ( name != "SCIENCE" && name.compare(0, 4, "CAL_") !=0 ) ) { - message << "ERROR invalid calibration target name \"" << name << "\": must be \"SCIENCE\" or start with \"CAL_\" "; + logwrite(function, "ERROR invalid calibration target name '"+name + +"': must be 'SCIENCE' or start with 'CAL_' "); return ERROR; } - this->calmap[name].name = name; - // token[1] = caldoor - if ( tokens[1].empty() || - ( tokens[1].find("open")==std::string::npos && tokens[1].find("close") ) ) { - message << "ERROR invalid caldoor \"" << tokens[1] << "\": expected {open|close}"; - return ERROR; - } - this->calmap[name].caldoor = (tokens.at(1).find("open")==0); + // create map and get a reference to use for the remaining values + calinfo_t &info = this->calmap[name]; + info.name = name; - // token[2] = calcover - if ( tokens[2].empty() || - ( tokens[2].find("open")==std::string::npos && tokens[2].find("close") ) ) { - message << "ERROR invalid calcover \"" << tokens[2] << "\": expected {open|close}"; - return ERROR; - } - this->calmap[name].calcover = (tokens.at(2).find("open")==0); + try { + // tokens 1-2 are caldoor and calcover + info.caldoor = open_close(tokens.at(1)); + info.calcover = open_close(tokens.at(2)); - // tokens[3:6] = LAMPTHAR, LAMPFEAR, LAMPBLUC, LAMPREDC - int n=3; // incremental token counter used for the following groups - for ( const auto &lamp : this->lampnames ) { - if ( tokens[n].empty() || - ( tokens[n].find("on")==std::string::npos && tokens[n].find("off")==std::string::npos ) ) { - message << "ERROR invalid state \"" << tokens[n] << "\" for " << lamp << ": expected {on|off}"; - logwrite( function, message.str() ); - return ERROR; + // tokens 3-6 are the channel active states, indexed by channel name + for (size_t i=0; i < 4; i++) { + info.channel_active[chans.at(i)] = on_off(tokens.at(3+i)); } - this->calmap[name].lamp[lamp] = (tokens[n].find("on")==0); - n++; - } - // tokens[7:8] = domelamps - // i indexes domelampnames vector {0,1} - // j indexes domelamp map {1,2} - for ( int i=0,j=1; j<=2; i++,j++ ) { - if ( tokens[n].empty() || - ( tokens[n].find("on")==std::string::npos && tokens[n].find("off")==std::string::npos ) ) { - message << "ERROR invalid state \"" << tokens[n] << "\" for " << domelampnames[i] << ": expected {on|off}"; - logwrite( function, message.str() ); - return ERROR; + // tokens 7-10 are lamp states LAMPTHAR, LAMPFEAR, LAMPBLUC, LAMPREDC + for (size_t i=0; i < 4; i++) { + info.lamp[lampnames.at(i)] = on_off(tokens.at(7+i)); } - this->calmap[name].domelamp[j] = (tokens[n].find("on")==0); - n++; - } - // tokens[0:14] = lampmod{1:6} - for ( int i=1; i<=6; i++ ) { - if ( tokens[n].empty() || - ( tokens[n].find("on")==std::string::npos && tokens[n].find("off")==std::string::npos ) ) { - message << "ERROR invalid state \"" << tokens[n] << "\" for lampmod" << n << ": expected {on|off}"; - logwrite( function, message.str() ); - return ERROR; + // tokens 11-12 are dome lamps + for (size_t i=0; i < 2; i++) { + info.domelamp[i] = on_off(tokens.at(11+i)); + } + + // tokens 13-19 + for (size_t i=0; i<6; i++) { + info.lampmod[i] = on_off(tokens.at(13+i)); } - this->calmap[name].lampmod[i] = (tokens[n].find("on")==0); - n++; + } + catch (const std::exception &e) { + logwrite(function, "ERROR: "+std::string(e.what())); + return ERROR; } return NO_ERROR; diff --git a/sequencerd/sequencer_interface.h b/sequencerd/sequencer_interface.h index a90dcced..f4569e7c 100644 --- a/sequencerd/sequencer_interface.h +++ b/sequencerd/sequencer_interface.h @@ -132,12 +132,14 @@ namespace Sequencer { class CalibrationTarget { public: CalibrationTarget() : + chans { "U", "G", "R", "I" }, lampnames { "LAMPTHAR", "LAMPFEAR", "LAMPBLUC", "LAMPREDC" }, domelampnames { "LOLAMP", "HILAMP" } { } ///< struct holds all calibration parameters not in the target database typedef struct { std::string name; // calibration target name + std::map channel_active; // true=on bool caldoor; // true=open bool calcover; // true=open std::map lamp; // true=on @@ -152,14 +154,17 @@ namespace Sequencer { const std::unordered_map &getmap() const { return calmap; }; ///< returns just the map contents for specified targetname key - const calinfo_t* get_info( const std::string &_name ) const { + const calinfo_t &get_info( const std::string &_name ) const { auto it = calmap.find(_name); - if ( it != calmap.end() ) return &it->second; - return nullptr; + if ( it == calmap.end() ) { + throw std::runtime_error("calinfo not found for: "+_name); + } + return it->second; } private: std::unordered_map calmap; + std::vector chans; std::vector lampnames; std::vector domelampnames; }; diff --git a/sequencerd/sequencerd.cpp b/sequencerd/sequencerd.cpp index a447ca09..841bb4f0 100644 --- a/sequencerd/sequencerd.cpp +++ b/sequencerd/sequencerd.cpp @@ -129,7 +129,7 @@ int main(int argc, char **argv) { // initialize the pub/sub handler // - if ( sequencerd.sequence.init_pubsub() == ERROR ) { + if ( sequencerd.sequence.init_pubsub( {"camerad"} ) == ERROR ) { logwrite(function, "ERROR initializing publisher-subscriber handler"); sequencerd.exit_cleanly(); } diff --git a/utils/utilities.h b/utils/utilities.h index 5308568d..7203c578 100644 --- a/utils/utilities.h +++ b/utils/utilities.h @@ -371,7 +371,7 @@ class BoolState { */ class PreciseTimer { private: - static const long max_short_sleep = 3000000; // units are microseconds + static inline constexpr long max_short_sleep = 3000000; // units are microseconds std::atomic should_hold; std::atomic on_hold;