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;