diff --git a/indi-gige/AUTHORS b/indi-gige/AUTHORS index 6a93f70ba..3ddc0a051 100644 --- a/indi-gige/AUTHORS +++ b/indi-gige/AUTHORS @@ -1 +1,2 @@ Hendrik Beijeman (hbeyeman@gmail.com) http://phym.nl +Spencer E. Olson (olsonse@umich.edu) diff --git a/indi-gige/README b/indi-gige/README index 898a466cc..af236cf5d 100644 --- a/indi-gige/README +++ b/indi-gige/README @@ -15,7 +15,7 @@ Requirements cfitsio-devel is required to compile support for FITS. -+ aravis >= v0.6 ++ aravis >= v0.8 aravis is required, see https://github.com/AravisProject/aravis @@ -57,9 +57,9 @@ GigE machine vision overview (2b) Vendor specific set Project Aravis only uses genicam standard. For some features you need to - access the CSR directly. These can easily be queried with arv-tool-0.6 that come + access the CSR directly. These can easily be queried with arv-tool-0.8 that come with aravis, i.e. - $ arv-tool-0.6 control R[0xF0F00A00] R[0xF0F00A04] R[0xF0F00A08] R[0xF0F00A10] R[0xF0F00A14] + $ arv-tool-0.8 control R[0xF0F00A00] R[0xF0F00A04] R[0xF0F00A08] R[0xF0F00A10] R[0xF0F00A14] Point Grey Research-16048874 R[0xf0f00a00] = 0x08000600 diff --git a/indi-gige/indi_gige_ccd.xml b/indi-gige/indi_gige_ccd.xml index 8430e0d87..72ca017ce 100644 --- a/indi-gige/indi_gige_ccd.xml +++ b/indi-gige/indi_gige_ccd.xml @@ -1,7 +1,7 @@ indi_gige_ccd - 0.1 + 0.2 diff --git a/indi-gige/src/ArvFactory.cpp b/indi-gige/src/ArvFactory.cpp index a34a06478..3df07e416 100644 --- a/indi-gige/src/ArvFactory.cpp +++ b/indi-gige/src/ArvFactory.cpp @@ -26,23 +26,49 @@ #define BLACKFLY_MODEL "BFLY-PGE-31S4M" -arv::ArvCamera *ArvFactory::find_first_available(void) +namespace arv { + std::unique_ptr create_camera(unsigned int index) + { + if (index >= arv_get_n_devices()) + /* no devices found */ + return nullptr; + + const char *device_id = arv_get_device_id(index); + const char *model_name = arv_get_device_model(index); + + if (memmem(model_name, strlen(model_name), BLACKFLY_MODEL, strlen(BLACKFLY_MODEL))) + { + printf("Creating BlackFly... for %s-%s\n", model_name, device_id); + return std::unique_ptr(new BlackFly(device_id, model_name)); + } + else + { + printf("Creating Generic... for %s-%s\n", model_name, device_id); + return std::unique_ptr(new ArvGeneric(device_id, model_name)); + } + } +} + +std::unique_ptr ArvFactory::find_first_available(void) +{ + /* We first ensure the library knows of all available devices and info */ + arv_update_device_list(); + return create_camera(0); +} + +ArvFactory::Iterator ArvFactory::begin(void) +{ + /* We first ensure the library knows of all available devices and info */ + arv_update_device_list(); + return ArvFactory::Iterator(0); +} + +ArvFactory::Iterator ArvFactory::end(void) +{ + return ArvFactory::Iterator(arv_get_n_devices()); +} + +std::unique_ptr ArvFactory::Index::instantiate(void) const { - GError *error = NULL; - ::ArvCamera *camera = arv_camera_new(nullptr, &error); - const char *model_name = arv_camera_get_model_name(camera, &error); - - if ((camera == nullptr) || (model_name == nullptr)) - return nullptr; - - if (memmem(model_name, strlen(model_name), BLACKFLY_MODEL, strlen(BLACKFLY_MODEL))) - { - printf("Creating BlackFly...\n"); - return new BlackFly((void *)camera); - } - else - { - printf("Creating Generic...\n"); - return new ArvGeneric((void *)camera); - } + return create_camera(this->index); } diff --git a/indi-gige/src/ArvGeneric.cpp b/indi-gige/src/ArvGeneric.cpp index bfd502809..f3f269205 100644 --- a/indi-gige/src/ArvGeneric.cpp +++ b/indi-gige/src/ArvGeneric.cpp @@ -18,23 +18,60 @@ */ #include "ArvGeneric.h" +#include +#include + using namespace arv; -const char *ArvGeneric::_str_val(const char *s) -{ - return (s ? s : "None"); +namespace { + inline const char *_str_val(const std::string & s) + { + return (s != "" ? s.c_str() : "None"); + } +} + +// Variadic template error handler function +template +void ArvGeneric::call_log(const char *func_description, Func &&func, Args&&... args) { + GError * error = nullptr; + func(args..., &error); + if (error) { + if (error->message) + LOGF_ERROR("%s: %s", func_description, error->message); + else + LOGF_ERROR("%s: unknown error!", func_description); + } +} +#define CALL(func, ...) \ + this->call_log(#func "(" #__VA_ARGS__ ")", func, __VA_ARGS__) + +// Variadic template error handler function +template +auto ArvGeneric::call_log_return(const char *func_description, Func &&func, Args&&... args) { + GError * error = nullptr; + auto ret = func(args..., &error); + if (error) { + if (error->message) + LOGF_ERROR("%s: %s", func_description, error->message); + else + LOGF_ERROR("%s: unknown error!", func_description); + } + return ret; } +#define CALL_RETURN(func, ...) \ + this->call_log_return(#func "(" #__VA_ARGS__ ")", func, __VA_ARGS__) + const char *ArvGeneric::vendor_name() { - return this->_str_val(this->cam.vendor_name); + return _str_val(this->cam.vendor_name); } const char *ArvGeneric::model_name() { - return this->_str_val(this->cam.model_name); + return _str_val(this->cam.model_name); } const char *ArvGeneric::device_id() { - return this->_str_val(this->cam.device_id); + return _str_val(this->cam.device_id); } min_max_property ArvGeneric::get_bin_x() { @@ -81,66 +118,95 @@ min_max_property ArvGeneric::get_frame_rate() return min_max_property(this->cam.frame_rate); } +bool ArvGeneric::has_feature(const char * feature) +{ + return CALL_RETURN(arv_camera_is_feature_available, this->camera, feature) == TRUE; +} + +double ArvGeneric::get_float(const char * feature) +{ + return CALL_RETURN(arv_camera_get_float, this->camera, feature); +} + template bool ArvGeneric::_get_bounds(void (*fn_arv_bounds)(::ArvCamera *, T *min, T *max, GError**), min_max_property *prop) { T min, max; - fn_arv_bounds(this->camera, &min, &max, &(this->error)); + CALL(fn_arv_bounds, this->camera, &min, &max); prop->update(min, max); + return true; } -bool ArvGeneric::is_exposing() +template +bool ArvGeneric::_get_incr(T (*fn_arv_incr)(::ArvCamera *, GError**), min_max_property *prop) { - return this->stream_active; + prop->set_increment(CALL_RETURN(fn_arv_incr, this->camera)); + return true; } -bool ArvGeneric::is_connected() + +bool ArvGeneric::is_exposing_single() { - return (this->camera ? true : false); + bool single = this->single_acquisition_active.load(); + bool stream = this->stream_active.load(); + if (single && stream) + LOG_ERROR("Streaming during single-frame acquisition?!?"); + return single; } -bool ArvGeneric::_stream_active() + +bool ArvGeneric::is_streaming() { - return this->stream_active; + bool single = this->single_acquisition_active.load(); + bool stream = this->stream_active.load(); + if (single && stream) + LOG_ERROR("Streaming during single-frame acquisition?!?"); + return stream; } -ArvGeneric::ArvGeneric(void *camera_device) : ArvCamera(camera_device) +bool ArvGeneric::is_connected() { - this->_init(); - this->camera = (::ArvCamera *)camera_device; - this->dev = arv_camera_get_device(this->camera); + return (this->camera ? true : false); +} - this->cam.model_name = arv_camera_get_model_name(this->camera, &(this->error)); - this->cam.vendor_name = arv_camera_get_vendor_name(this->camera, &(this->error)); - this->cam.device_id = arv_camera_get_device_id(this->camera, &(this->error)); +ArvGeneric::ArvGeneric(std::string device_id, std::string model_name) +: ArvCamera(device_id, model_name) +{ + this->_init(); + /* device_id and model name must be available to INDI before Connect */ + this->cam.device_id = device_id; + this->cam.model_name = model_name; } ArvGeneric::~ArvGeneric() { - if (this->is_connected()) - { - this->disconnect(); - } - this->_init(); + this->disconnect(); } bool ArvGeneric::connect() { + printf("%s\n", __PRETTY_FUNCTION__); /* (Re-)connect by means of the device-id */ if (!this->camera) { - this->camera = ::arv_camera_new(this->cam.device_id, &(this->error)); + this->camera = CALL_RETURN(::arv_camera_new,this->cam.device_id.c_str()); if (!this->camera) return false; this->dev = arv_camera_get_device(this->camera); - this->cam.model_name = arv_camera_get_model_name(this->camera, &(this->error)); - this->cam.vendor_name = arv_camera_get_vendor_name(this->camera, &(this->error)); - this->cam.device_id = arv_camera_get_device_id(this->camera, &(this->error)); + this->cam.model_name = CALL_RETURN(arv_camera_get_model_name, this->camera); + this->cam.vendor_name = CALL_RETURN(arv_camera_get_vendor_name,this->camera); + // do not change device_id since arv_camera_get_device_id seems to just + // return the serial number instead of the less useful + // "{vendor} {model}-{serial}" than arv_get_device_id that is used in + // camera enumeration. + //this->cam.device_id = CALL_RETURN(arv_camera_get_device_id,this->camera); } + this->_configure(); return true; } bool ArvGeneric::_configure(void) { + printf("%s\n", __PRETTY_FUNCTION__); this->_set_initial_config(); return this->_get_initial_config(); } @@ -148,8 +214,8 @@ bool ArvGeneric::_configure(void) void ArvGeneric::_init() { this->camera = nullptr; - this->buffer = nullptr; this->stream = nullptr; + this->single_acquisition_active = false; this->stream_active = false; /* Don't clear device_id, its needed to re-attach with connect() */ @@ -159,10 +225,12 @@ bool ArvGeneric::disconnect() { if (this->is_connected()) { - this->_test_exposure_and_abort(); + this->exposure_abort(); + g_clear_object(&this->stream); g_clear_object(&this->camera); } this->_init(); + return true; } bool ArvGeneric::_set_initial_config() @@ -170,40 +238,43 @@ bool ArvGeneric::_set_initial_config() /* Configure "manual" mode * (1) disable auto exposure * (2) disable auto framerate (to enable maximum possible exposure time) - * (3) set binning to 1x1 - * (4) set software trigger */ - arv_camera_set_binning(camera, 1, 1, &error); - arv_camera_set_gain_auto(camera, ARV_AUTO_OFF, &error); - arv_camera_set_exposure_time_auto(camera, ARV_AUTO_OFF, &error); - arv_camera_set_trigger(camera, "Software", &error); + * (3) set binning to 1x1 */ + CALL(arv_camera_set_binning, camera, 1, 1); + CALL(arv_camera_set_gain_auto, camera, ARV_AUTO_OFF); + CALL(arv_camera_set_exposure_time_auto, camera, ARV_AUTO_OFF); return true; } -bool ArvGeneric::_get_initial_config() -{ - this->_get_bounds(arv_camera_get_x_binning_bounds, &this->cam.bin_x); - this->_get_bounds(arv_camera_get_y_binning_bounds, &this->cam.bin_y); - this->_get_bounds(arv_camera_get_x_offset_bounds, &this->cam.x_offset); - this->_get_bounds(arv_camera_get_y_offset_bounds, &this->cam.y_offset); - this->_get_bounds(arv_camera_get_width_bounds, &this->cam.width); - this->_get_bounds(arv_camera_get_height_bounds, &this->cam.height); - this->_get_bounds(arv_camera_get_frame_rate_bounds, &this->cam.frame_rate); - this->_get_bounds(arv_camera_get_exposure_time_bounds, &this->cam.exposure); - this->_get_bounds(arv_camera_get_gain_bounds, &this->cam.gain); +#define _GET_BOUNDS(T, feature, prop) \ + this->_get_bounds(arv_camera_get_ ## feature ## _bounds, &this->cam.prop) +#define _GET_BOUNDS_INCR(T, feature, prop) \ + _GET_BOUNDS(T, feature, prop); \ + this->_get_incr(arv_camera_get_ ## feature ## _increment, &this->cam.prop) - this->cam.vendor_name = arv_camera_get_vendor_name(camera, &error); - this->cam.model_name = arv_camera_get_model_name(camera, &error); - this->cam.device_id = arv_camera_get_device_id(camera, &error); - /* No GVCP call for this..., specialize if necessary */ - this->cam.pixel_pitch.set_single(1.0); +bool ArvGeneric::_get_initial_config() +{ + _GET_BOUNDS_INCR(gint, x_binning, bin_x); + _GET_BOUNDS_INCR(gint, y_binning, bin_y); + _GET_BOUNDS_INCR(gint, x_offset, x_offset); + _GET_BOUNDS_INCR(gint, y_offset, y_offset); + _GET_BOUNDS_INCR(gint, width, width); + _GET_BOUNDS_INCR(gint, height, height); + _GET_BOUNDS(double, frame_rate, frame_rate); + _GET_BOUNDS(double, exposure_time, exposure); + _GET_BOUNDS(double, gain, gain); + + /* No GVCP call for this..., specializations of this class could make this + * read-only by setting min=max=val (see BlackFly implementation). */ + this->cam.pixel_pitch.update(1.0, 40.0); + this->cam.pixel_pitch.set(1.0); return true; } int ArvGeneric::get_frame_byte_size() { - return arv_camera_get_payload(this->camera, &(this->error)); + return CALL_RETURN(arv_camera_get_payload, this->camera); } void ArvGeneric::set_geometry(int const x, int const y, int const w, int const h) @@ -213,23 +284,22 @@ void ArvGeneric::set_geometry(int const x, int const y, int const w, int const h this->cam.width.set(w); this->cam.height.set(h); - arv_camera_set_region(this->camera, this->cam.x_offset.val(), this->cam.y_offset.val(), this->cam.width.val(), - this->cam.height.val(), &(this->error)); + CALL(arv_camera_set_region, this->camera, this->cam.x_offset.val(), + this->cam.y_offset.val(), this->cam.width.val(), + this->cam.height.val()); } -void ArvGeneric::update_geometry(void) +std::tuple ArvGeneric::update_geometry(void) { - gint x, y, w, h, binx, biny; + gint x, y, w, h; - arv_camera_get_region(this->camera, &x, &y, &w, &h, &(this->error)); - arv_camera_get_binning(this->camera, &binx, &biny, &(this->error)); + CALL(arv_camera_get_region, this->camera, &x, &y, &w, &h); this->cam.x_offset.set(x); this->cam.y_offset.set(y); this->cam.width.set(w); this->cam.height.set(h); - this->cam.bin_x.set(binx); - this->cam.bin_y.set(biny); + return std::tuple(x, y, w, h); } void ArvGeneric::set_bin(int const bin_x, int const bin_y) @@ -237,148 +307,254 @@ void ArvGeneric::set_bin(int const bin_x, int const bin_y) this->cam.bin_x.set(bin_x); this->cam.bin_y.set(bin_y); - arv_camera_set_binning(this->camera, this->cam.bin_x.val(), this->cam.bin_y.val(), &(this->error)); + CALL(arv_camera_set_binning, this->camera, this->cam.bin_x.val(), this->cam.bin_y.val()); } -void ArvGeneric::_test_exposure_and_abort(void) +std::pair ArvGeneric::update_bin() { - if (this->_stream_active()) - this->exposure_abort(); + /* read this back to ensure we know what binning was actually set. For at + * least some cameras, a binning of 3 gets promoted to a binning of 4. */ + gint binx, biny; + CALL(arv_camera_get_binning, this->camera, &binx, &biny); + this->cam.bin_x.set(binx); + this->cam.bin_y.set(biny); + return std::pair(binx, biny); } template -void ArvGeneric::_set_cam_exposure_property(void (*arv_set)(::ArvCamera *, T, GError**), min_max_property *prop, - T const new_val) +void ArvGeneric::set_cam_exposure_property( + void (*arv_set)(::ArvCamera *, T, GError**), + min_max_property *prop, T const new_val, + T (*arv_get)(::ArvCamera *, GError**) ) { - this->_test_exposure_and_abort(); + if (this->is_exposing_single()) + this->exposure_abort(); prop->set(new_val); - arv_set(this->camera, prop->val(), &(this->error)); + CALL(arv_set, this->camera, prop->val()); + + if (arv_get != nullptr) + prop->set(CALL_RETURN(arv_get, this->camera)); } void ArvGeneric::set_gain(double const val) { - this->_set_cam_exposure_property(arv_camera_set_gain, &this->cam.gain, val); + this->set_cam_exposure_property(arv_camera_set_gain, &this->cam.gain, val, + arv_camera_get_gain); } + void ArvGeneric::set_exposure_time(double const val) { - this->_set_cam_exposure_property(arv_camera_set_exposure_time, &this->cam.exposure, val); + this->set_cam_exposure_property(arv_camera_set_exposure_time, + &this->cam.exposure, val, + arv_camera_get_exposure_time); } -::ArvBuffer *ArvGeneric::_buffer_create(void) +void ArvGeneric::create_stream(unsigned int n_buffers) { - ::ArvBuffer *buffer; + this->stream = CALL_RETURN(arv_camera_create_stream, this->camera, nullptr, nullptr); + if (this->stream == nullptr) { + LOG_ERROR("Could not allocate a stream object!"); + return; + } - /* Ensure no buffers in stream */ - while (1) - { - buffer = arv_stream_try_pop_buffer(this->stream); - if (buffer) - g_clear_object(&buffer); - else + gint const payload = CALL_RETURN(arv_camera_get_payload, this->camera); + for (; n_buffers > 0; --n_buffers) { + auto buffer = arv_buffer_new(payload, nullptr); + if (this->stream == nullptr) { + LOG_ERROR("Could not allocate stream buffer!"); break; + } + arv_stream_push_buffer(this->stream, buffer); } - - gint const payload = arv_camera_get_payload(this->camera, &(this->error)); - buffer = arv_buffer_new(payload, nullptr); - arv_stream_push_buffer(this->stream, buffer); - return buffer; } -::ArvStream *ArvGeneric::_stream_create(void) +void ArvGeneric::start_acquisition(unsigned int n_buffers, + ArvAcquisitionMode mode) { - ::ArvStream *stream = arv_camera_create_stream(this->camera, nullptr, nullptr, &(this->error)); - return stream; + this->exposure_abort(); + + // 1. make stream + this->create_stream(n_buffers); + + // 2. Disable triggers; just acquire as soon as possible. + CALL(arv_camera_clear_triggers, this->camera); + + // 3. start the acquisition stream + CALL(arv_camera_set_acquisition_mode, this->camera, mode); + CALL(arv_camera_start_acquisition, this->camera); } -void ArvGeneric::_stream_start() +void ArvGeneric::start_streaming_impl(unsigned int n_buffers) { + this->start_acquisition(n_buffers, ARV_ACQUISITION_MODE_CONTINUOUS); this->stream_active = true; +} - /* Start the acquisition stream */ - arv_camera_set_acquisition_mode(this->camera, ARV_ACQUISITION_MODE_SINGLE_FRAME, &(this->error)); - arv_camera_start_acquisition(this->camera, &(this->error)); +void ArvGeneric::stop_streaming() +{ + this->stop_acquisition(); } -void ArvGeneric::_stream_stop() +void ArvGeneric::stop_acquisition() { /* stop the acquisition stream */ - arv_camera_stop_acquisition(this->camera, &(this->error)); - g_object_unref(this->stream); + CALL(arv_camera_stop_acquisition, this->camera); + /* Free stream resources. */ + g_clear_object(&this->stream); this->stream_active = false; + this->single_acquisition_active = false; } -void ArvGeneric::_trigger_exposure() +void ArvGeneric::exposure_start(void) { - /* Trigger for an exposure */ - arv_camera_software_trigger(this->camera, &(this->error)); + this->start_acquisition(1, ARV_ACQUISITION_MODE_SINGLE_FRAME); + this->single_acquisition_active = true; } -void ArvGeneric::exposure_start(void) +void ArvGeneric::exposure_abort(void) { - this->_test_exposure_and_abort(); - this->stream = this->_stream_create(); - this->buffer = this->_buffer_create(); - - this->_stream_start(); - this->_trigger_exposure(); + if (this->is_acquiring()) + { + CALL(arv_camera_abort_acquisition, this->camera); + this->stop_acquisition(); + } } -void ArvGeneric::exposure_abort(void) +void ArvGeneric::_get_image(ArvGeneric::HandleImgCB fn_image_callback, + ArvBuffer * buf) { - if (this->_stream_active()) + if (fn_image_callback != nullptr) { - arv_camera_abort_acquisition(this->camera, &(this->error)); - this->_stream_stop(); + size_t size; + uint8_t const * data = (uint8_t const *)arv_buffer_get_data(buf, &size); + fn_image_callback(data, size); } } -void ArvGeneric::_get_image(void (*fn_image_callback)(void *const, uint8_t const *const, size_t), void *const usr_ptr) +ARV_EXPOSURE_STATUS ArvGeneric::exposure_poll(ArvGeneric::HandleImgCB fn_image_callback) { - ArvBuffer *const popped_buf = arv_stream_timeout_pop_buffer(this->stream, 100000); - if ((popped_buf != nullptr) && (popped_buf == this->buffer) && - arv_buffer_get_status(this->buffer) == ARV_BUFFER_STATUS_SUCCESS) + if (!this->is_exposing_single()) + return ARV_EXPOSURE_UNKNOWN; + { - if (fn_image_callback != nullptr) - { - size_t size; - uint8_t const *const data = (uint8_t const *const)arv_buffer_get_data(this->buffer, &size); - fn_image_callback(usr_ptr, data, size); + /* There is no point in examining the buffer status until it in the + * output queue of the stream. */ + gint n_inputs = 0, n_outputs = 0; + arv_stream_get_n_buffers(this->stream, &n_inputs, &n_outputs); + if (n_outputs < 1) { + if (n_inputs == 0) { + LOG_ERROR("Waiting for image data without input buffer!"); + } + return ARV_EXPOSURE_BUSY; } } - else + + ArvBuffer * buf = arv_stream_timeout_pop_buffer( + this->stream, static_cast(this->get_exposure().val())); + if (buf == nullptr) + return ARV_EXPOSURE_BUSY; + ARV_EXPOSURE_STATUS retval; + + ::ArvBufferStatus const status = arv_buffer_get_status(buf); + switch (status) { - //TODO: failure... + case ARV_BUFFER_STATUS_CLEARED: + retval = ARV_EXPOSURE_BUSY; + break; + case ARV_BUFFER_STATUS_FILLING: + retval = ARV_EXPOSURE_FILLING; + break; + case ARV_BUFFER_STATUS_UNKNOWN: + retval = ARV_EXPOSURE_UNKNOWN; + break; + case ARV_BUFFER_STATUS_SUCCESS: + this->_get_image(fn_image_callback, buf); + this->stop_acquisition(); + retval = ARV_EXPOSURE_FINISHED; + break; + case ARV_BUFFER_STATUS_TIMEOUT: + case ARV_BUFFER_STATUS_MISSING_PACKETS: + case ARV_BUFFER_STATUS_WRONG_PACKET_ID: + case ARV_BUFFER_STATUS_SIZE_MISMATCH: + case ARV_BUFFER_STATUS_ABORTED: + this->stop_acquisition(); + retval = ARV_EXPOSURE_FAILED; + break; + default: + retval = ARV_EXPOSURE_UNKNOWN; + break; } + + if (this->is_acquiring()) + /* give buffer back to stream and let the stream own buffer memory. + * This case seems unlikely. + */ + arv_stream_push_buffer(this->stream, buf); + else + // free orphaned buffer + g_clear_object(&buf); + return retval; } -ARV_EXPOSURE_STATUS ArvGeneric::exposure_poll(void (*fn_image_callback)(void *const, uint8_t const *const, size_t), - void *const usr_ptr) +ARV_EXPOSURE_STATUS ArvGeneric::next_streaming_image(ArvGeneric::HandleImgCB fn_image_callback) { - if (!this->_stream_active()) + auto get_n_inputs = [this]() { + /* There is no point in examining the buffer status until it in the + * output queue of the stream. */ + gint n_inputs = 0, n_outputs = 0; + arv_stream_get_n_buffers(this->stream, &n_inputs, &n_outputs); + return static_cast(n_inputs); + }; + + if (!this->is_streaming()) return ARV_EXPOSURE_UNKNOWN; - ::ArvBufferStatus const status = arv_buffer_get_status(this->buffer); + auto pre_inputs = get_n_inputs(); + ArvBuffer *const buf = arv_stream_timeout_pop_buffer( + this->stream, static_cast(this->get_exposure().val())); + if (buf == nullptr) { + //LOG_DEBUG("Timed out getting next streaming image"); + auto post_inputs = get_n_inputs(); + if (pre_inputs != post_inputs) + LOGF_ERROR("Leaked %d buffers when buffer pop returned NULL!!!", + (pre_inputs - post_inputs)); + return ARV_EXPOSURE_BUSY; + } + + ::ArvBufferStatus const status = arv_buffer_get_status(buf); + ARV_EXPOSURE_STATUS retval = ARV_EXPOSURE_UNKNOWN; switch (status) { + case ARV_BUFFER_STATUS_TIMEOUT: case ARV_BUFFER_STATUS_CLEARED: - return ARV_EXPOSURE_BUSY; + retval = ARV_EXPOSURE_BUSY; + break; case ARV_BUFFER_STATUS_FILLING: - return ARV_EXPOSURE_FILLING; + retval = ARV_EXPOSURE_FILLING; + break; case ARV_BUFFER_STATUS_UNKNOWN: - return ARV_EXPOSURE_UNKNOWN; + retval = ARV_EXPOSURE_UNKNOWN; + break; case ARV_BUFFER_STATUS_SUCCESS: - this->_get_image(fn_image_callback, usr_ptr); - this->_stream_stop(); - return ARV_EXPOSURE_FINISHED; - case ARV_BUFFER_STATUS_TIMEOUT: + this->_get_image(fn_image_callback, buf); + retval = ARV_EXPOSURE_FINISHED; + break; case ARV_BUFFER_STATUS_MISSING_PACKETS: case ARV_BUFFER_STATUS_WRONG_PACKET_ID: case ARV_BUFFER_STATUS_SIZE_MISMATCH: case ARV_BUFFER_STATUS_ABORTED: - this->_stream_stop(); - return ARV_EXPOSURE_FAILED; + LOG_ERROR("Exposure failed, stopping acquisition"); + this->stop_acquisition(); + retval = ARV_EXPOSURE_FAILED; + break; default: - return ARV_EXPOSURE_UNKNOWN; + retval = ARV_EXPOSURE_UNKNOWN; + break; } + + // give buffer back to stream and let the stream own buffer memory. + //LOG_DEBUG("Pushing buffer back to stream"); + arv_stream_push_buffer(this->stream, buf); + return retval; } diff --git a/indi-gige/src/ArvGeneric.h b/indi-gige/src/ArvGeneric.h index ea77dcc74..e8d686214 100644 --- a/indi-gige/src/ArvGeneric.h +++ b/indi-gige/src/ArvGeneric.h @@ -19,6 +19,9 @@ #ifndef CPP_ARV_GENERIC_H #define CPP_ARV_GENERIC_H +#include +#include + //#extern "C" { #include #include @@ -32,70 +35,100 @@ using namespace arv; class ArvGeneric : public arv::ArvCamera { public: - ArvGeneric(void *camera_device); - ~ArvGeneric(); - - bool is_connected(); - bool is_exposing(); - bool connect(); - bool disconnect(); - const char *vendor_name(); - const char *model_name(); - const char *device_id(); - int get_frame_byte_size(); - min_max_property get_bin_x(); - min_max_property get_bin_y(); - min_max_property get_x_offset(); - min_max_property get_y_offset(); - min_max_property get_width(); - min_max_property get_height(); - min_max_property get_bpp(); - min_max_property get_pixel_pitch(); - min_max_property get_exposure(); - min_max_property get_gain(); - min_max_property get_frame_rate(); - - void set_bin(int const bin_x, int const bin_y); - void set_geometry(int const x, int const y, int const w, int const h); - void update_geometry(void); - void set_exposure_time(double const val); - void set_gain(double const val); - - void exposure_start(void); - void exposure_abort(void); - ARV_EXPOSURE_STATUS exposure_poll(void (*fn_image_callback)(void *const, uint8_t const *const, size_t), - void *const usr_ptr); + using arv::ArvCamera::HandleImgCB; + + ArvGeneric(std::string device_id, std::string model_name); + virtual ~ArvGeneric(); + + virtual bool is_connected() override; + virtual bool is_exposing_single() override; + virtual bool is_streaming() override; + virtual bool connect() override; + virtual bool disconnect() override; + virtual const char *vendor_name() override; + virtual const char *model_name() override; + virtual const char *device_id() override; + virtual int get_frame_byte_size() override; + virtual min_max_property get_bin_x() override; + virtual min_max_property get_bin_y() override; + virtual min_max_property get_x_offset() override; + virtual min_max_property get_y_offset() override; + virtual min_max_property get_width() override; + virtual min_max_property get_height() override; + virtual min_max_property get_bpp() override; + virtual min_max_property get_pixel_pitch() override; + virtual min_max_property get_exposure() override; + virtual min_max_property get_gain() override; + virtual min_max_property get_frame_rate() override; + virtual bool has_feature(const char * feature) override; + virtual double get_float(const char * feature) override; + + virtual void set_bin(int const bin_x, int const bin_y) override; + virtual std::pair update_bin(void) override; + virtual void set_geometry(int const x, int const y, int const w, int const h) override; + virtual std::tuple update_geometry(void) override; + virtual void set_exposure_time(double const val) override; + virtual void set_gain(double const val) override; + + virtual void exposure_start(void) override; + virtual void exposure_abort(void) override; + virtual ARV_EXPOSURE_STATUS exposure_poll(HandleImgCB fn_image_callback) override; + virtual ARV_EXPOSURE_STATUS next_streaming_image(HandleImgCB fn_image_callback) override; + virtual void stop_streaming(void) override; protected: + virtual void start_streaming_impl(unsigned int n_buffers) override; void _init(void); - bool _configure(void); - void _test_exposure_and_abort(void); + virtual bool _configure(void); + + private: template bool _get_bounds(void (*fn_arv_bounds)(::ArvCamera *, T *min, T *max, GError**), min_max_property *prop); template - void _set_cam_exposure_property(void (*arv_set)(::ArvCamera *, T, GError**), min_max_property *prop, T const new_val); + bool _get_incr(T (*fn_arv_incr)(::ArvCamera *, GError**), min_max_property *prop); + + /** Set a given property where it should not be changed during a + * single-frame acquisition and attemping to do so should cause the + * acquisition to be aborted. + * This function does *not* stop acquisition if a change is made during a + * streaming operation. + * If the 'get' function is also passed, the value of the parameter will be + * queried from the camera after the 'set' function has been called. This + * can be useful since the camera can often demote/promote settings to some + * gridded pattern internal to the camera. + */ + template + void set_cam_exposure_property(void (*arv_set)(::ArvCamera *, T, GError**), + min_max_property *prop, T const new_val, + T (*arv_get)(::ArvCamera *, GError**) = nullptr); + + const char * getDeviceName() { return this->device_id(); } + // Variadic template error handler function with no return value + template + void call_log(const char *func_description, Func &&func, Args&&... args); + // Variadic template error handler function with no return value + template + auto call_log_return(const char *func_description, Func &&func, Args&&... args); - const char *_str_val(const char *s); - bool _get_initial_config(); - bool _set_initial_config(); - void _get_image(void (*fn_image_callback)(void *const, uint8_t const *const, size_t), void *const usr_ptr); + protected: + virtual bool _get_initial_config(); + virtual bool _set_initial_config(); + void _get_image(HandleImgCB fn_image_callback, ArvBuffer * buf); /* aravis library state variables */ ::ArvCamera *camera; ::ArvDevice *dev; - ::ArvStream *stream; - ::ArvBuffer *buffer; - ::GError *error; + ::ArvStream *stream; /// Hold all buffers in queues until freed /* streaming, capturing functions */ - ::ArvStream *_stream_create(void); - ::ArvBuffer *_buffer_create(void); - bool _stream_active(); - void _stream_start(); - void _stream_stop(); - void _trigger_exposure(); + /** Creates the current stream and pushes buffers to its input queue. */ + void create_stream(unsigned int n_buffers = 1); + void start_acquisition(unsigned int n_buffers, ArvAcquisitionMode mode); + /** Stops all acquisition. */ + void stop_acquisition(); - bool stream_active; + std::atomic single_acquisition_active; + std::atomic stream_active; /* Camera properties */ struct @@ -113,9 +146,9 @@ class ArvGeneric : public arv::ArvCamera min_max_property gain; min_max_property frame_rate; - const char *vendor_name; - const char *model_name; - const char *device_id; + std::string vendor_name; + std::string model_name; + std::string device_id; } cam; }; diff --git a/indi-gige/src/ArvInterface.h b/indi-gige/src/ArvInterface.h index 952d14529..9963cc58a 100644 --- a/indi-gige/src/ArvInterface.h +++ b/indi-gige/src/ArvInterface.h @@ -19,8 +19,14 @@ #ifndef CPP_ARV_IFACE_H #define CPP_ARV_IFACE_H -#include -#include +#include +#include + +#include +#include +#include +#include +#include namespace arv { @@ -37,12 +43,13 @@ template class min_max_property { public: - min_max_property() {} - min_max_property(T const min, T const max, T const val) + min_max_property() : _min(0), _max(0), _val(0), _incr(0) {} + min_max_property(T const min, T const max, T const val, T const incr = T(0)) { this->_min = min; this->_max = max; this->_val = val; + this->_incr = incr; } void update(T const min, T const max) @@ -51,8 +58,19 @@ class min_max_property this->_max = max; } - void set(T const new_val) + void set_increment(T const incr) { + this->_incr = incr; + } + + /** Set a new value for the property, obeying max/min limits and following + * incremental restrictions if set (i.e. if not 0). */ + void set(T new_val) + { + if (this->_incr != T(0)) + // enforce alignment to increments + new_val = int((new_val - this->_min)/this->_incr) * this->_incr + this->_min; + if (new_val > this->_max) this->_val = this->_max; else if (new_val < this->_min) @@ -65,19 +83,30 @@ class min_max_property T val() { return this->_val; } T min() { return this->_min; } T max() { return this->_max; } + T incr() { return this->_incr; } private: - T _min, _max, _val; + T _min, _max, _val, _incr; }; class ArvCamera { public: - ArvCamera([[maybe_unused]] void *camera_device) {} - virtual bool connect() = 0; - virtual bool disconnect() = 0; - virtual bool is_connected() = 0; - virtual bool is_exposing() = 0; + typedef std::function HandleImgCB; + + ArvCamera([[maybe_unused]] std::string device_id, + [[maybe_unused]] std::string model_name) {} + virtual ~ArvCamera() = default; + virtual bool connect() = 0; + virtual bool disconnect() = 0; + virtual bool is_connected() = 0; + /** Is a single-frame acquisition is underway? */ + virtual bool is_exposing_single() = 0; + /** Is a multiple-frame acquisition (streaming) is underway? */ + virtual bool is_streaming() = 0; + bool is_acquiring() { + return this->is_exposing_single() || this->is_streaming(); + } /* Get properties */ virtual const char *vendor_name() = 0; @@ -95,28 +124,120 @@ class ArvCamera virtual min_max_property get_exposure() = 0; virtual min_max_property get_gain() = 0; virtual min_max_property get_frame_rate() = 0; + virtual bool has_feature(const char * feature) = 0; + virtual double get_float(const char * feature) = 0; /* Set geometry */ virtual void set_bin(int const bin_x, int const bin_y) = 0; + /** update this class information of binning from the camera hardware. + * @return pair of binning values for x and y. + */ + virtual std::pair update_bin(void) = 0; virtual void set_geometry(int const x, int const y, int const w, int const h) = 0; - virtual void update_geometry(void) = 0; + /** update this class information of geometry from the camera hardware. + * @return tuple of (x, y, w, h). + */ + virtual std::tuple update_geometry(void) = 0; /* Set exposure */ virtual void set_exposure_time(double const val) = 0; virtual void set_gain(double const val) = 0; + /** Start a single-frame acquisition. */ virtual void exposure_start(void) = 0; virtual void exposure_abort(void) = 0; - virtual ARV_EXPOSURE_STATUS exposure_poll(void (*fn_image_callback)(void *const, uint8_t const *const, size_t), - void *const) = 0; + virtual ARV_EXPOSURE_STATUS exposure_poll(HandleImgCB fn_image_callback) = 0; + virtual ARV_EXPOSURE_STATUS next_streaming_image(HandleImgCB fn_image_callback) = 0; + virtual void stop_streaming(void) = 0; + /** Start a multi-frame acquisition (i.e. streaming). */ + void start_streaming(unsigned int n_buffers=10) { + this->start_streaming_impl(n_buffers); + } + + protected: + virtual void start_streaming_impl(unsigned int n_buffers) = 0; }; class ArvFactory { public: - static ArvCamera *find_first_available(void); - - //TODO: add iterative support to add all discovered cameras + /** Find, instantiate, and return a unique_ptr of the first available + * ArvCamera. + */ + static std::unique_ptr find_first_available(void); + + /** Camera device index, used by the camera iterator and to instantiate an + * ArvCamera. + */ + class Index { + int index; + public: + Index(int index) : index(index) { } + + /** Attempt to create a camera instance for the camera index. + * Does *not* update the device list. + */ + std::unique_ptr instantiate() const; + + Index & operator++() { + ++this->index; + return *this; + } + bool operator==(const Index & other) const { + return this->index == other.index; + } + bool operator!=(const Index & other) const { + return this->index != other.index; + } + }; + + /** Camera index iterator. */ + class Iterator { + Index index; + public: + // 1. Mandatory STL iterator traits + using iterator_category = std::input_iterator_tag; + using value_type = Index; + using difference_type = std::ptrdiff_t; + using pointer = const Index*; + using reference = const Index&; + + Iterator(Index index) : index(index) { } + + /** Dereference operator. */ + reference operator*() const { + return index; + } + + /** Prefix increment operator. */ + Iterator& operator++() { + ++this->index; + return *this; + } + + // Postfix increment operator + Iterator operator++(int) { + Iterator temp = *this; + ++(*this); + return temp; + } + + // Comparison operators + bool operator!=(const Iterator& other) const { + return this->index != other.index; + } + + bool operator==(const Iterator& other) const { + return this->index == other.index; + } + }; + + /** Updates the list of devices and returns the iterator to the first. */ + Iterator begin(); + /** Returns an iterator to the currently known end. + * This function does *not* update the list of devices. + */ + Iterator end(); }; } /* Namepsace */ diff --git a/indi-gige/src/BlackFly.cpp b/indi-gige/src/BlackFly.cpp index e7079067b..a1f5cc725 100644 --- a/indi-gige/src/BlackFly.cpp +++ b/indi-gige/src/BlackFly.cpp @@ -19,9 +19,12 @@ #include "ArvGeneric.h" #include "BlackFly.h" +#include + using namespace arv; -BlackFly::BlackFly(void *camera_device) : ArvGeneric(camera_device) +BlackFly::BlackFly(std::string device_id, std::string model_name) +: ArvGeneric(device_id, model_name) { printf("%s\n", __PRETTY_FUNCTION__); } @@ -41,7 +44,6 @@ bool BlackFly::_custom_settings() } gboolean result; - guint32 val; GError *error = nullptr; int i; @@ -69,17 +71,6 @@ bool BlackFly::_custom_settings() return false; } -bool BlackFly::connect(void) -{ - printf("%s\n", __PRETTY_FUNCTION__); - bool const ret = ArvGeneric::connect(); - if (ret) - { - this->_configure(); - } - return ret; -} - void BlackFly::_fixup(void) { ::ArvDevice *dev = this->dev; @@ -94,7 +85,6 @@ void BlackFly::_fixup(void) } gboolean result; - guint32 val; GError *error = nullptr; int i; @@ -115,13 +105,6 @@ void BlackFly::exposure_start(void) ArvGeneric::exposure_start(); } -bool BlackFly::_configure(void) -{ - printf("%s\n", __PRETTY_FUNCTION__); - this->_set_initial_config(); - return this->_get_initial_config(); -} - bool BlackFly::_get_initial_config(void) { printf("%s\n", __PRETTY_FUNCTION__); @@ -133,11 +116,12 @@ bool BlackFly::_get_initial_config(void) /* Probably can find this somewhere in genicam, too, but it depends on framerate * and the maximum exposure is consequently not published */ this->cam.exposure.update(5000, 11900000); + return true; } bool BlackFly::_set_initial_config(void) { printf("%s\n", __PRETTY_FUNCTION__); ArvGeneric::_set_initial_config(); - this->_custom_settings(); + return this->_custom_settings(); } diff --git a/indi-gige/src/BlackFly.h b/indi-gige/src/BlackFly.h index 9250d70e7..ebb64a331 100644 --- a/indi-gige/src/BlackFly.h +++ b/indi-gige/src/BlackFly.h @@ -19,17 +19,18 @@ #ifndef CPP_ARV_BLACKFLY_H #define CPP_ARV_BLACKFLY_H +#include + class BlackFly : public ArvGeneric { public: - BlackFly(void *camera_device); - bool connect(); - void exposure_start(void); + BlackFly(std::string device_id, std::string model_name); + virtual ~BlackFly() = default; + virtual void exposure_start(void) override; protected: - bool _configure(void); - bool _get_initial_config(); - bool _set_initial_config(); + virtual bool _get_initial_config() override; + virtual bool _set_initial_config() override; private: bool _custom_settings(); diff --git a/indi-gige/src/indi_gige.cpp b/indi-gige/src/indi_gige.cpp index ccf9e9569..4bc46668e 100644 --- a/indi-gige/src/indi_gige.cpp +++ b/indi-gige/src/indi_gige.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include "indidevapi.h" #include "eventloop.h" @@ -42,8 +43,32 @@ #define TIMER_US_TO_MS (1000) #define TIMER_US_TO_S (1000000) -#define TIMER_TICK_MS (100) -#define CAPS (CCD_CAN_ABORT | CCD_CAN_BIN | CCD_CAN_SUBFRAME) +#define CAPS (CCD_CAN_ABORT | CCD_CAN_BIN | CCD_CAN_SUBFRAME | CCD_HAS_STREAMING) + +/* NOTE ABOUT BINNING: + * At least for FLIR GiGE cameras, changing binning changes the apparent size of + * the chip, such that the region of interest is configured using a smaller + * number of pixels. This appears to be contrary to how INDI expects binning to + * be configured. Following the CCD Simulator (if that is a decent example of + * implementing a INDI::CCD device, it rather appears that the region of + * interest is still expected to be set using the entire available Width/Height. + * It therefore appears necessary to mitigate this mismatch since (at least for + * ekos) the client appears to mess up the apparent image if binning is + * configured but a smaller region is selected, but in reality the entire + * avaialble sensor was binned. + * + * So, for now (as of 26 Feb 2026), even when binning is used, this driver will + * use the full MaxWidth and MaxHeight manually divided by the binning to select + * the region of interest. + * + * This means that any use of get_x_offset().val(), get_y_offset().val(), + * get_width().val(), or get_height().val() from the GigECCD::camera object must + * be manually multiplied or divided by the values from get_bin_x().val() and + * get_bin_y().val() as appropriate. Similarly, camera->set_geometry(...) must + * be given values that are divided by the appropriate binning. + * + * TODO: verify this behavior on GiGE cameras from other vendors. + */ static class Loader { @@ -51,9 +76,12 @@ static class Loader public: Loader() { - arv::ArvCamera *camera = arv::ArvFactory::find_first_available(); - cameras.push_back(std::unique_ptr(new GigECCD(camera))); - IDLog("Found Camera: %s\n", camera->model_name()); + for (auto camera_index : arv::ArvFactory()) { + auto camera = camera_index.instantiate(); + IDLog("Found Camera: %s\n", camera->device_id()); + auto gigeccd = new GigECCD(std::move(camera)); + cameras.push_back(std::unique_ptr(gigeccd)); + } } } loader; @@ -62,11 +90,10 @@ const char *GigECCD::getDefaultName() return "GigE CCD"; } -GigECCD::GigECCD(arv::ArvCamera *camera) +GigECCD::GigECCD(std::unique_ptr camera) + : camera(std::move(camera)) { - this->camera = camera; - snprintf(this->name, sizeof(this->name), "GigE CCD%s", this->camera->model_name()); - setDeviceName(this->name); + setDeviceName(this->camera->device_id()); } GigECCD::~GigECCD() @@ -84,19 +111,25 @@ bool GigECCD::initProperties() bool GigECCD::_update_geometry(void) { + std::lock_guard lock(this->camera_mutex); /* Get actual values */ - this->camera->update_geometry(); + auto [x, y, w, h] = this->camera->update_geometry(); + + /* As per note at begin of this file, we manually manage the region of + * interest conversion between the camera and INDI because the camera + * changes the apparent region of interest available depending on binning, + * while INDI does not. */ + auto bin_x = this->camera->get_bin_x().val(), + bin_y = this->camera->get_bin_y().val(); /* Sync these with INDI */ - PrimaryCCD.setBin(this->camera->get_bin_x().val(), this->camera->get_bin_y().val()); - PrimaryCCD.setFrame(this->camera->get_x_offset().val(), this->camera->get_y_offset().val(), - this->camera->get_width().val(), this->camera->get_height().val()); + INDI::CCD::UpdateCCDFrame(x*bin_x, y*bin_y, w*bin_x, h*bin_y); /* Sanity checks, reserve buffers */ - int const width = this->camera->get_width().val(); - int const height = this->camera->get_height().val(); int const frame_byte_size = this->camera->get_frame_byte_size(); - int const indi_bufsize = PrimaryCCD.getSubW() * PrimaryCCD.getSubH() * PrimaryCCD.getBPP() / 8; + int const indi_bufsize = (PrimaryCCD.getSubW() / PrimaryCCD.getBinX()) + * (PrimaryCCD.getSubH() / PrimaryCCD.getBinY()) + * PrimaryCCD.getBPP() / 8; if (indi_bufsize != frame_byte_size) { @@ -109,15 +142,29 @@ bool GigECCD::_update_geometry(void) LOGF_INFO("Reserving INDI image buffer size %i bytes", indi_bufsize); PrimaryCCD.setFrameBufferSize(frame_byte_size); } + + this->Streamer->setPixelFormat(INDI_MONO, PrimaryCCD.getBPP()); + return true; } void GigECCD::_update_indi_properties(void) { + std::lock_guard lock(this->camera_mutex); LOG_INFO("update_indi_properties()"); - IUFillNumber(&this->indiprop_gain[0], "Range", "", "%g", (double)this->camera->get_gain().min(), - (double)this->camera->get_gain().max(), 1., (double)this->camera->get_gain().val()); - IUFillNumberVector(&this->indiprop_gain_prop, this->indiprop_gain, 1, getDeviceName(), "Gain", "", MAIN_CONTROL_TAB, - IP_RW, 60, IPS_IDLE); + + // Gain + GainNP[0].fill("GAIN", "value", "%.f", + (double)this->camera->get_gain().min(), + (double)this->camera->get_gain().max(), 1., + (double)this->camera->get_gain().val()); + GainNP.fill(getDeviceName(), "CCD_GAIN", "Gain", MAIN_CONTROL_TAB, IP_RW, 60, IPS_IDLE); + + auto pitch = this->camera->get_pixel_pitch(); + PixelSizeNP[0].fill("PIXEL_SIZE", "Size [μm]", "%.2f", + pitch.min(), pitch.max(), .1, pitch.val()); + PixelSizeNP.fill(getDeviceName(), "CCD_PIXEL_SIZE", "Pixel", + IMAGE_SETTINGS_TAB, + pitch.max() == pitch.min() ? IP_RO : IP_RW, 60, IPS_IDLE); IUFillText(&indiprop_info[0], "Vendor Name", "", this->camera->vendor_name()); IUFillText(&indiprop_info[1], "Model Name", "", this->camera->model_name()); @@ -126,13 +173,23 @@ void GigECCD::_update_indi_properties(void) 0, IPS_IDLE); defineProperty(&indiprop_info_prop); - defineProperty(&this->indiprop_gain_prop); + defineProperty(this->GainNP); + defineProperty(this->PixelSizeNP); + if (this->camera->has_feature("DeviceTemperature")) { + this->TemperatureNP.setPermission(IP_RO); + defineProperty(this->TemperatureNP); + } } void GigECCD::_delete_indi_properties(void) { - this->deleteProperty(this->indiprop_gain_prop.name); + this->deleteProperty(this->GainNP); + this->deleteProperty(this->PixelSizeNP); this->deleteProperty(this->indiprop_info_prop.name); + if (TemperatureNP.getPermission() == IP_RO) { + this->TemperatureNP.setPermission(IP_RW); + this->deleteProperty(this->TemperatureNP); + } } //Initial call @@ -140,6 +197,7 @@ bool GigECCD::updateProperties() { INDI::CCD::updateProperties(); + std::lock_guard lock(this->camera_mutex); if (this->camera->is_connected()) { this->_update_indi_properties(); @@ -147,8 +205,9 @@ bool GigECCD::updateProperties() this->camera->get_bpp().val(), this->camera->get_pixel_pitch().val(), this->camera->get_pixel_pitch().val()); + this->_update_bin(); (void)this->_update_geometry(); - this->timer_id = this->SetTimer(TIMER_TICK_MS); + this->timer_id = this->SetTimer(this->getCurrentPollingPeriod()); } else { @@ -161,18 +220,83 @@ bool GigECCD::updateProperties() bool GigECCD::Connect() { + std::lock_guard lock(this->camera_mutex); IDLog("Connect to Camera: %s\n", camera->model_name()); + this->start_streaming_thread(); return camera->connect(); } bool GigECCD::Disconnect() { LOGF_INFO("%s", __PRETTY_FUNCTION__); -#if 0 - //TODO: re-iterate and acquire proper camera from AvrFactory (based on ID?) + this->stop_streaming_thread(); + std::lock_guard lock(this->camera_mutex); return camera->disconnect(); -#endif - return true; +} + +void GigECCD::start_streaming_thread() +{ + auto receive_image = [this](uint8_t const *const data, size_t size) + { + /* locked as per indiccd.h guidance. */ + std::lock_guard lock(this->ccdBufferLock); + this->Streamer->newFrame(data, size); + //LOG_DEBUG("injected image into stream manager"); + }; + + auto streaming_worker = [this, receive_image]() { + while (!this->streaming_thread_stop_requested.load()) { + std::unique_lock lock(this->camera_mutex); + if (!this->streaming_thread_active.load()) { + if (this->camera->is_streaming()) + // Have been streaming; have now been requested to stop + this->camera->stop_streaming(); + + this->streaming_thread_condition.wait(lock); + + if (!this->streaming_thread_active.load()) { + LOG_INFO("Quit probably requested for streaming thread"); + // probably just requested to quit so go to while loop test + continue; + } + + /* While we *weren't* streaming, we are now requested to start + * streaming. + */ + LOG_INFO("Starting streaming"); + this->camera->start_streaming(); + } + + // camera_mutex should be locked at this point + auto status = camera->next_streaming_image(receive_image); + switch (status) { + case arv::ARV_EXPOSURE_UNKNOWN: + case arv::ARV_EXPOSURE_FAILED: { + LOG_ERROR("Streaming acquisition had unknown failure"); + this->camera->exposure_abort(); + break; + } + default: + break; + } + } + + LOG_INFO("Shutting down streaming thread"); + }; + + this->streaming_thread = std::thread(streaming_worker); +} + +void GigECCD::stop_streaming_thread() +{ + LOG_INFO("Requesting streaming stop"); + { + std::lock_guard lock(this->ccdBufferLock); + this->streaming_thread_active = false; + this->streaming_thread_stop_requested = true; + } + this->streaming_thread_condition.notify_all(); + this->streaming_thread.join(); } bool GigECCD::StartExposure(float duration) @@ -182,32 +306,69 @@ bool GigECCD::StartExposure(float duration) if (PrimaryCCD.getFrameType() == INDI::CCDChip::BIAS_FRAME) duration = 0; + std::lock_guard lock(this->camera_mutex); camera->set_exposure_time((double)(duration)*1000000.0); + PrimaryCCD.setExposureDuration(duration); // to ensure FITS correct header TIME_VAL_INIT(&this->exposure_transfer_time); TIME_VAL_GET(&this->exposure_start_time); camera->exposure_start(); - return camera->is_exposing(); + return camera->is_exposing_single(); } bool GigECCD::AbortExposure() { LOGF_INFO("%s", __PRETTY_FUNCTION__); + std::lock_guard lock(this->camera_mutex); camera->exposure_abort(); return true; } +bool GigECCD::StartStreaming() +{ + { + std::lock_guard lock(this->camera_mutex); + if (this->camera->is_exposing_single()) { + LOG_ERROR("Invalid streaming request during single-frame acquisition"); + return false; + } + + this->camera->set_exposure_time(Streamer->getTargetExposure()*1000000.0); + LOG_INFO("Requesting streaming start"); + this->streaming_thread_active = true; + } + this->streaming_thread_condition.notify_all(); + return true; +} + +bool GigECCD::StopStreaming() +{ + LOG_INFO("Requesting streaming stop"); + this->streaming_thread_active = false; + this->streaming_thread_condition.notify_all(); + return true; +} + void GigECCD::_update_image(uint8_t const *const data, size_t size) { - LOGF_INFO("Receiving %i bytes image", size); + LOGF_INFO("Received %i bytes image", size); size_t const frame_buf_size = PrimaryCCD.getFrameBufferSize(); if ((size == frame_buf_size) && (data != nullptr)) { - uint8_t *const image = PrimaryCCD.getFrameBuffer(); - memcpy(image, (void *const)data, frame_buf_size); + { /* locked as per indiccd.h guidance. */ + std::lock_guard lock(this->ccdBufferLock); + uint8_t *const image = PrimaryCCD.getFrameBuffer(); + memcpy(image, (void const*)data, frame_buf_size); + } + if (TemperatureNP.getPermission() == IP_RO) { + std::lock_guard lock(this->camera_mutex); + this->TemperatureNP[0].setValue(this->camera->get_float("DeviceTemperature")); + this->TemperatureNP.setState(IPS_OK); + this->TemperatureNP.apply(); + } this->ExposureComplete(&PrimaryCCD); } else @@ -218,23 +379,21 @@ void GigECCD::_update_image(uint8_t const *const data, size_t size) } } -void GigECCD::_receive_image_hook(void *const class_ptr, uint8_t const *const data, size_t size) -{ - GigECCD *const cls = static_cast(class_ptr); - cls->_update_image(data, size); -} - void GigECCD::_handle_failed(void) { LOG_ERROR("Failure occurred, filling image with black"); + std::lock_guard lock(this->camera_mutex); camera->exposure_abort(); PrimaryCCD.setExposureLeft(0); - /* Fill with black */ - uint8_t *const image = PrimaryCCD.getFrameBuffer(); - memset(image, 0, PrimaryCCD.getFrameBufferSize()); + { /* locked as per indiccd.h guidance. */ + std::lock_guard lock(this->ccdBufferLock); + /* Fill with black */ + uint8_t *const image = PrimaryCCD.getFrameBuffer(); + memset(image, 0, PrimaryCCD.getFrameBufferSize()); + } this->ExposureComplete(&PrimaryCCD); } @@ -247,6 +406,7 @@ void GigECCD::_handle_timeout(struct timeval *const tv, uint32_t timeout_us) struct timeval now; TIME_VAL_GET(&now); + std::lock_guard lock(this->camera_mutex); uint32_t const elapsed = ((TIME_VAL_US(&now)) - (TIME_VAL_US(tv))); uint32_t const exposure_time = (uint32_t)this->camera->get_exposure().val(); uint32_t const time_left = exposure_time - elapsed; @@ -256,26 +416,37 @@ void GigECCD::_handle_timeout(struct timeval *const tv, uint32_t timeout_us) else PrimaryCCD.setExposureLeft((float)time_left / (float)TIMER_US_TO_S); - if (elapsed > timeout_us) + if (elapsed > timeout_us) { + LOGF_ERROR("Image acquisition timed out (>%d μs)", timeout_us); this->_handle_failed(); + } } void GigECCD::TimerHit() { - this->timer_id = this->SetTimer(TIMER_TICK_MS); - if (!this->camera->is_connected() || !this->camera->is_exposing()) + this->timer_id = this->SetTimer(this->getCurrentPollingPeriod()); + if (!this->camera->is_connected() || !this->camera->is_exposing_single()) return; - arv::ARV_EXPOSURE_STATUS const status = camera->exposure_poll(this->_receive_image_hook, this); + std::lock_guard lock(this->camera_mutex); + auto receive_image = [this](uint8_t const *const data, size_t size) + { + this->_update_image(data, size); + }; + + + auto status = camera->exposure_poll(receive_image); switch (status) { case arv::ARV_EXPOSURE_FINISHED: - /* Nothing to do, ArvCamera automatically unsets is_exposing */ + // Nothing to do, ArvCamera automatically unsets is_exposing_single break; case arv::ARV_EXPOSURE_UNKNOWN: - case arv::ARV_EXPOSURE_FAILED: + case arv::ARV_EXPOSURE_FAILED: { + LOG_ERROR("Image acquisition had unknown failure"); this->_handle_failed(); break; + } case arv::ARV_EXPOSURE_FILLING: this->_handle_timeout(&this->exposure_transfer_time, TIMER_TRANSFER_TIMEOUT_US); break; @@ -290,16 +461,30 @@ bool GigECCD::ISNewNumber(const char *dev, const char *name, double values[], ch { if (!strcmp(dev, this->getDeviceName())) { - if (!strcmp(name, this->indiprop_gain_prop.name)) + if (GainNP.isNameMatch(name)) { - IUUpdateNumber(&this->indiprop_gain_prop, values, names, n); - this->camera->set_gain(this->indiprop_gain[0].value); - this->indiprop_gain_prop.s = IPS_OK; + GainNP.update(values, names, n); + GainNP.setState(IPS_OK); + std::lock_guard lock(this->camera_mutex); + this->camera->set_gain(this->GainNP[0].getValue()); /* Get-back from camera system */ - double actual_value = this->camera->get_gain().val(); - IUUpdateNumber(&this->indiprop_gain_prop, &actual_value, names, n); - IDSetNumber(&this->indiprop_gain_prop, nullptr); + this->GainNP[0].setValue(this->camera->get_gain().val()); + + GainNP.apply(); + saveConfig(PixelSizeNP); + return true; + } + + if (PixelSizeNP.isNameMatch(name)) + { + PixelSizeNP.update(values, names, n); + PixelSizeNP.setState(IPS_OK); + PixelSizeNP.apply(); + + auto dx = PixelSizeNP[0].getValue(); + PrimaryCCD.setPixelSize(dx, dx); + saveConfig(PixelSizeNP); return true; } } @@ -309,22 +494,56 @@ bool GigECCD::ISNewNumber(const char *dev, const char *name, double values[], ch bool GigECCD::UpdateCCDFrame(int x, int y, int w, int h) { - LOGF_INFO("%s x=%i y=%i w=%i h=%i", __PRETTY_FUNCTION__, x, y, w, h); + std::lock_guard lock(this->camera_mutex); + + /* As per note at begin of this file, we manually manage the region of + * interest conversion between the camera and INDI because the camera + * changes the apparent region of interest available depending on binning, + * while INDI does not. */ + auto bin_x = this->camera->get_bin_x().val(), + bin_y = this->camera->get_bin_y().val(); - this->camera->set_geometry(x, y, w, h); + LOGF_INFO("%s x=%i y=%i w=%i h=%i binx=%i biny=%i", + __PRETTY_FUNCTION__, x, y, w, h, bin_x, bin_y); + + this->camera->set_geometry(x/bin_x, y/bin_y, w/bin_x, h/bin_y); return this->_update_geometry(); } bool GigECCD::UpdateCCDBin(int binx, int biny) { LOGF_INFO("%s binx=%i biny=%i", __PRETTY_FUNCTION__, binx, biny); + std::lock_guard lock(this->camera_mutex); camera->set_bin(binx, biny); + this->_update_bin(); return UpdateCCDFrame(PrimaryCCD.getSubX(), PrimaryCCD.getSubY(), PrimaryCCD.getSubW(), PrimaryCCD.getSubH()); } +void GigECCD::_update_bin(void) { + auto [bx, by] = this->camera->update_bin(); /* Get actual values */ + + /* Sync actuals values to INDI */ + INDI::CCD::UpdateCCDBin(bx, by); +} + bool GigECCD::UpdateCCDFrameType(INDI::CCDChip::CCD_FRAME fType) { LOGF_INFO("%s", __PRETTY_FUNCTION__); PrimaryCCD.setFrameType(fType); return true; } + +void GigECCD::addFITSKeywords(INDI::CCDChip *targetChip, std::vector &fitsKeyword) +{ + INDI::CCD::addFITSKeywords(targetChip, fitsKeyword); + + fitsKeyword.push_back({"GAIN", GainNP[0].getValue(), 3, "Gain"}); +} + +bool GigECCD::saveConfigItems(FILE *fp) +{ + INDI::CCD::saveConfigItems(fp); + GainNP.save(fp); + PixelSizeNP.save(fp); + return true; +} diff --git a/indi-gige/src/indi_gige.h b/indi-gige/src/indi_gige.h index 3792feb89..350105351 100644 --- a/indi-gige/src/indi_gige.h +++ b/indi-gige/src/indi_gige.h @@ -21,7 +21,12 @@ #define GENERIC_CCD_H #include -#include + +#include +#include +#include +#include +#include #include "ArvInterface.h" @@ -30,46 +35,63 @@ using namespace std; class GigECCD : public INDI::CCD { public: - GigECCD(arv::ArvCamera *camera); + GigECCD(std::unique_ptr camera); virtual ~GigECCD(); - const char *getDefaultName(); + virtual const char *getDefaultName() override; - bool initProperties(); - bool updateProperties(); + virtual bool initProperties() override; + virtual bool updateProperties() override; - bool Connect(); - bool Disconnect(); + virtual bool Connect() override; + virtual bool Disconnect() override; - bool StartExposure(float duration); - bool AbortExposure(); + virtual bool StartExposure(float duration) override; + virtual bool AbortExposure() override; + /** Request streaming to start. + * This should not be called with camera_mutex locked. */ + virtual bool StartStreaming() override; + /** Request streaming to stop. + * This should not be called with camera_mutex locked. */ + virtual bool StopStreaming() override; protected: - void TimerHit(); - virtual bool UpdateCCDFrame(int x, int y, int w, int h); - virtual bool UpdateCCDBin(int binx, int biny); - virtual bool UpdateCCDFrameType(INDI::CCDChip::CCD_FRAME fType); + virtual void TimerHit() override; + virtual bool UpdateCCDFrame(int x, int y, int w, int h) override; + virtual bool UpdateCCDBin(int binx, int biny) override; + virtual bool UpdateCCDFrameType(INDI::CCDChip::CCD_FRAME fType) override; + virtual void addFITSKeywords(INDI::CCDChip *targetChip, std::vector &fitsKeyword) override; + virtual bool saveConfigItems(FILE *fp) override; private: void _delete_indi_properties(void); void _update_indi_properties(void); - bool _update_geometry(void); + void _update_bin(void); /// update binning to INDI from hardware + bool _update_geometry(void); /// update geometry to INDI from hardware void _update_image(uint8_t const *const data, size_t size); - static void _receive_image_hook(void *const class_ptr, uint8_t const *const data, size_t size); void _handle_failed(void); void _handle_timeout(struct timeval *const tv, uint32_t timeout_us); + void start_streaming_thread(); + /** Request streaming to stop and the streaming thread to quit. + * This should not be called with camera_mutex locked. */ + void stop_streaming_thread(); - arv::ArvCamera *camera; - char name[32]; + std::unique_ptr camera; + std::recursive_mutex camera_mutex; int timer_id; struct timeval exposure_start_time; struct timeval exposure_transfer_time; + std::thread streaming_thread; + std::atomic streaming_thread_stop_requested {false}; + std::atomic streaming_thread_active {false}; + std::condition_variable_any streaming_thread_condition; + /* Indi properties */ - INumber indiprop_gain[1]; - INumberVectorProperty indiprop_gain_prop; + INDI::PropertyNumber GainNP {1}; + INDI::PropertyNumber PixelSizeNP {1}; IText indiprop_info[3] {}; ITextVectorProperty indiprop_info_prop;