diff --git a/camerad/archon.cpp b/camerad/archon.cpp index 55001dca..fa5a5030 100644 --- a/camerad/archon.cpp +++ b/camerad/archon.cpp @@ -36,7 +36,7 @@ namespace Archon { this->image_data = nullptr; this->image_data_bytes = 0; this->image_data_allocated = 0; - this->is_longexposure = false; + this->is_longexposure_set = false; this->is_window = false; this->is_autofetch = false; this->win_hstart = 0; @@ -230,12 +230,12 @@ namespace Archon { this->camera_info.port = -1; this->archon.setport( -1 ); - this->is_longexposure = false; this->n_hdrshift = 16; this->camera.firmware[0] = ""; this->exposeparam = ""; + this->longexposeparam.clear(); this->trigin_exposeparam = ""; this->trigin_untimedparam = ""; this->trigin_readoutparam = ""; @@ -300,6 +300,14 @@ namespace Archon { applied++; } + if ( config.param[entry] == "LONGEXPOSE_PARAM" ) { // LONGEXPOSE_PARAM + this->longexposeparam = config.arg[entry]; + message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; + logwrite( function, message.str() ); + this->camera.async.enqueue( message.str() ); + applied++; + } + if (config.param[entry].compare(0, 19, "TRIGIN_EXPOSE_PARAM")==0) { // TRIGIN_EXPOSE_PARAM this->trigin_exposeparam = config.arg[entry]; message.str(""); message << "CONFIG:" << config.param[entry] << "=" << config.arg[entry]; @@ -1756,14 +1764,6 @@ namespace Archon { this->modeselected = false; // require that a mode be selected after loading new firmware - // Even if exptime, longexposure were previously set, a new ACF could have different - // default values than the server has, so reset these to "undefined" in order to - // force the server to ask for them. - // - this->camera_info.exposure_time = -1; - this->camera_info.exposure_factor = -1; - this->camera_info.exposure_unit.clear(); - return error; } /**************** Archon::Interface::load_acf *******************************/ @@ -3756,34 +3756,43 @@ namespace Archon { // same default values that the ACF has, rather than hope that the ACF programmer picks // their defaults to match mine. // - if ( this->camera_info.exposure_time == -1 ) { + if ( ! this->camera_info.exposure_time.is_set() ) { logwrite( function, "NOTICE:exptime has not been set--will read from Archon" ); this->camera.async.enqueue( "NOTICE:exptime has not been set--will read from Archon" ); // read the Archon configuration memory // std::string etime; - if ( read_parameter( "exptime", etime ) != NO_ERROR ) { logwrite( function, "ERROR: reading \"exptime\" parameter from Archon" ); return ERROR; } + if ( read_parameter( "exptime", etime ) != NO_ERROR ) { + logwrite( function, "ERROR: reading \"exptime\" parameter from Archon" ); + return ERROR; + } // Tell the server these values // std::string retval; if ( this->exptime( etime, retval ) != NO_ERROR ) { logwrite( function, "ERROR: setting exptime" ); return ERROR; } } - if ( this->camera_info.exposure_factor == -1 || - this->camera_info.exposure_unit.empty() ) { + + if ( ! this->is_longexposure_set && ! this->longexposeparam.empty() ) { logwrite( function, "NOTICE:longexposure has not been set--will read from Archon" ); this->camera.async.enqueue( "NOTICE:longexposure has not been set--will read from Archon" ); // read the Archon configuration memory // std::string lexp; - if ( read_parameter( "longexposure", lexp ) != NO_ERROR ) { logwrite( function, "ERROR: reading \"longexposure\" parameter from Archon" ); return ERROR; } + if ( read_parameter( this->longexposeparam, lexp ) != NO_ERROR ) { + logwrite( function, "ERROR reading \""+this->longexposeparam+"\" parameter from Archon" ); + return ERROR; + } // Tell the server these values // std::string retval; - if ( this->longexposure( lexp, retval ) != NO_ERROR ) { logwrite( function, "ERROR: setting longexposure" ); return ERROR; } + if ( this->longexposure( lexp, retval ) != NO_ERROR ) { + logwrite( function, "ERROR: setting longexposure" ); + return ERROR; + } } // If nseq_in is not supplied then set nseq to 1. @@ -3915,7 +3924,7 @@ namespace Archon { message.str(""); message << "pre-exposure " << expcount << " of " << this->camera_info.num_pre_exposures; logwrite( function, message.str() ); - if ( this->camera_info.exposure_time != 0 ) { // wait for the exposure delay to complete (if there is one) + if ( this->camera_info.exposure_time.value() != 0 ) { // wait for the exposure delay to complete (if there is one) error = this->wait_for_exposure(); if ( error != NO_ERROR ) { logwrite( function, "ERROR: waiting for pre-exposure" ); @@ -3966,7 +3975,7 @@ namespace Archon { } } - if ( this->camera_info.exposure_time != 0 ) { // wait for the exposure delay to complete (if there is one) + if ( this->camera_info.exposure_time.value() != 0 ) { // wait for the exposure delay to complete (if there is one) error = this->wait_for_exposure(); if ( error != NO_ERROR ) { logwrite( function, "ERROR: waiting for exposure" ); @@ -4521,34 +4530,43 @@ namespace Archon { // This ensures that, if the client doesn't set these values then the server will have the // same default values that the ACF has, rather than hope that the ACF programmer picks // their defaults to match mine. - if ( this->camera_info.exposure_time == -1 ) { + if ( ! this->camera_info.exposure_time.is_set() ) { logwrite( function, "NOTICE:exptime has not been set--will read from Archon" ); this->camera.async.enqueue( "NOTICE:exptime has not been set--will read from Archon" ); // read the Archon configuration memory // std::string etime; - if ( read_parameter( "exptime", etime ) != NO_ERROR ) { logwrite( function, "ERROR: reading \"exptime\" parameter from Archon" ); return ERROR; } + if ( read_parameter( "exptime", etime ) != NO_ERROR ) { + logwrite( function, "ERROR: reading \"exptime\" parameter from Archon" ); + return ERROR; + } // Tell the server these values // std::string retval; if ( this->exptime( etime, retval ) != NO_ERROR ) { logwrite( function, "ERROR: setting exptime" ); return ERROR; } } - if ( this->camera_info.exposure_factor == -1 || - this->camera_info.exposure_unit.empty() ) { + + if ( ! this->is_longexposure_set && ! this->longexposeparam.empty() ) { logwrite( function, "NOTICE:longexposure has not been set--will read from Archon" ); this->camera.async.enqueue( "NOTICE:longexposure has not been set--will read from Archon" ); // read the Archon configuration memory // std::string lexp; - if ( read_parameter( "longexposure", lexp ) != NO_ERROR ) { logwrite( function, "ERROR: reading \"longexposure\" parameter from Archon" ); return ERROR; } + if ( read_parameter( this->longexposeparam, lexp ) != NO_ERROR ) { + logwrite( function, "ERROR: reading \"longexposure\" parameter from Archon" ); + return ERROR; + } // Tell the server these values // std::string retval; - if ( this->longexposure( lexp, retval ) != NO_ERROR ) { logwrite( function, "ERROR: setting longexposure" ); return ERROR; } + if ( this->longexposure( lexp, retval ) != NO_ERROR ) { + logwrite( function, "ERROR setting longexposure" ); + return ERROR; + } } // If nseq_in is not supplied then set nseq to 1. @@ -4642,7 +4660,7 @@ namespace Archon { // } // wait for the exposure delay to complete (if there is one) - if ( this->camera_info.exposure_time != 0 ) { + if ( this->camera_info.exposure_time.value() != 0 ) { error = this->wait_for_exposure(); if ( error != NO_ERROR ) { logwrite( function, "ERROR: waiting for exposure" ); @@ -4752,7 +4770,7 @@ namespace Archon { // same default values that the ACF has, rather than hope that the ACF programmer picks // their defaults to match mine. // - if ( this->camera_info.exposure_time == -1 ) { + if ( ! this->camera_info.exposure_time.is_set() ) { logwrite( function, "NOTICE:exptime has not been set--will read from Archon" ); this->camera.async.enqueue( "NOTICE:exptime has not been set--will read from Archon" ); @@ -4766,20 +4784,25 @@ namespace Archon { std::string retval; if ( this->exptime( etime, retval ) != NO_ERROR ) { logwrite( function, "ERROR: setting exptime" ); return ERROR; } } - if ( this->camera_info.exposure_factor == -1 || - this->camera_info.exposure_unit.empty() ) { + if ( ! this->is_longexposure_set && ! this->longexposeparam.empty() ) { logwrite( function, "NOTICE:longexposure has not been set--will read from Archon" ); this->camera.async.enqueue( "NOTICE:longexposure has not been set--will read from Archon" ); // read the Archon configuration memory // std::string lexp; - if ( read_parameter( "longexposure", lexp ) != NO_ERROR ) { logwrite( function, "ERROR: reading \"longexposure\" parameter from Archon" ); return ERROR; } + if ( read_parameter( this->longexposeparam, lexp ) != NO_ERROR ) { + logwrite( function, "ERROR: reading \"longexposure\" parameter from Archon" ); + return ERROR; + } // Tell the server these values // std::string retval; - if ( this->longexposure( lexp, retval ) != NO_ERROR ) { logwrite( function, "ERROR: setting longexposure" ); return ERROR; } + if ( this->longexposure( lexp, retval ) != NO_ERROR ) { + logwrite( function, "ERROR setting longexposure" ); + return ERROR; + } } // If nseq_in is not supplied then set nseq to 1. @@ -4894,7 +4917,7 @@ namespace Archon { message.str(""); message << "pre-exposure " << expcount << " of " << this->camera_info.num_pre_exposures; logwrite( function, message.str() ); - if ( this->camera_info.exposure_time != 0 ) { // wait for the exposure delay to complete (if there is one) + if ( this->camera_info.exposure_time.value() != 0 ) { // wait for the exposure delay to complete (if there is one) error = this->wait_for_exposure(); if ( error != NO_ERROR ) { logwrite( function, "ERROR: waiting for pre-exposure" ); @@ -4945,7 +4968,7 @@ namespace Archon { } } - if ( this->camera_info.exposure_time != 0 ) { // wait for the exposure delay to complete (if there is one) + if ( this->camera_info.exposure_time.value() != 0 ) { // wait for the exposure delay to complete (if there is one) error = this->wait_for_exposure(); if ( error != NO_ERROR ) { logwrite( function, "ERROR: waiting for exposure" ); @@ -5047,12 +5070,10 @@ namespace Archon { /**************** Archon::Interface::video *********************************/ - /**************** Archon::Interface::wait_for_exposure **********************/ + /***** Archon::Interface::wait_for_exposure *********************************/ /** - * @fn wait_for_exposure - * @brief creates a wait until the exposure delay has completed - * @param none - * @return ERROR or NO_ERROR + * @brief creates a wait until the exposure delay has completed + * @return ERROR | NO_ERROR * * This is not the actual exposure delay, nor does it accurately time the exposure * delay. This function merely creates a reasonably accurate wait on the host to @@ -5071,49 +5092,74 @@ namespace Archon { std::stringstream message; long error = NO_ERROR; - int exposure_timeout_time; // Time to wait for the exposure delay to time out - unsigned long int timer, increment=0; + // Predicting when the exposure ends requires knowing when it started. + // The Archon timer was read when the exposure was initiated but that + // may generate multiple frames so each time through here requires + // getting the start time. It's not accurate but good enough for this. + // + unsigned long int archon_start_time; + error = this->get_timer(&archon_start_time); // Archon internal timer (one tick=10 nsec) - // For long exposures, waittime is 1 second less than the exposure time. - // For short exposures, waittime is an integral number of msec below 90% of the exposure time - // and will be used to keep track of elapsed time, for timeout errors. + // set a max timeout time of 1000 ms more than the exposure time // - double waittime; - if ( this->is_longexposure ) { - waittime = this->camera_info.exposure_time - 1; + uint32_t exposure_timeout_time = this->camera_info.exposure_time.ms() + 1000; // Time to wait in msec for the exposure delay to time out - } else { - waittime = floor( 0.9 * this->camera_info.exposure_time / this->camera_info.exposure_factor ); // in units of this->camera_info.exposure_unit + // Wait for 1000 ms less than the exposure time + // unless the waittime works out to be less than 1000 ms then + // don't wait at all, just start polling. + // + uint32_t waittime=0; + if ( this->camera_info.exposure_time.ms() > 1000 ) { + waittime = this->camera_info.exposure_time.ms() - 1000; } + waittime = ( waittime < 1000 ? 0 : waittime ); // Wait, (don't sleep) for the above waittime. // This is a period that could be aborted by setting the this->abort flag. //TODO not yet implemented? // All that is happening here is a wait -- there is no Archon polling going on here. // - double start_time = get_clock_time(); // get the current clock time from host (in seconds) - double now = start_time; // Prediction is the predicted finish_timer, used to compute exposure time progress, // and is computed as start_time + exposure_time in Archon ticks. - // Each Archon tick is 10 nsec (1e8 sec). Divide by exposure_factor (=1 for sec, =1000 for msec). + // Each Archon tick is 10 nsec (1e8 sec) and exposure_time.value() is in ms so multiply + // exposure time by MSEC_TO_TICK. // - unsigned long int prediction = this->start_timer + this->camera_info.exposure_time * 1e8 / this->camera_info.exposure_factor; + unsigned long int prediction = archon_start_time + this->camera_info.exposure_time.ms() * MSEC_TO_TICK; + + { + auto tstart = std::chrono::steady_clock::now(); + while (true) { + auto tnow = std::chrono::steady_clock::now(); + auto elapsed = std::chrono::duration_cast(tnow - tstart).count(); + if ( elapsed > waittime ) break; + std::this_thread::sleep_for( std::chrono::microseconds( 10 )); + } + } + // If the waittime is more than 1000 ms then publish exposure progress + // +/*** TODO optionally broadcast progress + { std::cerr << "exposure progress: "; - while ((now - (waittime + start_time) < 0) && !this->abort) { - // sleep 10 msec = 1e6 Archon ticks - std::this_thread::sleep_for( std::chrono::milliseconds( 10 )); - increment += 1000000; - now = get_clock_time(); - this->camera_info.exposure_progress = (double)increment / (double)(prediction - this->start_timer); - if (this->camera_info.exposure_progress < 0 || this->camera_info.exposure_progress > 1) this->camera_info.exposure_progress=1; - std::cerr << std::setw(3) << (int)(this->camera_info.exposure_progress*100) << "\b\b\b"; + auto tstart = std::chrono::steady_clock::now(); + auto tend = tstart + std::chrono::seconds( static_cast(std::ceil( total_s )) ); + while ( !this->abort && std::chrono::steady_clock::now() < tend ) { + auto elapsed = std::chrono::steady_clock::now() - tstart; + double elapsed_s = std::chrono::duration(elapsed).count(); + this->camera_info.exposure_progress = 100.0 * ( elapsed_s / total_s ); + std::cerr << std::setw(3) << static_cast(std::round(this->camera_info.exposure_progress)) << "\b\b\b"; // ASYNC status message reports the elapsed time in the chosen unit // - message.str(""); message << "EXPOSURE:" << (int)(this->camera_info.exposure_time - (this->camera_info.exposure_progress * this->camera_info.exposure_time)); + message.str(""); message << "EXPOSURE:" << (int)(this->camera_info.exposure_time.value() - (this->camera_info.exposure_progress * this->camera_info.exposure_time.value())); this->camera.async.enqueue( message.str() ); +// auto tnow = std::chrono::steady_clock::now(); +// auto elapsed = std::chrono::duration_cast(tnow - tstart).count(); +// if ( elapsed > waittime ) break; + std::this_thread::sleep_for( std::chrono::milliseconds( 1 )); } + } +***/ if (this->abort) { std::cerr << "\n"; @@ -5121,24 +5167,16 @@ namespace Archon { return NO_ERROR; } - // Set the time-out value in ms. If the exposure time is less than a second, set - // the timeout to 1 second. Otherwise, set it to the exposure time plus - // 1 second. - // - if ( this->camera_info.exposure_time / this->camera_info.exposure_factor < 1 ) { - exposure_timeout_time = 1000; //ms - - } else { - exposure_timeout_time = (this->camera_info.exposure_time / this->camera_info.exposure_factor) * 1000 + 1000; - } - - // Now start polling the Archon for the last remaining portion of the exposure delay + // Now start polling the Archon for the last remaining portion of the exposure delay. + // Record the start time to compare against current time and allow timing out. // bool done = false; + std::chrono::steady_clock::time_point tstart = std::chrono::steady_clock::now(); while (!done && !this->abort) { // Poll Archon's internal timer // - if ( (error=this->get_timer(&timer)) == ERROR ) { + unsigned long int archon_time_now; + if ( (error=this->get_timer(&archon_time_now)) == ERROR ) { std::cerr << "\n"; logwrite( function, "ERROR: could not get Archon timer" ); break; @@ -5146,41 +5184,40 @@ namespace Archon { // update progress // - this->camera_info.exposure_progress = (double)(timer - this->start_timer) / (double)(prediction - this->start_timer); + this->camera_info.exposure_progress = (double)(archon_time_now - archon_start_time) / (double)(prediction - archon_start_time); if (this->camera_info.exposure_progress < 0 || this->camera_info.exposure_progress > 1) this->camera_info.exposure_progress=1; // ASYNC status message reports the elapsed time in the chosen unit // - message.str(""); message << "EXPOSURE:" << (int)(this->camera_info.exposure_time - (this->camera_info.exposure_progress * this->camera_info.exposure_time)); + message.str(""); message << "EXPOSURE:" << (int)(this->camera_info.exposure_time.value() - (this->camera_info.exposure_progress * this->camera_info.exposure_time.value())); this->camera.async.enqueue( message.str() ); std::cerr << std::setw(3) << (int)(this->camera_info.exposure_progress*100) << "\b\b\b"; // send to stderr in case anyone is watching - // Archon timer ticks are in 10 nsec (1e-8) so when comparing to exposure_time, - // multiply exposure_time by 1e8/exposure_factor, where exposure_factor=1 or =1000 for exposure_unit sec or msec. + // Archon timer ticks are in 10 nsec (1e-8) and exposure_time.value() is in msec + // so multiply exposure_time.value() by MSEC_TO_TICK. // - if ( (timer - this->start_timer) >= ( this->camera_info.exposure_time * 1e8 / this->camera_info.exposure_factor ) ) { - this->finish_timer = timer; + if ( (archon_time_now - archon_start_time) >= ( this->camera_info.exposure_time.ms() * MSEC_TO_TICK ) ) { + this->finish_timer = archon_time_now; done = true; break; } // a little pause to slow down the requests to Archon - std::this_thread::sleep_for( std::chrono::milliseconds( 1 )); - // Added protection against infinite loops, probably will never be invoked - // because an Archon error getting the timer would exit the loop. - // exposure_timeout_time is in msec, and it's a little more than 1 msec to get - // through this loop. If this is decremented each time through then it should - // never hit zero before the exposure is finished, unless there is a serious - // problem. + std::this_thread::sleep_for( std::chrono::microseconds( 100 )); + + // There's a limit to how long we let this polling loop last // - if (--exposure_timeout_time < 0) { + std::chrono::steady_clock::time_point tnow = std::chrono::steady_clock::now(); + auto elapsed = std::chrono::duration_cast(tnow - tstart).count(); + if ( elapsed > exposure_timeout_time ) { std::cerr << "\n"; error = ERROR; this->camera.log_error( function, "timeout waiting for exposure" ); break; } } // end while (done == false && this->abort == false) + logwrite( function, "exposure complete" ); std::cerr << "\n"; @@ -5190,7 +5227,7 @@ namespace Archon { return error; } - /**************** Archon::Interface::wait_for_exposure **********************/ + /***** Archon::Interface::wait_for_exposure *********************************/ /**************** Archon::Interface::wait_for_readout ***********************/ @@ -5208,7 +5245,6 @@ namespace Archon { std::stringstream message; long error = NO_ERROR; int currentframe=this->lastframe; - int busycount=0; bool done = false; message.str(""); @@ -5220,7 +5256,7 @@ namespace Archon { // double waittime; try { - waittime = this->camera.readout_time.at(0) * 1.1; // this is in msec + waittime = ceil( 1.1* this->camera.readout_time[0] ); } catch(std::out_of_range &) { message.str(""); message << "readout time for Archon not found from config file"; @@ -5236,22 +5272,9 @@ namespace Archon { // while (!done && !this->abort) { - if (this->is_longexposure) usleep( 10000 ); // reduces polling frequency + if ( this->camera_info.exposure_time.is_longexposure() ) std::this_thread::sleep_for( std::chrono::microseconds( 10 ) ); error = this->get_frame_status(); - // If Archon is busy then ignore it, keep trying for up to ~ 3 second - // (300 attempts, ~10000us between attempts) - // - if (error == BUSY) { - if ( ++busycount > 300 ) { - done = true; - this->camera.log_error( function, "received BUSY from Archon too many times trying to get frame status" ); - break; - - } else continue; - - } else busycount=0; - if (error == ERROR) { done = true; logwrite( function, "ERROR: unable to get frame status" ); @@ -5455,12 +5478,14 @@ namespace Archon { /**************** Archon::Interface::set_parameter **************************/ - /**************** Archon::Interface::exptime ********************************/ + /***** Archon::Interface::exptime *******************************************/ /** - * @fn exptime - * @brief set/get the exposure time - * @param string - * @return ERROR or NO_ERROR + * @brief set/get the exposure time + * @details exposure time is in the units set by longexposure() or the + * unit can be optionally set here + * @param[in] exptime_in "