Commit cd05471d authored by Éric Thiébaut's avatar Éric Thiébaut
Browse files

Start a new multi-threaded way to control the camera

parent 60201ef8
......@@ -52,7 +52,7 @@ TAO_XPA_LIBS = ${TAO_LIBDIR}/libtao-xpa.a $(XPA_LIBS)
# Macros for the Phoenix Frame Grabber Library (FGL):
PHOENIX_STATIC_LIBRARY = libtao-phoenix.a
PHOENIX_SHARED_LIBRARY = libtao-phoenix.so
PHOENIX_OBJECTS = errors.o public.o private.o mikrotron-mc408x.o
PHOENIX_OBJECTS = control.o errors.o public.o private.o mikrotron-mc408x.o
PHOENIX_HEADERS = ${srcdir}/phoenix-private.h \
${srcdir}/phoenix.h \
${srcdir}/coaxpress.h \
......@@ -129,6 +129,9 @@ phoenix-server: ${phoenix_server_SOURCES}
$(CC) ${phoenix_server_CPPFLAGS} ${phoenix_server_CFLAGS} \
${phoenix_server_LDFLAGS} $< -o $@ ${phoenix_server_LIBS}
control.o: ${srcdir}/control.c $(PHOENIX_HEADERS)
$(CC) -c $(PHOENIX_CFLAGS) $< -o $@
public.o: ${srcdir}/public.c $(PHOENIX_HEADERS)
$(CC) -c $(PHOENIX_CFLAGS) $< -o $@
......
This diff is collapsed.
/* http://cyberzoide.developpez.com/graphviz/
reverse communication optimization algorithm
generate the TikZ figure with:
dot -Txdot reverse-fig1.dot | dot2tex --autosize -ftikz --codeonly > reverse-fig1.tkz
*/
digraph AcquistionCommands {
/* Global settings. */
edge [penwidth=2];
/* Stable states. */
node [shape=ellipse, color="/rdylgn10/8", style=filled];
sleeping [label = "Sleeping"];
acquiring [label = "Acquiring"];
/* Final state. */
node [shape=ellipse, color="/rdylgn10/2", style=filled];
done [label = "End"];
/* Transitional states. */
node [shape=ellipse, color="/rdylgn10/5", style=filled];
starting [label = "Starting"];
stopping [label = "Stopping"];
aborting [label = "Aborting"];
/* Transitions induced by "exit" command. */
edge [color="/rdylgn10/1", fontcolor="/rdylgn10/1"];
sleeping -> done [label = "exit"];
starting -> done [label = "exit"];
acquiring -> done [label = "exit"];
stopping -> done [label = "exit"];
aborting -> done [label = "exit"];
/* Transitions to stable states. */
edge [color = "/rdylgn10/10", fontcolor="/rdylgn10/10"];
starting -> acquiring;
stopping -> sleeping;
aborting -> sleeping;
/* Transitions to transitional states induced by commands. */
edge [color = "/rdylgn10/3", fontcolor="/rdylgn10/3"];
sleeping -> starting [label = "start"];
starting -> stopping [label = "stop"];
starting -> aborting [label = "abort"];
acquiring -> stopping [label = "stop"];
acquiring -> aborting [label = "abort"];
}
This diff is collapsed.
# Multi-threaded acquisition and control
This section describes how are managed cameras connected to a Phoenix frame
grabber.
## Introduction
The acquisition and control of the camera is multi-threaded. A controlling
thread, the **controller**, is in charge of openning and initializing the
camera, starting the processing thread and sending commands to this thread.
The processing thread, the **processor** is in charge of processing events
(commands sent by the controller and events sent by the frame grabber) and data
(from freshly acquired frame buffers). The Phoenix **frame grabber** runs the
**acquisition callback** in another thread. This acquisition callback is in
charge of sending events (to signal new frames and errors) to the acquisition
thread.
The communication between the threads is synchronized by a mutex (to lock the
camera) and two condition variables to notify events to the acquisition
thread and to notify changes in the camera state to the controlling thread.
The following **events** are implemented:
- `START` to start acquisition;
- `FRAME` to signal a new available acquisition buffer;
- `ERROR` to signal an error from the frame grabber;
- `STOP` to stop acquisition (current frame is processed);
- `ABORT` to abort acquisition (current frame is ignored);
- `QUIT` to make the program exit;
All events but `FRAME` and `ERROR` which are sent by the frame grabber are
*commands* that are expected to be sent by the controller thread.
During its "life", a camera can have the following states:
- `STARTING`
- `SLEEPING`
- `ACQUIRING`
- `PROCESSING`
- `STOPPING`
- `ABORTING`
- `QUITTING`
The commands control the transistion between states.
A callback is in charge of executing each kind of event. The callbacks may
unlock the camera during the operation but must not return without having
locked the camera.
## Event loop
The event loop is run by the processing thread (the **processor**). This loop
is written in such a way that the order in which events are processed is not
determined by the order of the lines of code. This is required as, during the
processing of a given event, other events may be scheduled (although this can
only occur if the camera is unlocked while processing the event). Hence the
processing of a given event consist in the following operations:
1. Clear the event bit to indicate that the event has been considered.
2. If no other events that would cancel the effects of the considered
event are pending (in particular "quit") and if the camera state is
appropriate, optionally update the camera state and execute the
callback associated with the event.
3. If executing the callback was successful and no "quit" event has
arrived while the callback was executed, optionally update the camera
state.
In this relatively simple implementation, inappropriate events are just
discarded. It is the responsability of the controlling process to schedule
commands in an appropriate order.
While processing an event, the acquisition thread may unlock the camera (this
may be done by the callbacks).
During the life of the acquisition thread, the callbacks and contextual data
may be modified by the controlling thread providing the camera is locked
before being modified and then unlocked.
## Glossary
- **controller**
- **processor**
- **frame grabber**
- **camera**
- **acquisition callback**
......@@ -233,7 +233,10 @@ typedef struct phnx_buffer {
*/
struct phnx_camera {
pthread_mutex_t mutex;/**< Lock to protect this structure */
pthread_cond_t notify;/**< Condition variable to signal events */
pthread_cond_t notify;/**< Condition variable to signal events to the
processor thread */
pthread_cond_t control;/**< Condition variable to signal changes of camera
state to the controller thread */
tao_error_t* errs;/**< Error stack */
uint64_t handle;/**< Camera handle */
int (*initialize)(phnx_camera_t*);
......@@ -283,6 +286,22 @@ struct phnx_camera {
int pending;/**< Number of pending image buffers
(always in the range 0:nbufs) */
bool drop;/**< Drop frames older than the last one? */
/* Processor thread. */
pthread_t processor;/**< Acquisition and processing thread */
int (*on_start)(void* ctx, phnx_camera_t* cam, int nbufs);
void* on_start_ctx; /* contextual data for the on_start callback */
int on_start_nbufs; /* Number of buffers for on_start. */
int (*on_frame)(void* ctx, phnx_camera_t* cam, phnx_buffer_t* buf);
void* on_frame_ctx; /* contextual data for the on_frame callback */
int (*on_error)(phnx_camera_t* cam);
void* on_error_ctx; /* contextual data for the on_error callback */
int (*on_stop)(phnx_camera_t* cam);
void* on_stop_ctx; /* contextual data for the on_stop callback */
int (*on_abort)(phnx_camera_t* cam);
void* on_abortt_ctx; /* contextual data for the on_abort callback */
int (*on_quit)(phnx_camera_t* cam);
void* on_quit_ctx; /* contextual data for the on_quit callback */
};
/**
......
......@@ -283,116 +283,6 @@ phnx_release_buffer(phnx_camera_t* cam)
return -1;
}
/*
* Callback for acquisition.
*
* Rules:
*
* - only one thread does the BUFFER_RELEASE operation;
*
* FIXME: Handle PHX_INTRPT_FRAME_START and PHX_INTRPT_FRAME_END events
* to have a better time-stamp.
*/
static void
acquisition_callback(tHandle handle, uint32_t events, void* data)
{
phnx_camera_t* cam = (phnx_camera_t*)data;
struct timespec now;
/* Minimal sanity check (no needs to lock and abort on error). */
if (cam->handle != handle) {
phnx_push_error(NULL, __func__, TAO_ASSERTION_FAILED);
}
/* Get timestamp immediately (before locking because it may block for some
time, abort on error). */
if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) {
phnx_push_error(NULL, "clock_gettime", errno);
}
/* Lock the camera, handle events and unlock the camera. */
phnx_lock(cam);
{
/* Get index of next imformational buffer (there are cam->nbufs+1
information buffers). */
int next = (cam->last + 1)%(cam->nbufs + 1);
#ifdef PHNX_DEBUG
{
double microseconds = phnx_elapsed_microseconds(cam, &now);
if ((PHX_INTRPT_FRAME_START & events) != 0) {
fprintf(stderr, "FrameStart [%d] %14.3f µs\n",
next, microseconds);
}
if ((PHX_INTRPT_FRAME_END & events) != 0) {
fprintf(stderr, "FrameEnd [%d] %14.3f µs\n",
next, microseconds);
}
if ((PHX_INTRPT_BUFFER_READY & events) != 0) {
fprintf(stderr, "BufferReady [%d] %14.3f µs\n",
next, microseconds);
}
}
#endif /* PHNX_DEBUG */
if ((PHX_INTRPT_FRAME_START & events) != 0) {
TAO_COPY_TIME(&cam->bufs[next].frame_start, &now);
++cam->frames;
if (cam->bufs[next].counter == -1) {
/* The counter of the next frame is set to -1 to detect
overruns. */
++cam->overruns;
cam->events |= PHNX_EVENT_ERROR;
} else {
cam->bufs[next].counter = -1;
}
}
if ((PHX_INTRPT_FRAME_END & events) != 0) {
TAO_COPY_TIME(&cam->bufs[next].frame_end, &now);
}
if ((PHX_INTRPT_BUFFER_READY & events) != 0) {
/* A new frame is available. */
if (cam->pending < cam->nbufs) {
phnx_buffer_t* buf = &cam->bufs[next];
TAO_COPY_TIME(&cam->bufs[next].buffer_ready, &now);
buf->counter = cam->frames;
cam->last = next;
cam->events |= PHNX_EVENT_FRAME;
++cam->pending;
} else {
/* FIXME: this should never happen */
++cam->overruns;
cam->events |= PHNX_EVENT_ERROR;
#ifdef PHNX_DEBUG
fprintf(stderr,
"{BUG} cam->pending=%d >= cam->nbufs=%d in %s (%s, line %d)\n",
cam->pending, cam->nbufs,
__func__, __FILE__, __LINE__);
#endif /* PHNX_DEBUG */
}
}
if ((PHX_INTRPT_FIFO_OVERFLOW & events) != 0) {
/* Fifo overflow. */
++cam->overflows;
cam->events |= PHNX_EVENT_ERROR;
}
if ((PHX_INTRPT_SYNC_LOST & events) != 0) {
/* Synchronization lost. */
++cam->lostsyncs;
cam->events |= PHNX_EVENT_ERROR;
}
if ((PHX_INTRPT_FRAME_LOST & events) != 0) {
/* Frame lost. */
++cam->lostframes;
cam->events |= PHNX_EVENT_ERROR;
}
if (cam->events != 0) {
/* Signal condition for waiting thread. */
phnx_notify(cam);
}
}
phnx_unlock(cam);
}
/* If macro `BUFFER_FULL_IMAGE` is set, all the pixels sent by the camera
are stored in the acquisition buffers and any cropping will done when
processing images. */
......@@ -811,222 +701,7 @@ ENCODE(nanoseconds)
#undef ENCODE
/*--------------------------------------------------------------------------*/
/* CREATE/DESTROY/CONFIGURE CAMERA INSTANCE */
#undef USE_CONFIG_MODE
phnx_camera_t*
phnx_create(tao_error_t** errs,
void (*handler)(const char*, int, const char*),
char* configname, const phnx_create_options_t* opts)
{
static phnx_create_options_t defaultoptions = PHNX_CREATE_OPTIONS_DEFAULT;
phnx_camera_t* cam;
etStat status;
phx_error_handler *error_handler = phx_default_error_handler;
/* Check assumptions. */
assert(sizeof(ui8) == 1);
assert(sizeof(ui16) == 2);
assert(sizeof(ui32) == 4);
assert(sizeof(ui64) == 8);
assert(sizeof(float32_t) == 4);
assert(sizeof(float64_t) == 8);
assert(sizeof(etParamValue) == sizeof(int));
assert(sizeof(etStat) == sizeof(int));
assert(sizeof(float32_t) == 4);
assert(sizeof(float64_t) == 8);
assert(sizeof(((phnx_camera_t*)0)->vendor) > CXP_DEVICE_VENDOR_NAME_LENGTH);
assert(sizeof(((phnx_camera_t*)0)->model) > CXP_DEVICE_MODEL_NAME_LENGTH);
/* Parse open options. */
etParamValue boardnumber;
if (opts == NULL) {
opts = &defaultoptions;
}
switch (opts->boardnumber) {
case PHNX_BOARD_NUMBER_AUTO: boardnumber = PHX_BOARD_NUMBER_AUTO; break;
case PHNX_BOARD_NUMBER_1: boardnumber = PHX_BOARD_NUMBER_1; break;
case PHNX_BOARD_NUMBER_2: boardnumber = PHX_BOARD_NUMBER_2; break;
case PHNX_BOARD_NUMBER_3: boardnumber = PHX_BOARD_NUMBER_3; break;
case PHNX_BOARD_NUMBER_4: boardnumber = PHX_BOARD_NUMBER_4; break;
case PHNX_BOARD_NUMBER_5: boardnumber = PHX_BOARD_NUMBER_5; break;
case PHNX_BOARD_NUMBER_6: boardnumber = PHX_BOARD_NUMBER_6; break;
case PHNX_BOARD_NUMBER_7: boardnumber = PHX_BOARD_NUMBER_7; break;
default:
tao_push_error(errs, __func__, TAO_BAD_ARGUMENT);
return NULL;
}
etParamValue channelnumber;
switch (opts->channelnumber) {
case PHNX_CHANNEL_NUMBER_AUTO: channelnumber = PHX_CHANNEL_NUMBER_AUTO; break;
case PHNX_CHANNEL_NUMBER_1: channelnumber = PHX_CHANNEL_NUMBER_1; break;
case PHNX_CHANNEL_NUMBER_2: channelnumber = PHX_CHANNEL_NUMBER_2; break;
default:
tao_push_error(errs, __func__, TAO_BAD_ARGUMENT);
return NULL;
}
etParamValue configmode;
switch (opts->configmode) {
case PHNX_CONFIG_MODE_NORMAL: configmode = PHX_CONFIG_NORMAL; break;
case PHNX_CONFIG_MODE_COMMS_ONLY: configmode = PHX_CONFIG_COMMS_ONLY; break;
case PHNX_CONFIG_MODE_ACQ_ONLY: configmode = PHX_CONFIG_ACQ_ONLY; break;
default:
tao_push_error(errs, __func__, TAO_BAD_ARGUMENT);
return NULL;
}
/* Allocate memory to store the camera instance. */
cam = NEW(errs, 1, phnx_camera_t);
if (cam == NULL) {
return NULL;
}
phnx_set_origin_of_time(cam, NULL);
cam->config.cameraencoding = TAO_ENCODING_UNKNOWN;
cam->config.bufferencoding = TAO_ENCODING_UNKNOWN;
cam->timeout = 500;
cam->state = PHNX_STATE_INITIALIZING;
cam->initlevel = 0;
cam->swap = false;
cam->coaxpress = false;
cam->temperature = NAN;
/* Initialize private lock. */
if (tao_initialize_mutex(&cam->errs, &cam->mutex, false) != 0) {
goto error;
}
cam->initlevel = 1;
/* Initialize private condition variable. */
if (tao_initialize_condition(&cam->errs, &cam->notify, false) != 0) {
goto error;
}
cam->initlevel = 2;
/* Create a Phoenix handle. */
if (handler != NULL) {
assert(sizeof(etStat) == sizeof(int));
error_handler = (phx_error_handler*)handler;
}
status = PHX_Create(&cam->handle, error_handler);
if (status != PHX_OK) {
phx_push_error(errs, "PHX_Create", status);
goto error;
}
cam->initlevel = 3;
/* Set the configuration file name. */
if (configname != NULL && configname[0] != 0) {
status = PHX_ParameterSet(cam->handle, PHX_CONFIG_FILE, &configname);
if (status != PHX_OK) {
phx_push_error(errs, "PHX_ParameterSet(PHX_CONFIG_FILE)", status);
goto error;
}
}
/* Set the board number. */
status = PHX_ParameterSet(cam->handle, PHX_BOARD_NUMBER, &boardnumber);
if (status != PHX_OK) {
phx_push_error(errs, "PHX_ParameterSet(PHX_BOARD_NUMBER)", status);
goto error;
}
/* Set the channel number. */
status = PHX_ParameterSet(cam->handle, PHX_CHANNEL_NUMBER, &channelnumber);
if (status != PHX_OK) {
phx_push_error(errs, "PHX_ParameterSet(PHX_CHANNEL_NUMBER)", status);
goto error;
}
/* Set the configuration mode. In our case, setting this parameter was
blocking, so we only do that if a non-default option is chosen. */
if (configmode != PHX_CONFIG_NORMAL) {
status = PHX_ParameterSet(cam->handle, PHX_CONFIG_MODE, &configmode);
if (status != PHX_OK) {
phx_push_error(errs, "PHX_ParameterSet(PHX_CONFIG_MODE)", status);
goto error;
}
}
/* Open the Phoenix board using the above configuration file. */
status = PHX_Open(cam->handle);
if (status != PHX_OK) {
phx_push_error(errs, "PHX_Open", status);
goto error;
}
cam->initlevel = 4;
/* Check whether we have a CoaXPress camera. */
if (phx_detect_coaxpress(cam) != 0) {
goto error;
}
/* Initialize specific camera model. */
for (int k = 0; known_cameras[k].check != NULL; ++k) {
if (known_cameras[k].check(cam) &&
known_cameras[k].initialize != NULL) {
if (known_cameras[k].initialize(cam) != 0) {
goto error;
}
cam->initialize = known_cameras[k].initialize;
break;
}
}
/* Update state and return. */
cam->state = PHNX_STATE_INITIALIZING;
return cam;
error:
if (cam != NULL) {
tao_transfer_errors(errs, &cam->errs);
}
phnx_destroy(cam);
return NULL;
}
void
phnx_destroy(phnx_camera_t* cam)
{
if (cam != NULL) {
/* Close/release/destroy resources in reverse order. */
if (cam->initlevel >= 5) {
/* Abort acquisition and unlock all buffers. */
(void)PHX_StreamRead(cam->handle, PHX_ABORT, NULL);
(void)PHX_StreamRead(cam->handle, PHX_UNLOCK, NULL);
if (cam->stop != NULL) {
(void)cam->stop(cam);
}
cam->state = 1;
}
if (cam->initlevel >= 4) {
/* Close the Phoenix board. */
(void)PHX_Close(&cam->handle);
cam->state = 0;
}
if (cam->initlevel >= 3) {
/* Destroy the Phoenix handle and release memory. */
(void)PHX_Destroy(&cam->handle);
}
if (cam->initlevel >= 2) {
/* Destroy condition variable. */
pthread_cond_destroy(&cam->notify);
}
if (cam->initlevel >= 1) {
/* Destroy mutex. */
pthread_mutex_destroy(&cam->mutex);
}
/* Free acquisition buffers. */
do_buffers(cam, 0);
/* Free error stack. */
tao_discard_errors(&cam->errs);
/* Release memory. */
free(cam);
}
}
/* CONFIGURE CAMERA INSTANCE */
int
phnx_load_configuration(phnx_camera_t* cam, int id)
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment