From 393554e469fb3cf24d02e62b318c1ce25e4f0f33 Mon Sep 17 00:00:00 2001 From: David Hale Date: Thu, 29 Jan 2026 22:54:23 -0800 Subject: [PATCH 01/19] sequencer target_qc_check ensures nexp >= 1 --- sequencerd/sequencer_interface.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/sequencerd/sequencer_interface.cpp b/sequencerd/sequencer_interface.cpp index e85740d9..1550662d 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 *******************************/ From 52c37925598f45360b1b1b01759b5e84513a3212 Mon Sep 17 00:00:00 2001 From: David Hale Date: Thu, 29 Jan 2026 22:57:06 -0800 Subject: [PATCH 02/19] adds capability of disabling camerad controllers, while leaving them powered and waveforms loaded. The biases are turned off and commands are not sent while inactive. untested --- camerad/astrocam.cpp | 958 +++++++++++++++++++++++--------------- camerad/astrocam.h | 35 +- camerad/camerad.cpp | 12 + common/camerad_commands.h | 6 + 4 files changed, 620 insertions(+), 391 deletions(-) diff --git a/camerad/astrocam.cpp b/camerad/astrocam.cpp index 594dbf67..9328c144 100644 --- a/camerad/astrocam.cpp +++ b/camerad/astrocam.cpp @@ -159,25 +159,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; } @@ -253,7 +253,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 +272,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 +308,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 +351,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 +428,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 +473,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())); @@ -595,53 +595,59 @@ 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 + this->controller.at(dev).devnum = dev; // device number + this->controller.at(dev).channel = chan; // spectrographic channel + this->controller.at(dev).ccd_id = id; // CCD identifier + this->controller.at(dev).have_ft = ft; // frame transfer supported? + this->controller.at(dev).firmware = firm; // firmware file /* arc::gen3::CArcDevice* pArcDev = NULL; // create a generic CArcDevice pointer pArcDev = new arc::gen3::CArcPCI(); // point this at a new CArcPCI() object ///< TODO @todo implement PCIe option Callback* pCB = new Callback(); // create a pointer to a Callback() class object - 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.at(dev).pArcDev = pArcDev; // set the pointer to this object in the public vector + this->controller.at(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; + this->controller.at(dev).pArcDev = ( new arc::gen3::CArcPCI() ); // set the pointer to this object in the public vector + this->controller.at(dev).pCallback = ( new Callback() ); // set the pointer to this object in the public vector + this->controller.at(dev).devname = ""; // device name + this->controller.at(dev).connected = false; // not yet connected + this->controller.at(dev).is_imsize_set = false; // need to set image_size + this->controller.at(dev).firmwareloaded = false; // no firmware loaded + + this->controller.at(dev).info.readout_name = amp; + this->controller.at(dev).info.readout_type = readout_type; + this->controller.at(dev).readout_arg = readout_arg; this->exposure_pending( dev, false ); - this->controller[dev].in_readout = false; - this->controller[dev].in_frametransfer = false; + this->controller.at(dev).in_readout = false; + this->controller.at(dev).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 + this->controller.at(dev).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 + this->controller.at(dev).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 ); + this->controller.at(dev).info.systemkeys.add_key( "CCD_ID", id, "CCD identifier parse", EXT, chan ); + this->controller.at(dev).info.systemkeys.add_key( "FT", ft, "frame transfer used", EXT, chan ); + this->controller.at(dev).info.systemkeys.add_key( "AMP_ID", amp, "CCD readout amplifier ID", EXT, chan ); + this->controller.at(dev).info.systemkeys.add_key( "SPEC_ID", chan, "spectrograph channel", EXT, chan ); + this->controller.at(dev).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 ); @@ -660,7 +666,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; @@ -746,10 +752,10 @@ 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"); + << " .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 +790,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 +844,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 +883,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 +933,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 +965,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 +974,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 +1037,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 +1072,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 +1190,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() ) { @@ -1233,6 +1226,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 +1240,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 +1252,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 +1276,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 * */ @@ -1302,10 +1298,12 @@ namespace AstroCam { 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 +1320,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 +1366,8 @@ namespace AstroCam { // which will get built up from parse_controller_config() below. // this->configured_devnums.clear(); + this->active_devnums.clear(); + this->connected_devnums.clear(); // loop through the entries in the configuration file, stored in config class // @@ -1542,10 +1542,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 +1562,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 +1586,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 +1602,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 +1642,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 +1721,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 +1744,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 +1753,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 +1766,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 +1779,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 +2252,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 +2269,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 +2406,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 +2470,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 ); @@ -2544,16 +2554,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 +2581,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 +2626,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 +2664,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 +2673,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 +2740,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 +2764,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 +2782,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 +2822,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 +2830,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 +2871,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 +2890,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 +2915,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 +2940,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,7 +3367,7 @@ 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 + if (!con.second.configured) continue; // skip controllers not configured // But only use it if the device is open // if ( con.second.connected ) { @@ -3399,7 +3409,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 +3451,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 +3465,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 +3484,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 +3527,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 +3544,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 +3555,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 +3564,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 +3671,7 @@ 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 + if (!con.second.configured) continue; // skip controllers not configured message << con.second.channel << " "; } message << "}\n"; @@ -3669,7 +3679,7 @@ 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 + if (!con.second.configured) continue; // skip controllers not configured message << con.second.devnum << " "; } message << "}\n"; @@ -3791,7 +3801,7 @@ 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 + if (!con.second.configured) continue; // skip controllers not configured message << con.second.channel << " "; } message << "}\n"; @@ -3799,7 +3809,7 @@ 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 + if (!con.second.configured) continue; // skip controllers not configured message << con.second.devnum << " "; } message << "}\n"; @@ -3872,7 +3882,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 +3921,7 @@ 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 + if (!con.second.configured) continue; // skip controllers not configured message << con.second.channel << " "; } message << "}\n"; @@ -3919,7 +3929,7 @@ 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 + if (!con.second.configured) continue; // skip controllers not configured message << con.second.devnum << " "; } message << "}\n"; @@ -3957,13 +3967,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 +4175,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 +4189,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 +4205,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 +4233,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 +4252,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 ); @@ -4578,16 +4585,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 +4659,7 @@ 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 + if (!con.second.configured) continue; // skip controllers not configured message << con.second.channel << " "; } message << "}\n"; @@ -4660,7 +4667,7 @@ 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 + if (!con.second.configured) continue; // skip controllers not configured message << con.second.devnum << " "; } message << "}\n"; @@ -4673,8 +4680,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 +4712,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 +4758,7 @@ 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 + if (!con.second.configured) continue; // skip controllers not configured message << con.second.channel << " "; } message << "}\n"; @@ -4759,7 +4766,7 @@ 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 + if (!con.second.configured) continue; // skip controllers not configured message << con.second.devnum << " "; } message << "}\n"; @@ -4789,7 +4796,12 @@ logwrite(function, message.str()); std::vector tokens; Tokenize( retstring, tokens, " " ); - Controller* pcontroller = &this->controller[dev]; + Controller* pcontroller = this->get_active_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 +4899,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 +4935,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 +5080,7 @@ 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 + if (!con.second.configured) continue; // skip controllers not configured message << con.second.channel << " "; } message << "}\n"; @@ -5076,7 +5088,7 @@ 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 + if (!con.second.configured) continue; // skip controllers not configured message << con.second.devnum << " "; } message << "}\n"; @@ -5125,8 +5137,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 +5169,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 +5260,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 +5305,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 +5335,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 +5350,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 +5416,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 +5442,163 @@ logwrite(function, message.str()); /***** AstroCam::Interface::FITS_handler ************************************/ + /***** AstroCam::Interface::camera_active_state *****************************/ + /** + * @brief set/get camera active state + * + */ + 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::string chan; + std::istringstream iss(args); + + // get channel name from args + // + if (!(iss >> chan)) { + logwrite(function, "ERROR parsing args. expected "); + retstring="bad_argument"; + 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())); + retstring="bad_channel"; + return ERROR; + } + + // get pointer to the Controller object for this device + // it only needs to exist and be connected + // + auto pcontroller = this->get_controller(dev); + + if (!pcontroller) { + logwrite(function, "ERROR: channel "+chan+" not configured"); + retstring="missing_device"; + return ERROR; + } + if (!pcontroller->configured || !pcontroller->connected) { + logwrite(function, "ERROR: channel "+chan+" not connected"); + retstring="not_connected"; + return ERROR; + } + + long error = NO_ERROR; + + // set or get active state as specified by cmd + // + switch (cmd) { + + // first set active flag, then turn on power + case AstroCam::ActiveState::Activate: + pcontroller->active = true; + // add this devnum to the active_devnums list + add_dev(dev, this->active_devnums); + error=this->do_native( dev, std::string("PON"), retstring ); + break; + + // first turn off power, then clear active flag + case AstroCam::ActiveState::DeActivate: + 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; + } + + retstring = 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"); + 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; + + return &con; + } + /***** 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 @@ -5497,6 +5666,8 @@ logwrite(function, message.str()); this->pArcDev = NULL; this->pCallback = NULL; this->connected = false; + this->configured = false; + this->active = false; this->is_imsize_set = false; this->firmwareloaded = false; this->firmware = ""; @@ -5822,6 +5993,7 @@ logwrite(function, message.str()); retstring.append( " telem ? | collect | test | calibd | flexured | focusd | tcsd\n" ); retstring.append( " isreadout\n" ); retstring.append( " pixelcount\n" ); + retstring.append( " devnums\n" ); return HELP; } @@ -5856,8 +6028,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 @@ -6178,18 +6350,18 @@ logwrite(function, message.str()); { 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 +6397,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 +6443,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,7 +6461,7 @@ logwrite(function, message.str()); if ( testname == "controller" ) { for ( auto &con : this->controller ) { - if ( con.second.inactive ) continue; // skip controllers flagged as inactive + if (!con.second.configured) continue; // skip controllers not configured message.str(""); message << "controller[" << con.second.devnum << "] connected:" << ( con.second.connected ? "T" : "F" ) << " bufsize:" << con.second.get_bufsize() << " rows:" << con.second.rows << " cols:" << con.second.cols @@ -6437,6 +6609,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..9866b6ea 100644 --- a/camerad/astrocam.h +++ b/camerad/astrocam.h @@ -46,6 +46,12 @@ namespace AstroCam { const int NUM_EXPBUF = 3; // number of exposure buffers + enum class ActiveState { + Activate, + DeActivate, + Query + }; + /** * ENUM list for each readout type */ @@ -590,7 +596,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,6 +607,16 @@ 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(); @@ -659,7 +676,7 @@ std::vector> fitsinfo; inline bool is_camera_idle() { int num=0; - for ( auto dev : this->devnums ) { + for ( auto dev : this->connected_devnums ) { num += ( this->controller[dev].in_readout ? 1 : 0 ); num += ( this->controller[dev].in_frametransfer ? 1 : 0 ); } @@ -670,7 +687,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 +696,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 ); @@ -910,7 +927,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 @@ -978,6 +996,9 @@ 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 ); @@ -1044,8 +1065,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..7425ce58 100644 --- a/camerad/camerad.cpp +++ b/camerad/camerad.cpp @@ -598,6 +598,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); } 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, From 738cf728d2a62bb7f56ca7e20c7d76846b6d1b04 Mon Sep 17 00:00:00 2001 From: David Hale Date: Fri, 30 Jan 2026 00:58:25 -0800 Subject: [PATCH 03/19] fixes bug: controller[dev] map wasn't being created --- camerad/astrocam.cpp | 149 +++++++++++++++++-------------------------- camerad/astrocam.h | 31 +++++++-- 2 files changed, 84 insertions(+), 96 deletions(-) diff --git a/camerad/astrocam.cpp b/camerad/astrocam.cpp index 9328c144..3a840e82 100644 --- a/camerad/astrocam.cpp +++ b/camerad/astrocam.cpp @@ -518,55 +518,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 + >> firm + >> amp + >> ft)) { + 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,50 +584,54 @@ namespace AstroCam { // // The first four come from the config file, the rest are defaults // - this->controller.at(dev).devnum = dev; // device number - this->controller.at(dev).channel = chan; // spectrographic channel - this->controller.at(dev).ccd_id = id; // CCD identifier - this->controller.at(dev).have_ft = ft; // frame transfer supported? - this->controller.at(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 pArcDev = new arc::gen3::CArcPCI(); // point this at a new CArcPCI() object ///< TODO @todo implement PCIe option Callback* pCB = new Callback(); // create a pointer to a Callback() class object - this->controller.at(dev).pArcDev = pArcDev; // set the pointer to this object in the public vector - this->controller.at(dev).pCallback = pCB; // set the pointer to this object in the public vector + 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.at(dev).pArcDev = ( new arc::gen3::CArcPCI() ); // set the pointer to this object in the public vector - this->controller.at(dev).pCallback = ( new Callback() ); // set the pointer to this object in the public vector - this->controller.at(dev).devname = ""; // device name - this->controller.at(dev).connected = false; // not yet connected - this->controller.at(dev).is_imsize_set = false; // need to set image_size - this->controller.at(dev).firmwareloaded = false; // no firmware loaded + 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 - this->controller.at(dev).info.readout_name = amp; - this->controller.at(dev).info.readout_type = readout_type; - this->controller.at(dev).readout_arg = readout_arg; + con.info.readout_name = amp; + con.info.readout_type = readout_type; + con.readout_arg = readout_arg; this->exposure_pending( dev, false ); - this->controller.at(dev).in_readout = false; - this->controller.at(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 - this->controller.at(dev).configured = true; + 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 - this->controller.at(dev).active = true; + con.active = true; // Header keys specific to this controller are stored in the controller's extension // - this->controller.at(dev).info.systemkeys.add_key( "CCD_ID", id, "CCD identifier parse", EXT, chan ); - this->controller.at(dev).info.systemkeys.add_key( "FT", ft, "frame transfer used", EXT, chan ); - this->controller.at(dev).info.systemkeys.add_key( "AMP_ID", amp, "CCD readout amplifier ID", EXT, chan ); - this->controller.at(dev).info.systemkeys.add_key( "SPEC_ID", chan, "spectrograph channel", EXT, chan ); - this->controller.at(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.at(dev).pFits = pFits; // set the pointer to this object in the public vector @@ -1369,6 +1362,10 @@ namespace AstroCam { 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 // for (int entry=0; entry < this->config.n_entries; entry++) { @@ -5650,36 +5647,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->configured = false; - this->active = 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) diff --git a/camerad/astrocam.h b/camerad/astrocam.h index 9866b6ea..a62e87ab 100644 --- a/camerad/astrocam.h +++ b/camerad/astrocam.h @@ -667,8 +667,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 ); @@ -677,8 +677,8 @@ std::vector> fitsinfo; inline bool is_camera_idle() { int num=0; for ( auto dev : this->connected_devnums ) { - 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(); @@ -867,7 +867,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 From d98d199cc294c16bfe000043c735ad65d18477e2 Mon Sep 17 00:00:00 2001 From: David Hale Date: Fri, 30 Jan 2026 11:15:44 -0800 Subject: [PATCH 04/19] bug fix Interface::parse_controller_config --- camerad/astrocam.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/camerad/astrocam.cpp b/camerad/astrocam.cpp index 3a840e82..ec6ea72d 100644 --- a/camerad/astrocam.cpp +++ b/camerad/astrocam.cpp @@ -536,9 +536,9 @@ namespace AstroCam { if (!(iss >> dev >> chan >> id + >> ft >> firm - >> amp - >> ft)) { + >> amp)) { logwrite(function, "ERROR bad config. expected { PCIDEV CHAN ID FT FIRMWARE READOUT }"); return ERROR; } From 794cbe8efbf3d57556ef79b8d64448e8c82d869c Mon Sep 17 00:00:00 2001 From: David Hale Date: Fri, 30 Jan 2026 11:32:00 -0800 Subject: [PATCH 05/19] gets latest camerad.cfg.in from main --- Config/camerad.cfg.in | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Config/camerad.cfg.in b/Config/camerad.cfg.in index 630a4666..fe3e69bb 100644 --- a/Config/camerad.cfg.in +++ b/Config/camerad.cfg.in @@ -31,9 +31,9 @@ ARCSIM_NUMDEV=0 # #CONTROLLER=(0 I sg2 yes /home/developer/Software/DSP/lod/sg2-20241115.lod U2) #CONTROLLER=(1 R engg yes /home/developer/Software/DSP/lod/engg-20241115.lod U1) -CONTROLLER=(0 I sg2 yes /home/developer/Software/DSP/lod/28-1458/sg2.lod U2) -CONTROLLER=(1 R engg yes /home/developer/Software/DSP/lod/28-1458/engg.lod U1) -CONTROLLER=(2 G dbspr yes /home/developer/Software/DSP/lod/51209-1651/dbspr.lod U2) +CONTROLLER=(0 I sg2 yes /home/developer/Software/DSP/lod/260127-1318/sg2.lod U2) +CONTROLLER=(1 R engg yes /home/developer/Software/DSP/lod/260127-1318/engg.lod U1) +CONTROLLER=(2 G dbspr yes /home/developer/Software/DSP/lod/260127-1318/dbspr.lod L1) CONTROLLER=(3 U dbspb no /home/developer/Software/DSP/DBSP-blue/tim.lod U1) # ----------------------------------------------------------------------------- From 54b589effad2a638b82aa0133408ff9441bbc26a Mon Sep 17 00:00:00 2001 From: David Hale Date: Fri, 30 Jan 2026 11:50:42 -0800 Subject: [PATCH 06/19] fixes bug in Interface::image_size --- camerad/astrocam.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/camerad/astrocam.cpp b/camerad/astrocam.cpp index ec6ea72d..000f1ca3 100644 --- a/camerad/astrocam.cpp +++ b/camerad/astrocam.cpp @@ -4793,7 +4793,12 @@ logwrite(function, message.str()); std::vector tokens; Tokenize( retstring, tokens, " " ); - Controller* pcontroller = this->get_active_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); From 6c0175eab34d4d5a56ec65ff33ce99ed3c72511c Mon Sep 17 00:00:00 2001 From: David Hale Date: Fri, 30 Jan 2026 12:07:00 -0800 Subject: [PATCH 07/19] additional checks for inactive controllers --- camerad/astrocam.cpp | 58 +++++++++++++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/camerad/astrocam.cpp b/camerad/astrocam.cpp index 000f1ca3..2359b931 100644 --- a/camerad/astrocam.cpp +++ b/camerad/astrocam.cpp @@ -744,8 +744,11 @@ 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 << " .configured=" << (con.second.configured?"T":"F") << " .active=" << (con.second.active?"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.configured) continue; // skip controllers not configured @@ -1284,7 +1287,7 @@ 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; @@ -3365,9 +3368,9 @@ namespace AstroCam { // for ( const auto &con : this->controller ) { if (!con.second.configured) continue; // skip controllers not configured - // But only use it if the device is open + // 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; @@ -3668,7 +3671,8 @@ for ( const auto &dev : selectdev ) { retstring.append( " Specify from { " ); message.str(""); for ( const auto &con : this->controller ) { - if (!con.second.configured) continue; // skip controllers not configured + // skip unconfigured and inactive controllers + if (!con.second.configured || !con.second.active) continue; message << con.second.channel << " "; } message << "}\n"; @@ -3676,7 +3680,8 @@ for ( const auto &dev : selectdev ) { retstring.append( " or from { " ); message.str(""); for ( const auto &con : this->controller ) { - if (!con.second.configured) continue; // skip controllers not configured + // skip unconfigured and inactive controllers + if (!con.second.configured || !con.second.active) continue; message << con.second.devnum << " "; } message << "}\n"; @@ -3798,7 +3803,8 @@ for ( const auto &dev : selectdev ) { retstring.append( " Specify from { " ); message.str(""); for ( const auto &con : this->controller ) { - if (!con.second.configured) continue; // skip controllers not configured + // skip unconfigured and inactive controllers + if (!con.second.configured || !con.second.active) continue; message << con.second.channel << " "; } message << "}\n"; @@ -3806,7 +3812,8 @@ for ( const auto &dev : selectdev ) { retstring.append( " or from { " ); message.str(""); for ( const auto &con : this->controller ) { - if (!con.second.configured) continue; // skip controllers not configured + // skip unconfigured and inactive controllers + if (!con.second.configured || !con.second.active) continue; message << con.second.devnum << " "; } message << "}\n"; @@ -3918,7 +3925,8 @@ for ( const auto &dev : selectdev ) { retstring.append( " Specify from { " ); message.str(""); for ( const auto &con : this->controller ) { - if (!con.second.configured) continue; // skip controllers not configured + // skip unconfigured and inactive controllers + if (!con.second.configured || !con.second.active) continue; message << con.second.channel << " "; } message << "}\n"; @@ -3926,7 +3934,8 @@ for ( const auto &dev : selectdev ) { retstring.append( " or from { " ); message.str(""); for ( const auto &con : this->controller ) { - if (!con.second.configured) continue; // skip controllers not configured + // skip unconfigured and inactive controllers + if (!con.second.configured || !con.second.active) continue; message << con.second.devnum << " "; } message << "}\n"; @@ -4289,6 +4298,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() ); @@ -4656,7 +4666,8 @@ logwrite(function, message.str()); retstring.append( " Specify from { " ); message.str(""); for ( const auto &con : this->controller ) { - if (!con.second.configured) continue; // skip controllers not configured + // skip unconfigured and inactive controllers + if (!con.second.configured || !con.second.active) continue; message << con.second.channel << " "; } message << "}\n"; @@ -4664,7 +4675,8 @@ logwrite(function, message.str()); retstring.append( " or from { " ); message.str(""); for ( const auto &con : this->controller ) { - if (!con.second.configured) continue; // skip controllers not configured + // skip unconfigured and inactive controllers + if (!con.second.configured || !con.second.active) continue; message << con.second.devnum << " "; } message << "}\n"; @@ -4755,7 +4767,8 @@ logwrite(function, message.str()); retstring.append( " Specify from { " ); message.str(""); for ( const auto &con : this->controller ) { - if (!con.second.configured) continue; // skip controllers not configured + // skip unconfigured and inactive controllers + if (!con.second.configured || !con.second.active) continue; message << con.second.channel << " "; } message << "}\n"; @@ -4763,7 +4776,8 @@ logwrite(function, message.str()); retstring.append( " or from { " ); message.str(""); for ( const auto &con : this->controller ) { - if (!con.second.configured) continue; // skip controllers not configured + // skip unconfigured and inactive controllers + if (!con.second.configured || !con.second.active) continue; message << con.second.devnum << " "; } message << "}\n"; @@ -5082,7 +5096,8 @@ logwrite(function, message.str()); retstring.append( " Specify from { " ); message.str(""); for ( const auto &con : this->controller ) { - if (!con.second.configured) continue; // skip controllers not configured + // skip unconfigured and inactive controllers + if (!con.second.configured || !con.second.active) continue; message << con.second.channel << " "; } message << "}\n"; @@ -5090,7 +5105,8 @@ logwrite(function, message.str()); retstring.append( " or from { " ); message.str(""); for ( const auto &con : this->controller ) { - if (!con.second.configured) continue; // skip controllers not configured + // skip unconfigured and inactive controllers + if (!con.second.configured || !con.second.active) continue; message << con.second.devnum << " "; } message << "}\n"; @@ -6434,7 +6450,9 @@ logwrite(function, message.str()); for ( auto &con : this->controller ) { if (!con.second.configured) continue; // skip controllers not configured - message.str(""); message << "controller[" << con.second.devnum << "] connected:" << ( con.second.connected ? "T" : "F" ) + 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" ) @@ -6541,7 +6559,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 "); @@ -6567,7 +6585,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); From 9882fbb92fbc5037febfba0a893ea727cf7fe840 Mon Sep 17 00:00:00 2001 From: David Hale Date: Fri, 30 Jan 2026 14:48:38 -0800 Subject: [PATCH 08/19] implements configurable activation commands per controller --- Config/camerad.cfg.in | 11 +++++++ camerad/astrocam.cpp | 75 ++++++++++++++++++++++++++++++++++++++----- camerad/astrocam.h | 3 ++ 3 files changed, 81 insertions(+), 8 deletions(-) diff --git a/Config/camerad.cfg.in b/Config/camerad.cfg.in index fe3e69bb..afd2d2b2 100644 --- a/Config/camerad.cfg.in +++ b/Config/camerad.cfg.in @@ -136,6 +136,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/camerad/astrocam.cpp b/camerad/astrocam.cpp index 2359b931..0118e80a 100644 --- a/camerad/astrocam.cpp +++ b/camerad/astrocam.cpp @@ -648,6 +648,57 @@ namespace AstroCam { /***** AstroCam::Interface::parse_controller_config *************************/ + /***** AstroCam::Interface::parse_activate_commands *************************/ + /** + * @brief parses the ACTIVATE_COMMANDS keywords from config file + * @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 @@ -1390,6 +1441,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++; @@ -5513,12 +5572,16 @@ logwrite(function, message.str()); // switch (cmd) { - // first set active flag, then turn on power + // first set active flag, then send activation commands case AstroCam::ActiveState::Activate: pcontroller->active = true; // add this devnum to the active_devnums list add_dev(dev, this->active_devnums); - error=this->do_native( dev, std::string("PON"), retstring ); + // 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 @@ -5554,19 +5617,15 @@ logwrite(function, message.str()); */ Interface::Controller* Interface::get_controller(const int dev) { const std::string function("AstroCam::Interface::get_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()); + logwrite(function, "controller for dev "+std::to_string(dev)+" not found"); return nullptr; } - Controller &con = it->second; - - return &con; + return &it->second; } /***** AstroCam::Interface::get_controller **********************************/ diff --git a/camerad/astrocam.h b/camerad/astrocam.h index a62e87ab..19fdcd22 100644 --- a/camerad/astrocam.h +++ b/camerad/astrocam.h @@ -965,6 +965,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; }; @@ -1026,6 +1028,7 @@ std::vector> fitsinfo; 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 From c9870180854889d65648db21572675614f8069bc Mon Sep 17 00:00:00 2001 From: David Hale Date: Fri, 30 Jan 2026 14:56:35 -0800 Subject: [PATCH 09/19] updates camerad.cfg.in --- Config/camerad.cfg.in | 8 ++++---- DSP | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Config/camerad.cfg.in b/Config/camerad.cfg.in index afd2d2b2..9c32e5d0 100644 --- a/Config/camerad.cfg.in +++ b/Config/camerad.cfg.in @@ -142,10 +142,10 @@ USERKEYS_PERSIST=yes # 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") +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/DSP b/DSP index cfcf67af..4f4c6801 160000 --- a/DSP +++ b/DSP @@ -1 +1 @@ -Subproject commit cfcf67af5cd4d217077fff7b548191eb9de2021c +Subproject commit 4f4c6801322f52fb43347e8218552cb47829342a From 1f49d53d33a820df50061387bd3631bea5dbb612 Mon Sep 17 00:00:00 2001 From: David Hale Date: Fri, 30 Jan 2026 17:23:48 -0800 Subject: [PATCH 10/19] updates CAL_TARGET configuration table in sequencerd.cfg.in to include the activate states for the camera controllers, and updates CalibrationTarget::configure to read the new format --- Config/sequencerd.cfg.in | 30 ++++----- sequencerd/sequencer_interface.cpp | 98 ++++++++++++++---------------- sequencerd/sequencer_interface.h | 3 + 3 files changed, 63 insertions(+), 68 deletions(-) diff --git a/Config/sequencerd.cfg.in b/Config/sequencerd.cfg.in index 81c2f977..4592f094 100644 --- a/Config/sequencerd.cfg.in +++ b/Config/sequencerd.cfg.in @@ -162,26 +162,28 @@ 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_CONT open close on on on on on on on on off off off off off on off 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/sequencerd/sequencer_interface.cpp b/sequencerd/sequencer_interface.cpp index 1550662d..eecf09c8 100644 --- a/sequencerd/sequencer_interface.cpp +++ b/sequencerd/sequencer_interface.cpp @@ -943,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; } @@ -960,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); - - // 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; + 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 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.at(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+1)); } - 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..f3f7f83f 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 @@ -160,6 +162,7 @@ namespace Sequencer { private: std::unordered_map calmap; + std::vector chans; std::vector lampnames; std::vector domelampnames; }; From ea20b72338fa98b0e56bbc2bacc3c97cc0ae921c Mon Sep 17 00:00:00 2001 From: David Hale Date: Mon, 2 Feb 2026 08:21:25 -0800 Subject: [PATCH 11/19] adds minimum to compile with zmq pub-sub --- Config/camerad.cfg.in | 7 +++++ camerad/CMakeLists.txt | 7 +++++ camerad/astrocam.cpp | 50 +++-------------------------------- camerad/astrocam.h | 59 +++++++++++++++++++++++++++++++++++++++++- camerad/camerad.cpp | 8 ++++++ 5 files changed, 84 insertions(+), 47 deletions(-) diff --git a/Config/camerad.cfg.in b/Config/camerad.cfg.in index 9c32e5d0..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 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 0118e80a..2c2e6655 100644 --- a/camerad/astrocam.cpp +++ b/camerad/astrocam.cpp @@ -16,6 +16,10 @@ extern Camera::Server server; namespace AstroCam { + void Interface::publish_snapshot() { + } + + long NewAstroCam::new_expose( std::string nseq_in ) { logwrite( "NewAstroCam::new_expose", nseq_in ); return( NO_ERROR ); @@ -187,52 +191,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 diff --git a/camerad/astrocam.h b/camerad/astrocam.h index 19fdcd22..c275fb2e 100644 --- a/camerad/astrocam.h +++ b/camerad/astrocam.h @@ -19,6 +19,8 @@ #include #include #include +#include +#include #include "utilities.h" #include "common.h" @@ -562,6 +564,7 @@ namespace AstroCam { */ class Interface : public Camera::InterfaceBase { private: + zmqpp::context context; // int bufsize; int FITS_STRING_KEY; int FITS_DOUBLE_KEY; @@ -618,7 +621,42 @@ namespace AstroCam { } 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), + 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 // @@ -626,6 +664,25 @@ 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={}) { + 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(); + // vector of pointers to Camera Information containers, one for each exposure number // std::vector> fitsinfo; diff --git a/camerad/camerad.cpp b/camerad/camerad.cpp index 7425ce58..bb6509b6 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. From ddbece578b50136f441549db7288ce9d79b5fda5 Mon Sep 17 00:00:00 2001 From: David Hale Date: Mon, 2 Feb 2026 08:33:23 -0800 Subject: [PATCH 12/19] more to minimum pubsub --- camerad/camerad.h | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) 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" ) { From f96c6736483212ada5c96f0247c4b184d875f551 Mon Sep 17 00:00:00 2001 From: David Hale Date: Mon, 2 Feb 2026 11:02:01 -0800 Subject: [PATCH 13/19] bug fix AstroCam::Interface::init_pubsub --- camerad/astrocam.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/camerad/astrocam.h b/camerad/astrocam.h index c275fb2e..d024a3f4 100644 --- a/camerad/astrocam.h +++ b/camerad/astrocam.h @@ -677,6 +677,9 @@ namespace AstroCam { ///< 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); } From 9d1eaf322ab62697831b39ac5705deabd06db9cb Mon Sep 17 00:00:00 2001 From: David Hale Date: Mon, 2 Feb 2026 14:01:26 -0800 Subject: [PATCH 14/19] camerad now uses ZMQ to properly publish "is exposure ready" and sequencerd is subscribing to that and waiting on that to start an exposure --- camerad/astrocam.cpp | 35 ++++++++++++++++++++++++++++++++++- camerad/astrocam.h | 7 +++++-- camerad/camerad.cpp | 15 +++++++++++++++ common/message_keys.h | 26 ++++++++++++++++++++++++++ sequencerd/sequence.cpp | 38 ++++++++++++++++++++++++++++++++++++++ sequencerd/sequence.h | 12 ++++++++++-- 6 files changed, 128 insertions(+), 5 deletions(-) create mode 100644 common/message_keys.h diff --git a/camerad/astrocam.cpp b/camerad/astrocam.cpp index 2c2e6655..8e51b24d 100644 --- a/camerad/astrocam.cpp +++ b/camerad/astrocam.cpp @@ -12,12 +12,42 @@ */ #include "camerad.h" +#include "message_keys.h" + extern Camera::Server server; namespace AstroCam { - void Interface::publish_snapshot() { + /**** 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->is_exposure_ready.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 ) { @@ -2518,6 +2548,7 @@ namespace AstroCam { // Log this message once only // if ( interface.exposure_pending() ) { + interface.is_exposure_ready.store(false); interface.camera.async.enqueue_and_log( function, "NOTICE:exposure pending" ); interface.camera.async.enqueue( "CAMERAD:READY:false" ); } @@ -2549,6 +2580,8 @@ namespace AstroCam { interface.do_expose(interface.nexp); } else { + interface.is_exposure_ready.store(true); + interface.publish_snapshot(); interface.camera.async.enqueue_and_log( function, "NOTICE:ready for next exposure" ); interface.camera.async.enqueue( "CAMERAD:READY:true" ); } diff --git a/camerad/astrocam.h b/camerad/astrocam.h index d024a3f4..185f065b 100644 --- a/camerad/astrocam.h +++ b/camerad/astrocam.h @@ -21,6 +21,7 @@ #include #include #include +#include #include "utilities.h" #include "common.h" @@ -632,6 +633,7 @@ namespace AstroCam { should_subscriber_thread_run(false), framethreadcount(0), state_monitor_thread_running(false), + is_exposure_ready(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 @@ -656,7 +658,7 @@ namespace AstroCam { 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 // @@ -684,7 +686,7 @@ namespace AstroCam { } void start_subscriber_thread() { Common::PubSubHandler::start_subscriber_thread(*this); } void stop_subscriber_thread() { Common::PubSubHandler::stop_subscriber_thread(*this); } - void publish_snapshot(); + void publish_snapshot(std::string* retstring=nullptr); // vector of pointers to Camera Information containers, one for each exposure number // @@ -783,6 +785,7 @@ std::vector> fitsinfo; * exposure pending stuff * */ + std::atomic is_exposure_ready; std::condition_variable exposure_condition; std::mutex exposure_lock; static void dothread_monitor_exposure_pending( Interface &interface ); diff --git a/camerad/camerad.cpp b/camerad/camerad.cpp index bb6509b6..d995bf9a 100644 --- a/camerad/camerad.cpp +++ b/camerad/camerad.cpp @@ -789,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/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..f1e1bf2f 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->is_camera_ready.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 @@ -417,6 +435,26 @@ namespace Sequencer { logwrite( function, "sequencer running" ); + // wait until camera is ready to expose + // + if (!this->is_camera_ready.load()) { + + this->async.enqueue_and_log(function, "NOTICE: waiting for camera to be ready to expose"); + + std::unique_lock lock(this->camerad_mtx); + this->camerad_cv.wait( lock, [this]() { + return( this->is_camera_ready.load() || this->cancel_flag.load() ); + } ); + + if (this->cancel_flag.load()) { + logwrite(function, "sequence cancelled"); + return; + } + else { + logwrite(function, "camera ready to expose"); + } + } + // Get the next target from the database when single_obsid is empty // if ( this->single_obsid.empty() ) { diff --git a/sequencerd/sequence.h b/sequencerd/sequence.h index 8703c1d6..1bc3c65e 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 is_camera_ready; 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), + is_camera_ready(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(); From 780ef7fd38dae32fad4c561fc45d04cb4663183b Mon Sep 17 00:00:00 2001 From: David Hale Date: Mon, 2 Feb 2026 15:31:46 -0800 Subject: [PATCH 15/19] restore Config files from main branch --- Config/flexured.cfg.in | 8 ++++---- Config/focusd.cfg.in | 10 +++++----- Config/sequencerd.cfg.in | 6 +++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Config/flexured.cfg.in b/Config/flexured.cfg.in index 3b2aaa86..c71cbe23 100644 --- a/Config/flexured.cfg.in +++ b/Config/flexured.cfg.in @@ -35,11 +35,11 @@ MOTOR_CONTROLLER="I @IP_FLEXURE_I@ 50000 1 3" # 2 = X = spectral # 3 = Y = spatial # -MOTOR_AXIS="U 1 25 225 0 na" -MOTOR_AXIS="U 2 -1000 1000 0 na" -MOTOR_AXIS="U 3 -1000 1000 0 na" +MOTOR_AXIS="U 1 25 225 0 na 100.0" +MOTOR_AXIS="U 2 -1000 1000 0 na 0.0" +MOTOR_AXIS="U 3 -1000 1000 0 na 0.0" -MOTOR_AXIS="G 1 25 225 0 na 0.0" +MOTOR_AXIS="G 1 25 225 0 na 100.0" MOTOR_AXIS="G 2 -1000 1000 0 na 0.0" MOTOR_AXIS="G 3 -1000 1000 0 na 0.0" diff --git a/Config/focusd.cfg.in b/Config/focusd.cfg.in index 72d9d1a1..81212e20 100644 --- a/Config/focusd.cfg.in +++ b/Config/focusd.cfg.in @@ -30,7 +30,7 @@ EMULATOR_PORT=@FOCUSD_EMULATOR@ MOTOR_CONTROLLER="I @IP_ETS8P@ @ETS8P_FOCUS@ 1 1" MOTOR_CONTROLLER="R @IP_ETS8P@ @ETS8P_FOCUS@ 2 1" MOTOR_CONTROLLER="G @IP_ETS8P@ @ETS8P_FOCUS@ 3 1" -MOTOR_CONTROLLER="U @IP_ETS8P@ @ETS8P_FOCUS@ 4 1" +#MOTOR_CONTROLLER="U @IP_ETS8P@ @ETS8P_FOCUS@ 4 1" #MOTOR_CONTROLLER="I localhost @FOCUSD_EMULATOR@ 1 1" # emulator #MOTOR_CONTROLLER="R localhost @FOCUSD_EMULATOR@ 2 1" # emulator #MOTOR_CONTROLLER="G localhost @FOCUSD_EMULATOR@ 3 1" # emulator @@ -42,8 +42,8 @@ MOTOR_CONTROLLER="U @IP_ETS8P@ @ETS8P_FOCUS@ 4 1" # MOTOR_AXIS="I 1 0 7.6 0 ref 4.75" MOTOR_AXIS="R 1 0 6.7 0 ref 2.45" -MOTOR_AXIS="G 1 0 7.8 0 ref 3.20" -MOTOR_AXIS="U 1 0 7.8 0 ref 0.00" +MOTOR_AXIS="G 1 0 7.8 0 ref 3.35" +#MOTOR_AXIS="U 1 0 7.8 0 ref 0.00" # Specify nominal focus positions for each actuator # MOTOR_POS=" " @@ -55,5 +55,5 @@ MOTOR_AXIS="U 1 0 7.8 0 ref 0.00" # MOTOR_POS="I 1 0 4.75 nominal" MOTOR_POS="R 1 0 2.45 nominal" -MOTOR_POS="G 1 0 3.20 nominal" -MOTOR_POS="U 1 0 0.00 nominal" +MOTOR_POS="G 1 0 3.35 nominal" +#MOTOR_POS="U 1 0 0.00 nominal" diff --git a/Config/sequencerd.cfg.in b/Config/sequencerd.cfg.in index 4592f094..3a5a6901 100644 --- a/Config/sequencerd.cfg.in +++ b/Config/sequencerd.cfg.in @@ -120,12 +120,12 @@ VIRTUAL_SLITO_EXPOSE=3.0 # slit offset for science exposure # The names used here must have a matching NPS_PLUG name defined in power.cfg # POWER_SLIT=SLIT -POWER_CAMERA=(SHUTTER LEACH_R LEACH_I LEACH_U) +POWER_CAMERA=(SHUTTER LEACH_R LEACH_I LEACH_U LEACH_G) POWER_CALIB=(CAL_MOT LAMP_MOD) POWER_LAMP=(LAMPTHAR LAMPFEAR LAMPBLUC LAMPREDC) -POWER_FLEXURE=(FLEX_I FLEX_R) +POWER_FLEXURE=(FLEX_I FLEX_R FLEX_U FLEX_G) POWER_FILTER=ACAM_MOT -POWER_FOCUS=(FOCUS_IR) +POWER_FOCUS=(FOCUS_IR FOCUS_G FOCUS_U) POWER_TELEM=CR1000 POWER_THERMAL=(LKS_IG LKS_UR CR1000) POWER_ACAM=(ACAM ACAM_MOT) From 43f39d831e6d0cb49d51cc12a05d5994a58293c1 Mon Sep 17 00:00:00 2001 From: David Hale Date: Mon, 2 Feb 2026 16:04:34 -0800 Subject: [PATCH 16/19] fixes bug in Sequencer::CalibrationTarget::configure --- sequencerd/sequencer_interface.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sequencerd/sequencer_interface.cpp b/sequencerd/sequencer_interface.cpp index eecf09c8..810feb63 100644 --- a/sequencerd/sequencer_interface.cpp +++ b/sequencerd/sequencer_interface.cpp @@ -997,12 +997,12 @@ namespace Sequencer { // tokens 11-12 are dome lamps for (size_t i=0; i < 2; i++) { - info.domelamp.at(i) = on_off(tokens.at(11+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+1)); + info.lampmod[i] = on_off(tokens.at(13+i)); } } catch (const std::exception &e) { From 63a7ef3570a910b65a13685dac11504565128bad Mon Sep 17 00:00:00 2001 From: David Hale Date: Mon, 2 Feb 2026 17:35:01 -0800 Subject: [PATCH 17/19] fixes deadlock and sequencer subscribes to camerad --- camerad/astrocam.cpp | 18 +++++++++++++++++ sequencerd/sequence.cpp | 41 ++++++++++++++++++++------------------- sequencerd/sequencerd.cpp | 2 +- 3 files changed, 40 insertions(+), 21 deletions(-) diff --git a/camerad/astrocam.cpp b/camerad/astrocam.cpp index 8e51b24d..cc23a70d 100644 --- a/camerad/astrocam.cpp +++ b/camerad/astrocam.cpp @@ -1251,6 +1251,8 @@ namespace AstroCam { error = ERROR; } + this->publish_snapshot(); + return( error ); } /***** AstroCam::Interface::do_connect_controller ***************************/ @@ -2549,6 +2551,7 @@ namespace AstroCam { // if ( interface.exposure_pending() ) { interface.is_exposure_ready.store(false); + interface.publish_snapshot(); interface.camera.async.enqueue_and_log( function, "NOTICE:exposure pending" ); interface.camera.async.enqueue( "CAMERAD:READY:false" ); } @@ -6029,6 +6032,7 @@ 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( " isready\n" ); retstring.append( " isreadout\n" ); retstring.append( " pixelcount\n" ); retstring.append( " devnums\n" ); @@ -6384,6 +6388,10 @@ logwrite(function, message.str()); logwrite( function, message.str() ); retstring.append( message.str() ); retstring.append( "\n" ); + message.str(""); message << "is_exposure_ready=" << ( this->is_exposure_ready.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(); @@ -6600,6 +6608,16 @@ logwrite(function, message.str()); } else // ---------------------------------------------------- + // isready + // ---------------------------------------------------- + // am I ready for an exposure? + if (testname=="isready") { + retstring=(this->is_exposure_ready?"yes":"no"); + logwrite(function, retstring); + return NO_ERROR; + } + else + // ---------------------------------------------------- // isreadout // ---------------------------------------------------- // call ARC API isReadout() function directly diff --git a/sequencerd/sequence.cpp b/sequencerd/sequence.cpp index f1e1bf2f..943a0547 100644 --- a/sequencerd/sequence.cpp +++ b/sequencerd/sequence.cpp @@ -435,26 +435,6 @@ namespace Sequencer { logwrite( function, "sequencer running" ); - // wait until camera is ready to expose - // - if (!this->is_camera_ready.load()) { - - this->async.enqueue_and_log(function, "NOTICE: waiting for camera to be ready to expose"); - - std::unique_lock lock(this->camerad_mtx); - this->camerad_cv.wait( lock, [this]() { - return( this->is_camera_ready.load() || this->cancel_flag.load() ); - } ); - - if (this->cancel_flag.load()) { - logwrite(function, "sequence cancelled"); - return; - } - else { - logwrite(function, "camera ready to expose"); - } - } - // Get the next target from the database when single_obsid is empty // if ( this->single_obsid.empty() ) { @@ -761,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->is_camera_ready.load()) { + + this->async.enqueue_and_log(function, "NOTICE: waiting for camera to be ready to expose"); + + this->camerad_cv.wait( lock, [this]() { + return( this->is_camera_ready.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 ); @@ -4014,6 +4011,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->is_camera_ready.load() ? "yes" : "no"); + this->async.enqueue_and_log( function, message.str() ); retstring.append( message.str() ); error = NO_ERROR; 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(); } From 4f5a991d1057e7d11873f6c09b5597f3a95326b4 Mon Sep 17 00:00:00 2001 From: David Hale Date: Tue, 3 Feb 2026 08:55:10 -0800 Subject: [PATCH 18/19] unify can_expose variable between camerad-sequencerd --- camerad/astrocam.cpp | 14 +++++++------- camerad/astrocam.h | 4 ++-- sequencerd/sequence.cpp | 8 ++++---- sequencerd/sequence.h | 4 ++-- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/camerad/astrocam.cpp b/camerad/astrocam.cpp index cc23a70d..9fde63c2 100644 --- a/camerad/astrocam.cpp +++ b/camerad/astrocam.cpp @@ -30,7 +30,7 @@ namespace AstroCam { // build JSON message with my telemetry jmessage_out[Key::SOURCE] = "camerad"; - jmessage_out[Key::Camerad::READY] = this->is_exposure_ready.load(); + jmessage_out[Key::Camerad::READY] = this->can_expose.load(); // publish JSON message try { @@ -2550,7 +2550,7 @@ namespace AstroCam { // Log this message once only // if ( interface.exposure_pending() ) { - interface.is_exposure_ready.store(false); + 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" ); @@ -2583,7 +2583,7 @@ namespace AstroCam { interface.do_expose(interface.nexp); } else { - interface.is_exposure_ready.store(true); + 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" ); @@ -6032,7 +6032,7 @@ 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( " isready\n" ); + retstring.append( " canexpose\n" ); retstring.append( " isreadout\n" ); retstring.append( " pixelcount\n" ); retstring.append( " devnums\n" ); @@ -6388,7 +6388,7 @@ logwrite(function, message.str()); logwrite( function, message.str() ); retstring.append( message.str() ); retstring.append( "\n" ); - message.str(""); message << "is_exposure_ready=" << ( this->is_exposure_ready.load() ? "true" : "false" ); + message.str(""); message << "can_expose=" << ( this->can_expose.load() ? "true" : "false" ); logwrite( function, message.str() ); retstring.append( message.str() ); retstring.append( "\n" ); @@ -6611,8 +6611,8 @@ logwrite(function, message.str()); // isready // ---------------------------------------------------- // am I ready for an exposure? - if (testname=="isready") { - retstring=(this->is_exposure_ready?"yes":"no"); + if (testname=="canexpose") { + retstring=(this->can_expose?"yes":"no"); logwrite(function, retstring); return NO_ERROR; } diff --git a/camerad/astrocam.h b/camerad/astrocam.h index 185f065b..05999020 100644 --- a/camerad/astrocam.h +++ b/camerad/astrocam.h @@ -633,7 +633,7 @@ namespace AstroCam { should_subscriber_thread_run(false), framethreadcount(0), state_monitor_thread_running(false), - is_exposure_ready(true), // am I ready for the next exposure? + 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 @@ -785,7 +785,7 @@ std::vector> fitsinfo; * exposure pending stuff * */ - std::atomic is_exposure_ready; + std::atomic can_expose; std::condition_variable exposure_condition; std::mutex exposure_lock; static void dothread_monitor_exposure_pending( Interface &interface ); diff --git a/sequencerd/sequence.cpp b/sequencerd/sequence.cpp index 943a0547..f6de3e1a 100644 --- a/sequencerd/sequence.cpp +++ b/sequencerd/sequence.cpp @@ -50,7 +50,7 @@ namespace Sequencer { void Sequence::handletopic_camerad(const nlohmann::json &jmessage) { if (jmessage.contains(Key::Camerad::READY)) { int isready = jmessage[Key::Camerad::READY].get(); - this->is_camera_ready.store(isready, std::memory_order_relaxed); + this->can_expose.store(isready, std::memory_order_relaxed); std::lock_guard lock(camerad_mtx); this->camerad_cv.notify_all(); } @@ -744,12 +744,12 @@ namespace Sequencer { // wait until camera is ready to expose // std::unique_lock lock(this->camerad_mtx); - if (!this->is_camera_ready.load()) { + 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->is_camera_ready.load() || this->cancel_flag.load() ); + return( this->can_expose.load() || this->cancel_flag.load() ); } ); if (this->cancel_flag.load()) { @@ -4013,7 +4013,7 @@ namespace Sequencer { this->async.enqueue_and_log( function, message.str() ); retstring.append( message.str() ); retstring.append( "\n" ); - message.str(""); message << "NOTICE: camera ready to expose: " << (this->is_camera_ready.load() ? "yes" : "no"); + 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() ); diff --git a/sequencerd/sequence.h b/sequencerd/sequence.h index 1bc3c65e..c931cce2 100644 --- a/sequencerd/sequence.h +++ b/sequencerd/sequence.h @@ -281,7 +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 is_camera_ready; + 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; ///< @@ -309,7 +309,7 @@ namespace Sequencer { Sequence() : context(), ready_to_start(false), - is_camera_ready(false), + can_expose(false), is_science_frame_transfer(false), notify_tcs_next_target(false), arm_readout_flag(false), From af3ffa567095c4ec6176edd03f2552f3f136f7ab Mon Sep 17 00:00:00 2001 From: David Hale Date: Tue, 3 Feb 2026 13:33:51 -0800 Subject: [PATCH 19/19] sequencer sends activate states from CAL_ table to camerad updates sequencerd.cfg.in other minor cleanup --- Config/sequencerd.cfg.in | 3 +- camerad/astrocam.cpp | 133 +++++++++++++++++-------------- sequencerd/sequence.cpp | 60 +++++++++----- sequencerd/sequencer_interface.h | 8 +- utils/utilities.h | 2 +- 5 files changed, 120 insertions(+), 86 deletions(-) diff --git a/Config/sequencerd.cfg.in b/Config/sequencerd.cfg.in index 3a5a6901..61d81707 100644 --- a/Config/sequencerd.cfg.in +++ b/Config/sequencerd.cfg.in @@ -178,7 +178,8 @@ CAL_TARGET=(CAL_THAR open close on on on on on on on on off off 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_CONT open close on on on on on on on on off off off off off on 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) diff --git a/camerad/astrocam.cpp b/camerad/astrocam.cpp index 9fde63c2..2675ccdb 100644 --- a/camerad/astrocam.cpp +++ b/camerad/astrocam.cpp @@ -639,6 +639,8 @@ namespace AstroCam { /***** 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, ...]" * */ @@ -5516,85 +5518,98 @@ logwrite(function, message.str()); /***** 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::string chan; + std::vector _devnums; // local list of devnum(s) associated with chan(s) + std::string chan; // current channel std::istringstream iss(args); - // get channel name from args + // get channel name(s) from args and + // convert to a vector of devnum(s) // - if (!(iss >> chan)) { - logwrite(function, "ERROR parsing args. expected "); - retstring="bad_argument"; - return ERROR; + 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); } - // 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())); - retstring="bad_channel"; - return ERROR; - } + long error = NO_ERROR; - // get pointer to the Controller object for this device - // it only needs to exist and be connected + retstring.clear(); + + // activate/deactivate each dev // - auto pcontroller = this->get_controller(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); - if (!pcontroller) { - logwrite(function, "ERROR: channel "+chan+" not configured"); - retstring="missing_device"; - return ERROR; - } - if (!pcontroller->configured || !pcontroller->connected) { - logwrite(function, "ERROR: channel "+chan+" not connected"); - retstring="not_connected"; - return ERROR; - } + // 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; + } - long error = NO_ERROR; + // set or get active state as specified by cmd + switch (cmd) { - // 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 set active flag, then send activation commands - case AstroCam::ActiveState::Activate: - 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; - // first turn off power, then clear active flag - case AstroCam::ActiveState::DeActivate: - 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; + } - // do nothing - case AstroCam::ActiveState::Query: - default: - break; + // build up return string + retstring += (pcontroller->channel+":"+(pcontroller->active ? "activated " : "deactivated ")); } - retstring = pcontroller->active ? "activated" : "deactivated"; - return error; } /***** AstroCam::Interface::camera_active_state *****************************/ diff --git a/sequencerd/sequence.cpp b/sequencerd/sequence.cpp index f6de3e1a..2e2f9410 100644 --- a/sequencerd/sequence.cpp +++ b/sequencerd/sequence.cpp @@ -765,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 @@ -2146,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() && @@ -2184,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(); @@ -2200,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 ) { @@ -2211,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 ) { diff --git a/sequencerd/sequencer_interface.h b/sequencerd/sequencer_interface.h index f3f7f83f..f4569e7c 100644 --- a/sequencerd/sequencer_interface.h +++ b/sequencerd/sequencer_interface.h @@ -154,10 +154,12 @@ 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: 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;