/**
* @file o3000.c
* @brief O-3000 Camera driver
* @author Patrick Brunner - brunner@stettbacher.ch
* @author Patrick Roth - roth@stettbacher.ch
* @version 1.1
* @date 2016-03-01
* @copyright 2012-2016 Stettbacher Signal Processing AG
*
* @remarks
*
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*
*/
/**
@mainpage O-3000 camera driver
@section intro Introduction
This document describes the Application Programming Interface (API) for the
Stettbacher Signal Processing USB O-3000 Camera, further referenced as "the camera".
The driver delivers a generic user interface for accessing the camera and runs on different operating system like:
The driver supports session handling. Therfore it's able to handle several camera session on the same host system accessing from one
or several user applications.
@section api API
Two groups of functions represent the interface:
- A set of functions provided by the camera API which can be called from
the user application (see @ref api_fun).
- A set of callback functions the user application must implement and
register with the camera API (see @ref cb_fun).
@subsection api_fun API functions
The basic approach using the camera driver is:
- Implementing all required callback functions, see @ref cb_fun.
- Initialising the camera driver, see @ref o3000_init.
- Connecting to the camera driver, see @ref o3000_connect.
Once the user application is connected to the driver by calling @ref o3000_connect, it will block until:
- The driver exits by calling @ref o3000_exit.
- The camera is disconnected from the system.
XML messages are transfered to the camera by calling @ref o3000_send_xml. If the camera sends a XML message to the host, the
driver triggers the @ref xml_cb. The same concept is used, when the camera sends video data to the host. After receving a complete frame, the driver calls the
@ref video_cb.
@note The video driver triggers any callback from the thread context, where @ref o3000_connect is called from. This means that the @ref o3000_send_xml function should
never be called from the Video, XML or Logging callback. Otherwise the driver will lock. It's important, that any XML message sent with @ref o3000_send_xml to the camera
is called from a different thread context.
@subsection cb_fun Callback functions
@subsubsection video_cb Video handler
The video handler is called by the camera driver whenever a complete frame
is received. The video handler holds a pointer to the image
data (the frame) and a pointer to a structure describing the image format.
This handler is mandatory.
@subsubsection xml_cb XML handler
The XML handler is called by the camera driver whenever a XML message is
received. The XML handler holds a pointer to a string buffer
containing the XML message, and the length of that message. This handler is
mandatory.
@subsubsection log_cb Logging handler
The Logging handler is called by the driver to print debug messages.
The handler contains a pointer to a (zero-terminated) string buffer
containing the log message. This handler is optional.
@note The verbosity of the messages depends on the log level and is defined from @ref O3000_LOG_NONE to @ref O3000_LOG_VERBOSE.
@section example Example
A simplified example with C is shown below, focussing on the order in which the various
camera driver functions should be called. It's up to the user to implement
multithreading in order to avoid a deadlock due to the blocking nature of @ref o3000_connect!
@code
#include
#include
#include
#include
static char xml_msg[] = {""};
static void xml_handling(int id, char* msg, int len) {
// insert your code here
}
static void video_handling(int id, unsigned char* buf, struct img_header_t* img_header) {
// insert your code here
}
static void log_handling(int id, char* msg) {
// insert your code here
}
int main(int argc, char **argv) {
int ret = 0;
int num_camera, i;
int cam_session;
// setup camera session with 4 MByte video cache
cam_session = o3000_init(O3000_VID, O3000_PID, 1024*1024*4, xml_handling, video_handling, log_handling, O3000_LOG_ERROR);
if(cam_session < 0) {
exit(1);
}
num_camera = o3000_device_discovery(cam_session);
if(num_camera < 0) {
o3000_exit(cam_session);
exit(1);
}
if(num_camera == 0) {
printf("%s: no camera connected to the system\n", __func__);
o3000_exit(cam_session);
exit(1);
}
// establish connection to first available camera
for(i = 0; i < num_camera; i++) {
printf("%s: establish connection to camera %d\n", __func__, i);
ret = o3000_connect(cam_session, i, xml_msg, strlen(xml_msg));
if(ret == O3000_ERROR_BUSY) {
printf("%s: device %d is already in use\n", __func__, i);
}
else {
break;
}
}
o3000_exit(cam_session);
exit(0);
}
@endcode
*/
/* Video buffering concept
At program initialisation, a number of transfer buffers is sent to libusb.
Libusb handles the low-level interactions with the kernel. A transfer is split
into a number of URBs. These URBs are sent to the kernel and returned to libusb
with the data received. As soon as enough data was received, libusb calls the
transfer callback, which processes the data. In this case, the transfer handler
is called. As soon as the transfer is handled, the transfer is resubmitted to
libusb, ready to be filled again.
Each transfer points to a buffer, where received data is copied to. All transfer
buffers T(x) and the frame buffer FB (see below) are contiguous, i.e. starting
from T(0) (transfer 0) to the end of the frame buffer a linear memory region
without holes is allocated.
Memory Layout:
+---- start of video cache
| +---- start of frame buffer
| |
| |
V V
+------+------+------+------+------+------+--------------------+
| T(0) | T(1) | T(2) | T(3) | .... | T(n) | Frame buffer |
+------+------+------+------+------+------+--------------------+
The transfers are submitted starting from T(0). As they are queued, the order
T(0), T(1), etc. is always maintained. After filling the last buffer T(n),
the sequence starts over with T(0). No double-buffering takes place, as by the
strict sequence order and the fact that a received buffer is processed first
before resubmission there is no risk of overwriting unprocessed data.
The transfer handler scans through the data received. Whenever a complete frame
is found in the buffers, the video callback is called, which is supposed to
process the frame.
A special case applies, if an incomplete frame remains at the end of T(n), which
will be continued with T(0) (and maybe subsequent transfers). In that situation,
if the frame is completely received, the second part, starting from T(0) will
be copied to the frame buffer. As the frame buffer is linked directly after T(n),
the frame is now located in a linear part of the memory, which is a condition for
the video callback function which expects a pointer to a linear buffer, holding
the frame.
An other special case occurs, when a snapshot is sent. The last portion of a snapshot
is usually sent as a short bulk transfer packet, which means, that a subsequent snapshot
or streaming frame will be aligned to the start of a transfer, leaving a gap of invalid
data between two frames in the video cache.
Depending on the transfer size chosen, a frame may extend over several transfers,
or a transfer may contain several frames.
*/
#include
#include
#include
#include
#include "o3000_portable.h"
#include "o3000_private.h"
#include "o3000.h"
#include "o3000_xfer_handler.h"
#include "o3000_upgrade.h"
#define CAM_IN_EP (1 | LIBUSB_ENDPOINT_IN) ///< endpoint for video data
#define XML_IN_EP (2 | LIBUSB_ENDPOINT_IN) ///< endpoint for inbound XML messages
#define XML_OUT_EP (1) ///< endpoint for outbound XML messages
/**
* Inbound XML messages size
*/
#define XML_IN_BUF_SIZE 16384
/**
* Minimum video cache size.
* It's equal to the maximum image size which would appear.
*/
#define MIN_VIDEO_CACHE_SIZE (MAX_IMAGE_SIZE)
/**
* maximum number of camera sessions
*/
#define MAX_NUM_SESSION 16
/**
* The video cache is divided into several USB transfer queued at at kernel.
*/
#define NUM_USB_VIDEO_TRANSFER 10
/**
* Session table
* Each table entry points to an active session. If the entry is NULL it's not used and can be used for a new session.
*/
static struct o3000_session_t *session_table[MAX_NUM_SESSION];
/*
* Thread synchronization mutex
*/
static o3000_mutex_static_t session_lock = O3000_MUTEX_INITIALIZER;
/**
* Maps loglevel numbers to descriptive strings
*/
static const char *dbg_strlevel[] = {
"NONE",
"ERROR",
"WARNING",
"INFO",
"DEBUG"
};
/**
* Return session by ID
*
* @param id session ID
* @return adress to session or NULL if not available
*/
static struct o3000_session_t *get_session(int id) {
struct o3000_session_t *session;
if(id < 0 || id >= MAX_NUM_SESSION) {
return NULL;
}
session = session_table[id];
if(session == NULL) {
return NULL;
}
return session;
}
/**
* Cleanup given session.
* Everything is cleaned up like:
* - closing current session
* - release USB transfers
* - release video cache and XML message buffer
*
* In the end, the current session is freed.
*
*/
static void cleanup(int id) {
struct o3000_session_t *session;
int i;
session = get_session(id);
if(session == NULL) {
return;
}
// release device list
if(session->device_list != NULL) {
o3000_log(session, O3000_LOG_DEBUG, "%s: cleanup USB device discovery list\n", __func__);
libusb_free_device_list(session->device_list, 1);
session->device_list = NULL;
session->num_device_list = 0;
}
// close USB device
if(session->dev != NULL) {
o3000_log(session, O3000_LOG_DEBUG, "%s: close USB device handler\n", __func__);
libusb_close(session->dev);
session->dev = NULL;
}
// release USB video transfer
for(i = 0; i < NUM_USB_VIDEO_TRANSFER; i++) {
if(session->transfer_data[i] != NULL) {
o3000_log(session, O3000_LOG_DEBUG, "%s: release USB video transfers %d\n", __func__, i);
libusb_free_transfer(session->transfer_data[i]);
session->transfer_data[i] = NULL;
}
}
// release USB transfer transfer array
if(session->transfer_data != NULL) {
o3000_log(session, O3000_LOG_DEBUG, "%s: release USB transfer array\n", __func__);
free(session->transfer_data);
session->transfer_data = NULL;
}
// release USB XML transfer for incoming messages
if(session->transfer_xml_in != NULL) {
o3000_log(session, O3000_LOG_DEBUG, "%s: release USB XML transfer for incoming messages\n", __func__);
libusb_free_transfer(session->transfer_xml_in);
session->transfer_xml_in = NULL;
}
// release XML buffer
if(session->xml_in_buf != NULL) {
o3000_log(session, O3000_LOG_DEBUG, "%s: release XML message buffer\n", __func__);
free(session->xml_in_buf);
session->xml_in_buf = NULL;
}
// release video cache
if(session->video_cache != NULL) {
o3000_log(session, O3000_LOG_DEBUG, "%s: release video cache\n", __func__);
free(session->video_cache);
session->video_cache = NULL;
}
// add more cleanup code here...
// ..
// close libusb context
if(session->libusb_ctx != NULL) {
o3000_log(session, O3000_LOG_DEBUG, "%s: release USB context\n", __func__);
libusb_exit(session->libusb_ctx);
session->libusb_ctx = NULL;
}
// finally release this session
if(session_table[id] != NULL) {
o3000_log(session, O3000_LOG_DEBUG, "%s: release USB session\n", __func__);
free(session_table[id]);
session_table[id] = NULL;
}
}
/**
* Check wether given USB device is a camera
*
* @param session session pointer
* @param device USB device to check
* @return TRUE if it's a camera otherwise FALSE
*/
static int is_camera(struct o3000_session_t *session, libusb_device *device) {
int ret, dev_of_interest;
struct libusb_device_descriptor desc;
ret = libusb_get_device_descriptor(device, &desc);
if(ret != 0) {
o3000_log(session, O3000_LOG_ERROR, "%s: getting USB descriptor failed (code %d)\n", __func__, ret);
return FALSE;
}
dev_of_interest = FALSE;
if((desc.idVendor == session->vid) && (desc.idProduct == session->pid)) {
dev_of_interest = TRUE;
}
o3000_log(session, O3000_LOG_DEBUG, "%s: USB device detected (Device class %d, VID:PID 0x%x:0x%x, S/N %d) - %s\n",
__func__, desc.bDeviceClass, desc.idVendor, desc.idProduct, desc.iSerialNumber, dev_of_interest == TRUE ? "camera":"no camera");
return dev_of_interest;
}
/**
* Print a meaningful message for given transfer result.
*
* @param result libusb submit transfer return code.
* @return o3000 error code translation (see @ref o3000.h)
*/
static int print_transfer_result(struct o3000_session_t *session, int result) {
int error_code = O3000_ERROR_OTHER;
switch(result) {
case 0:
o3000_log(session, O3000_LOG_DEBUG, "%s: transfer successfully completed\n", __func__);
error_code = O3000_SUCCESS;
break;
case LIBUSB_ERROR_NO_DEVICE:
o3000_log(session, O3000_LOG_ERROR, "%s: device has been disconnected\n", __func__);
error_code = O3000_ERROR_NODEV;
break;
case LIBUSB_ERROR_BUSY:
o3000_log(session, O3000_LOG_ERROR, "%s: transfer has already been submitted\n", __func__);
error_code = O3000_ERROR_BUSY;
break;
case LIBUSB_ERROR_NOT_SUPPORTED:
o3000_log(session, O3000_LOG_ERROR, "%s: transfer flags not supported by the OS\n", __func__);
break;
default:
o3000_log(session, O3000_LOG_ERROR, "%s: other error (code %d)\n",__func__, result);
}
return error_code;
}
/**
* Print a meaningful message for given transfer status.
*
* @param status libusb status code
*/
static void print_transfer_status(struct o3000_session_t *session, enum libusb_transfer_status status) {
switch(status) {
case LIBUSB_TRANSFER_COMPLETED:
o3000_log(session, O3000_LOG_DEBUG, "%s: transfer successfully completed\n", __func__);
break;
case LIBUSB_TRANSFER_ERROR:
o3000_log(session, O3000_LOG_DEBUG, "%s: Error: transfer failed\n", __func__);
break;
case LIBUSB_TRANSFER_TIMED_OUT:
o3000_log(session, O3000_LOG_DEBUG, "transfer timed out\n", __func__);
break;
case LIBUSB_TRANSFER_CANCELLED:
o3000_log(session, O3000_LOG_DEBUG, "transfer was cancelled\n", __func__);
break;
case LIBUSB_TRANSFER_STALL:
o3000_log(session, O3000_LOG_DEBUG, "%s: endpoint stalled\n", __func__);
break;
case LIBUSB_TRANSFER_NO_DEVICE:
o3000_log(session, O3000_LOG_DEBUG, "%s: device has been disconnected\n", __func__);
break;
case LIBUSB_TRANSFER_OVERFLOW:
o3000_log(session, O3000_LOG_DEBUG, "%s: device sent more data than requested\n", __func__);
break;
default:
o3000_log(session, O3000_LOG_DEBUG, "%s: Error: unknown\n", __func__);
}
}
/**
* Check whether all transfers are cleanuped (freed).
* This includes all video transfers and the XML transfer.
*
* @param session current session
* @return FALSE if any transfer is pending for cleaning up; TRUE if all transfers are cleanuped
*/
static int allTransfersCleanup(struct o3000_session_t *session) {
int i;
if(session->transfer_xml_in != NULL) {
return FALSE;
}
for(i = 0; i < NUM_USB_VIDEO_TRANSFER; i++) {
if(session->transfer_data[i] != NULL) {
return FALSE;
}
}
return TRUE;
}
/**
* Cleanup current USB transfer (XML or video transfer).
*
* @param transfer current transfer to clean up
*/
static void cleanup_transfer(struct libusb_transfer *transfer) {
int i;
struct o3000_session_t *session = transfer->user_data;
session->cleanup_transfers = TRUE;
// print transfer status for debugging purposes
print_transfer_status(session, transfer->status);
if(transfer == session->transfer_xml_in) {
// release USB XML transfer
if(session->transfer_xml_in != NULL) {
libusb_free_transfer(session->transfer_xml_in);
o3000_log(session, O3000_LOG_INFO, "%s: cleanup XML transfer (%p)\n", __func__, session->transfer_xml_in);
session->transfer_xml_in = NULL;
transfer = NULL;
}
}
else {
// release USB video transfer
for(i = 0; i < NUM_USB_VIDEO_TRANSFER; i++) {
if(session->transfer_data[i] == transfer) {
libusb_free_transfer(session->transfer_data[i]);
o3000_log(session, O3000_LOG_INFO, "%s: cleanup video transfer %d (%p)\n", __func__, i, session->transfer_data[i]);
session->transfer_data[i] = NULL;
transfer = NULL;
break;
}
}
}
if(transfer != NULL) {
o3000_log(session, O3000_LOG_ERROR, "%s: FIXME: This transfer %p should be cleaned!\n", __func__, transfer);
}
// reset running flag only when all transfers are cleaned up
if(allTransfersCleanup(session)) {
o3000_log(session, O3000_LOG_INFO, "%s: all USB transfers are cleaned up\n", __func__);
session->running = FALSE;
}
}
/**
* Transfer done callback for inbound XML.
*
* @note Transfer done only means submitted, not actually completed!
*
* @param transfer Pointer to XML transfer which has been submitted
*/
static void LIBUSB_CALL cb_xml_in_xfer_done(struct libusb_transfer *transfer) {
int retval;
struct o3000_session_t *session = transfer->user_data;
if(transfer->status == LIBUSB_TRANSFER_COMPLETED) {
// check whether string length is out of range
if(transfer->actual_length < 0 || transfer->actual_length >= XML_IN_BUF_SIZE) {
o3000_log(session, O3000_LOG_ERROR, "%s: invalid XML message length (len = %d)\n", __func__, transfer->actual_length);
}
else {
session->xml_in_buf[transfer->actual_length] = '\0';
session->xml_cb(session->id, session->xml_in_buf, transfer->actual_length);
}
// resubmit transfer only if driver isn't cleaning up
if(session->cleanup_transfers == FALSE) {
retval = libusb_submit_transfer(session->transfer_xml_in);
if(retval != 0) {
o3000_log(session, O3000_LOG_ERROR, "%s: resubmit XML transfer failed (code %d) --> cleanup\n", __func__, retval);
print_transfer_result(session, retval);
cleanup_transfer(transfer);
}
}
else {
o3000_log(session, O3000_LOG_INFO, "%s: cleaning up in progress\n", __func__);
cleanup_transfer(transfer);
}
}
else {
/*
* USB transfer is not completed!
* Do only clean up the transfer if the device has been disconnected or cleaning-up flag is set.
* Otherwise skip error and resubmit current transfer.
*/
if(transfer->status == LIBUSB_TRANSFER_NO_DEVICE || session->cleanup_transfers == TRUE) {
o3000_log(session, O3000_LOG_INFO, "%s: cleaning up in progress (%s), no device (%s)\n",
__func__, session->cleanup_transfers == TRUE ? "yes":"no", transfer->status == LIBUSB_TRANSFER_NO_DEVICE ? "yes":"no");
cleanup_transfer(transfer);
}
else {
retval = libusb_submit_transfer(session->transfer_xml_in);
if(retval != 0) {
o3000_log(session, O3000_LOG_ERROR, "%s: Could resubmit XML transfer (code %d)\n", __func__, retval);
print_transfer_result(session, retval);
cleanup_transfer(transfer);
}
}
}
}
/**
* Transfer done callback for video data.
*
* @note Transfer done only means submitted, not actually completed!
*
* @param transfer Pointer to transfer which just has been submitted
*/
static void LIBUSB_CALL cb_video_xfer_done(struct libusb_transfer *transfer) {
int retval;
struct o3000_session_t *session = transfer->user_data;
if(transfer->status == LIBUSB_TRANSFER_COMPLETED) {
if(transfer->actual_length > 0) {
handle_transfer(session, (uint8_t*)(transfer->buffer), transfer->actual_length);
}
// resubmit transfer only if driver is not exiting
if(session->cleanup_transfers == FALSE) {
retval = libusb_submit_transfer(transfer);
if(retval != 0) {
o3000_log(session, O3000_LOG_ERROR, "%s: Could resubmit video transfer (code %d)\n", __func__, retval);
print_transfer_result(session, retval);
cleanup_transfer(transfer);
}
}
else {
o3000_log(session, O3000_LOG_INFO, "%s: cleaning up in progress\n", __func__);
cleanup_transfer(transfer);
}
}
else {
/*
* USB transfer is not completed!
* Do only clean up the transfer if the device has been disconnected or cleaning-up flag is set.
* Otherwise skip error and resubmit current transfer.
*/
if(transfer->status == LIBUSB_TRANSFER_NO_DEVICE || session->cleanup_transfers == TRUE) {
o3000_log(session, O3000_LOG_INFO, "%s: cleaning up in progress (%s), no device (%s)\n",
__func__, session->cleanup_transfers == TRUE ? "yes":"no", transfer->status == LIBUSB_TRANSFER_NO_DEVICE ? "yes":"no");
cleanup_transfer(transfer);
}
else {
retval = libusb_submit_transfer(transfer);
if(retval != 0) {
o3000_log(session, O3000_LOG_ERROR, "%s: Could resubmit video transfer (code %d)\n", __func__, retval);
print_transfer_result(session, retval);
cleanup_transfer(transfer);
}
}
}
}
/**
* Prepare USB transfers to receive video data and XML messages from device.
*
* @param session session pointer
* @return 0 on success, or error code (see @ref o3000.h)
*/
static int prepare_usb_transfers(struct o3000_session_t *session) {
int i, retval;
struct libusb_transfer *new_transfer;
int error_code = O3000_ERROR_OTHER;
uint8_t *xfer_buf;
// paranoia check
if(session->transfer_data == NULL) {
o3000_log(session, O3000_LOG_ERROR, "%s: FIXME: pointer array used for video transfer is not allocated!\n", __func__);
return O3000_ERROR_OTHER;
}
/*
* setup video transfer array
*/
for(i = 0; i < NUM_USB_VIDEO_TRANSFER; i++) {
// Allocate transfer
new_transfer = libusb_alloc_transfer(0);
if(new_transfer == NULL) {
o3000_log(session, O3000_LOG_ERROR, "%s: Could not allocate video transfer %d\n", __func__, i);
error_code = O3000_ERROR_NOMEM;
goto _prepare_usb_transfer_abort;
}
session->transfer_data[i] = new_transfer;
// Video transfers are using the same callback but with own user data pointer pointing to current session definition.
xfer_buf = session->video_cache + i*(session->video_chunk_size);
libusb_fill_bulk_transfer(new_transfer, session->dev, CAM_IN_EP, xfer_buf, session->video_chunk_size, cb_video_xfer_done, session, 0);
// Submit transfer
retval = libusb_submit_transfer(new_transfer);
if(retval != 0) {
o3000_log(session, O3000_LOG_ERROR, "%s: Could not submit video transfer %d (code %d)\n", __func__, i, retval);
error_code = print_transfer_result(session, retval);
goto _prepare_usb_transfer_abort;
}
o3000_log(session, O3000_LOG_DEBUG, "%s: video transfer %d allocated and submitted (%p)\n", __func__, i, new_transfer);
}
/*
* setup single XML transfer
*/
new_transfer = libusb_alloc_transfer(0);
if(new_transfer == NULL) {
o3000_log(session, O3000_LOG_ERROR, "%s: Could not allocate XML transfer\n", __func__);
error_code = O3000_ERROR_NOMEM;
goto _prepare_usb_transfer_abort;
}
session->transfer_xml_in = new_transfer;
// prepare and submit transfer
libusb_fill_bulk_transfer(new_transfer, session->dev, XML_IN_EP, (unsigned char*)(session->xml_in_buf), XML_IN_BUF_SIZE, cb_xml_in_xfer_done, session, 0);
retval = libusb_submit_transfer(new_transfer);
if(retval != 0) {
o3000_log(session, O3000_LOG_ERROR, "%s: Could not submit XML transfer (code %d)\n", __func__, retval);
error_code = print_transfer_result(session, retval);
goto _prepare_usb_transfer_abort;
}
o3000_log(session, O3000_LOG_DEBUG, "%s: XML transfer allocated and submitted (%p)\n", __func__, new_transfer);
return 0;
_prepare_usb_transfer_abort:
return error_code;
}
/**
* Send XML message to device.
*
* @param session current session
* @param msg message content
* @param msg_len message content length
* @return 0 on success, or error code (see @ref o3000.h)
*/
static int send_xml(struct o3000_session_t *session, const char *msg, int msg_len) {
int xml_msg_transferred, retval, ret_code;
// use timeout of 1 second
ret_code = 0;
retval = libusb_bulk_transfer(session->dev, XML_OUT_EP, (unsigned char*)msg, msg_len, &xml_msg_transferred, 1000);
if(retval != 0) {
if(retval == LIBUSB_ERROR_TIMEOUT) {
o3000_log(session, O3000_LOG_ERROR, "%s: sending XML message failed (timeout)\n", __func__, retval);
ret_code = O3000_ERROR_USB_TRANSFER_TIMEOUT;
}
else if(retval == LIBUSB_ERROR_PIPE) {
o3000_log(session, O3000_LOG_ERROR, "%s: sending XML message failed (pipe error)\n", __func__, retval);
ret_code = O3000_ERROR_USB_EP_HALTED;
}
else if(retval == LIBUSB_ERROR_NO_DEVICE) {
o3000_log(session, O3000_LOG_ERROR, "%s: sending XML message failed (no device)\n", __func__, retval);
ret_code = O3000_ERROR_NODEV;
}
else {
o3000_log(session, O3000_LOG_ERROR, "%s: sending XML message failed (code %d)\n", __func__, retval);
ret_code = O3000_ERROR_OTHER;
}
}
else if(xml_msg_transferred != msg_len) {
o3000_log(session, O3000_LOG_ERROR, "%s: less data are transfered to the device than expected (%d of %d bytes)\n", __func__, xml_msg_transferred, msg_len);
ret_code = O3000_ERROR_LESS_DATA;
}
return ret_code;
}
/**
* Send XML packet from host to device.
*
* @note Never call this function from the thread context @ref o3000_connect is called from!
*
* @param id session ID
* @param msg message content
* @param msg_len message content length
* @return 0 on success, or error code (see @ref o3000.h)
*/
int __stdcall o3000_send_xml(int id, const char *msg, int msg_len) {
struct o3000_session_t *session;
session = get_session(id);
if(session == NULL) {
return O3000_ERROR_INVALID_SESSION_ID;
}
if(msg == NULL) {
o3000_log(session, O3000_LOG_ERROR, "%s: no message defined\n", __func__);
return O3000_ERROR_OTHER;
}
if(msg_len <= 0) {
o3000_log(session, O3000_LOG_ERROR, "%s: invalid message length %d\n", __func__, msg_len);
return O3000_ERROR_OTHER;
}
if(session->running == FALSE) {
o3000_log(session, O3000_LOG_ERROR, "%s: driver is not running\n", __func__);
return O3000_ERROR_DRV_NOT_CONNECTED;
}
if(session->cleanup_transfers == TRUE) {
o3000_log(session, O3000_LOG_ERROR, "%s: driver is cleaning up\n", __func__);
return O3000_ERROR_DRV_NOT_CONNECTED;
}
return (send_xml(session, msg, msg_len));
}
/**
* Get the number of open sessions.
*
* @return number of open sessions
*/
static int get_num_open_sessions(void) {
int i, num_sessions = 0;
for(i = 0; i < MAX_NUM_SESSION; i++) {
if(session_table[i] != NULL) {
num_sessions ++;
}
}
return num_sessions;
}
/**
* Do firmware upgrade of connected camera device.
*
* Make sure, that all O-3000 sessions are closed before calling this
* function. Further, only one camera must be connected to the host system.
* Otherwise the update process will fail.
*
* @param data pointer to firmware update data binary
* @param data_len firmware update data lenght in bytes
* @return 0 on success, error code on failure defined at @ref o3000.h.
*/
int __stdcall o3000_firmware_upgrade(unsigned char *data, int data_len) {
int ret , num_sessions, num_cam;
// paranoia checks
if(data == NULL) {
printf("%s: Firmware binary not defined!\n", __func__);
return O3000_ERROR_NOT_VALID_ARGUMENT;
}
if(data_len <= 0) {
printf("%s: Invalid firmware binary length of %d bytes!\n", __func__, data_len);
return O3000_ERROR_NOT_VALID_ARGUMENT;
}
// checking whether all camera sessions are closed
num_sessions = get_num_open_sessions();
if (num_sessions > 0) {
printf("%s: There're still %d camera session(s) opened. Close all session to perform firmware upgrade.\n", __func__, num_sessions);
return O3000_ERROR_SESSION_STILL_OPEN;
}
// checking whether only one camera is connected to the system
num_cam = o3000_get_num_cam();
if(num_cam < 0) {
// forward error
printf("%s: Getting number of connected cameras failed with error code %d\n", __func__, num_cam);
return num_cam;
}
if(num_cam == 0) {
// no camera connected to the system
printf("%s: No camera connected to the host system.\n", __func__);
return O3000_ERROR_NODEV;
}
if(num_cam > 1) {
printf("%s: %d cameras connected to the host system. Make sure only one camera is connected.\n", __func__, num_cam);
return O3000_ERROR_OTHER;
}
// At this point only one camera is connected and all sessions are closed. Perform upgrade now.
ret = firmware_upgrade(data, data_len);
if(ret != 0) {
printf("%s: Firmware upgrade failed with error code %d.\n", __func__, ret);
}
return ret;
}
/**
* Disconnect a currently claimed USB connection.
* Use this function if the driver is connected to an USB device. The @ref o3000_connect function-call is blocking now.
* After disconnecting, the function @ref o3000_connect returns. The session is not cleaned-up due to the driver is ready the reconnect
* to the same USB device by calling @ref o3000_connect again.
*
* @param id session ID
* @return 0 on success, or error code defined at @ref o3000.h.
*/
int __stdcall o3000_disconnect(int id) {
struct o3000_session_t *session;
int retval;
session = get_session(id);
if(session == NULL) {
return O3000_ERROR_INVALID_SESSION_ID;
}
if(session->running == FALSE) {
o3000_log(session, O3000_LOG_WARNING, "%s: driver is not connected!\n", __func__);
return O3000_ERROR_DRV_NOT_CONNECTED;
}
o3000_log(session, O3000_LOG_DEBUG, "%s: disconnect USB device (release interface)\n", __func__);
session->cleanup_transfers = TRUE;
session->disconnect_dev = TRUE;
retval = libusb_release_interface(session->dev, 0);
if(retval == LIBUSB_ERROR_NOT_FOUND) {
o3000_log(session, O3000_LOG_ERROR, "%s: releasing interface failed: interface not claimed\n", __func__);
}
else if(retval == LIBUSB_ERROR_NO_DEVICE) {
o3000_log(session, O3000_LOG_ERROR, "%s: releasing interface failed: device has been disconnected\n", __func__);
}
else if(retval != 0) {
o3000_log(session, O3000_LOG_ERROR, "%s: releasing interface failed (code %d)\n", __func__, retval);
}
return retval;
}
/**
* Estabslish a connection to an existing device.
* If several devices are available on the same system, the device number is used. The number of devices are requested
* by @ref o3000_get_num_device.
*
* NOTE
* This function will block until the USB device is disconnected manually or the driver is stopped by calling @ref o3000_disconnect or @ref o3000_exit.
*
* @param id session ID
* @param device_nr device number to connect (use 0 to connect to first available device)
* @param config_msg configuration message to be sent to device after successful connection, or NULL if not used
* @param config_msg_len configuration message length
* @return 0 is returned only after calling @ref dmm_exit or @ref dmm_disconnect. Otherwise the error code defined at @ref o3000.h.
*/
int __stdcall o3000_connect(int id, int device_nr, char *config_msg, int config_msg_len) {
struct o3000_session_t *session;
int ret, i, dev_cnt, completed;
libusb_device *device;
libusb_device_handle *handle;
int error_code = 0;
session = get_session(id);
if(session == NULL) {
return O3000_ERROR_INVALID_SESSION_ID;
}
// reset all flags here
session->cleanup_transfers = FALSE;
session->disconnect_dev = FALSE;
session->release_session = FALSE;
// check whether device list is defined
if(session->device_list == NULL) {
ret = o3000_device_discovery(id);
if(ret < 0) {
o3000_log(session, O3000_LOG_ERROR, "%s: device discovery failed\n", __func__);
return ret;
}
}
// look for device in question
dev_cnt = 0;
device = NULL;
for(i = 0; i < session->num_device_list; i++) {
if(is_camera(session, session->device_list[i]) == TRUE) {
if(device_nr == dev_cnt) {
device = session->device_list[i];
break;
}
dev_cnt++;
}
}
if(device == NULL) {
o3000_log(session, O3000_LOG_ERROR, "%s: USB device not available\n", __func__);
error_code = O3000_ERROR_NODEV;
goto _connect_abort;
}
// now open the device
ret = libusb_open(device, &handle);
if(ret != 0) {
if(ret == LIBUSB_ERROR_NO_MEM) {
o3000_log(session, O3000_LOG_ERROR, "%s: opening device failed: no memory\n", __func__);
error_code = O3000_ERROR_NOMEM;
}
else if(ret == LIBUSB_ERROR_ACCESS) {
o3000_log(session, O3000_LOG_ERROR, "%s: opening device failed: no permission\n", __func__);
error_code = O3000_ERROR_ACCESS;
}
else if(ret == LIBUSB_ERROR_NO_DEVICE) {
o3000_log(session, O3000_LOG_ERROR, "%s: opening device failed: no such device\n", __func__);
error_code = O3000_ERROR_NODEV;
}
else {
o3000_log(session, O3000_LOG_ERROR, "%s: opening device failed: other\n", __func__);
error_code = O3000_ERROR_OTHER;
}
goto _connect_abort;
}
session->dev = handle;
// now claim the interface
ret = libusb_claim_interface(handle, 0);
if(ret != 0) {
if(ret == LIBUSB_ERROR_NOT_FOUND) {
o3000_log(session, O3000_LOG_ERROR, "%s: claiming device failed: requested device does not exist\n", __func__);
error_code = O3000_ERROR_NODEV;
}
else if(ret == LIBUSB_ERROR_BUSY) {
o3000_log(session, O3000_LOG_ERROR, "%s: claiming device failed: device already in use\n", __func__);
error_code = O3000_ERROR_BUSY;
}
else if(ret == LIBUSB_ERROR_NO_DEVICE) {
o3000_log(session, O3000_LOG_ERROR, "%s: claiming device failed: device has been disconnected\n", __func__);
error_code = O3000_ERROR_NODEV;
}
else {
o3000_log(session, O3000_LOG_ERROR, "%s: claiming device failed:other\n", __func__);
error_code = O3000_ERROR_OTHER;
}
goto _connect_abort;
}
// setup all USB transfers (video and XML)
if(prepare_usb_transfers(session)) {
o3000_log(session, O3000_LOG_ERROR, "%s: preparing USB transfers failed\n", __func__);
error_code = O3000_ERROR_OTHER;
goto _connect_abort;
}
/*
* Send configuration message to device if available
*/
if(config_msg != NULL && config_msg_len > 0) {
o3000_log(session, O3000_LOG_INFO, "%s: send configuration string\n", __func__);
ret = send_xml(session, config_msg, config_msg_len);
if(ret != 0) {
o3000_log(session, O3000_LOG_ERROR, "%s: sending configuration string failed (code %d)\n", __func__, ret);
}
}
/*
* Finally enter main-loop
*/
o3000_log(session, O3000_LOG_INFO, "%s: enter blocking loop for event handling\n", __func__);
session->running = TRUE;
while(session->running == TRUE) {
completed = 0;
ret = libusb_handle_events_completed(session->libusb_ctx, &completed);
if(ret != 0) {
o3000_log(session, O3000_LOG_ERROR, "%s: USB event handling error (code %d)\n", __func__, ret);
break;
}
}
/*
* There are following reasons to get here:
*
* 1. o3000_disconnect was called
* 2. o3000_exit was called
* 3. the device isn't available anymore because it was unplugged, it does an USB hardware reset
*
* The current session is cleaned-up only if o3000_exit has been called. Otherwise the session will further exist and a reconnection with o3000_connect will be allowed.
* Actually, it doesn't make any sense to connect to an unavailable device. But to keep it simple, a session is cleaned-up only with o3000_exit without any exception.
*/
if(session->release_session == FALSE && session->disconnect_dev == FALSE) {
// device was unplugged (see reason 3 few lines above)
o3000_log(session, O3000_LOG_DEBUG, "%s: release interface\n", __func__);
ret = libusb_release_interface(session->dev, 0);
if(ret != 0) {
o3000_log(session, O3000_LOG_ERROR, "%s: releasing interface failed (error code %d)\n", __func__, ret);
}
ret = O3000_ERROR_NODEV;
}
// close USB device
if(session->dev != NULL) {
o3000_log(session, O3000_LOG_DEBUG, "%s: close USB device handler\n", __func__);
libusb_close(session->dev);
session->dev = NULL;
}
// release session due to o3000_exit call
if(session->release_session == TRUE) {
o3000_log(session, O3000_LOG_DEBUG, "%s: cleanup session\n", __func__);
session->release_session = FALSE;
cleanup(id);
}
return ret;
_connect_abort:
// close USB device
if(session->dev != NULL) {
o3000_log(session, O3000_LOG_DEBUG, "%s: close USB device handler\n", __func__);
libusb_close(session->dev);
session->dev = NULL;
}
return error_code;
}
/**
* The device discovery returns the number of USB devices in question.
* In fact, the number of devices with the session PID/VID are counted. After knowing the number of devices, any connection can be
* established with @ref o3000_connect.
*
* @param id valid session ID
* @return number of USB device or a negative return code
*/
int __stdcall o3000_device_discovery(int id) {
int num_device, i, num_camera;
struct o3000_session_t *session;
session = get_session(id);
if(session == NULL) {
return O3000_ERROR_INVALID_SESSION_ID;
}
// check whether device list is allocated already
if(session->device_list != NULL) {
libusb_free_device_list(session->device_list, 1);
session->device_list = NULL;
}
// get list with all USB device currently attached to the system
session->num_device_list = 0;
num_device = libusb_get_device_list(session->libusb_ctx, &(session->device_list));
o3000_log(session, O3000_LOG_DEBUG, "%s: %d USB connected to the system\n", __func__, num_device);
if(num_device < 0) {
o3000_log(session, O3000_LOG_WARNING, "%s: currently no USB device attached to the system (code %d)\n", __func__, num_device);
return O3000_ERROR_NODEV;
}
session->num_device_list = num_device;
// count number of devices in question
num_camera = 0;
for(i = 0; i < num_device; i++) {
if(is_camera(session, session->device_list[i]) == TRUE) {
num_camera++;
}
}
o3000_log(session, O3000_LOG_INFO, "%s: number of cameras found: %d\n", __func__, num_camera);
return num_camera;
}
/**
* Get number of connected O-3000 cameras to the system.
*
* This function is thread save and can be called without an already opened
* session.
*
* @return number of connected cameras or negative error code (see @ref o3000.h)
*/
int __stdcall o3000_get_num_cam(void) {
libusb_context *libusb_ctx;
libusb_device **device_list;
int num_device, num_camera, i;
struct libusb_device_descriptor desc;
if(libusb_init(&libusb_ctx)) {
return O3000_ERROR_OTHER;
}
num_device = libusb_get_device_list(libusb_ctx, &device_list);
if(num_device < 0) {
libusb_exit(libusb_ctx);
return O3000_ERROR_NODEV;
}
// count number of devices in question
num_camera = 0;
for(i = 0; i < num_device; i++) {
if(libusb_get_device_descriptor(device_list[i], &desc)) {
libusb_free_device_list(device_list, 1);
libusb_exit(libusb_ctx);
return O3000_ERROR_OTHER;
}
if((desc.idVendor != O3000_VID) || (desc.idProduct != O3000_PID)) {
continue;
}
num_camera++;
}
libusb_free_device_list(device_list, 1);
libusb_exit(libusb_ctx);
return num_camera;
}
/**
* Logging function.
*
* Concatenates level and message and forwards the resulting string to the logging function.
*
* @param session session pointer
* @param level The logging level (from @ref O3000_LOG_NONE to @ref O3000_LOG_DEBUG)
* @param fmt The format string
*/
void __stdcall o3000_log(struct o3000_session_t *session, const int level, const char *fmt, ...) {
va_list args;
char str[1024];
char *str_ptr = str;
int level_int;
int max_level;
if(level > session->loglevel) {
return; // ignore messages beyond session loglevel
}
if(session->log_cb == NULL) {
// session has no callback registered; ignore request
return;
}
if(fmt == NULL) {
return;
}
// check range of level and limit (clamp) it
max_level = sizeof(dbg_strlevel)/sizeof(char*)-1;
level_int = (level > max_level) ? max_level : level;
str_ptr += sprintf(str_ptr, "%s: ", dbg_strlevel[level_int]);
va_start(args, fmt);
vsprintf(str, fmt, args);
va_end(args);
session->log_cb(session->id, str);
}
/**
* Initialize O-3000 session.
* A new O-3000 session is initialized by calling this function. Several buffers are allocated and the USB context is setup.
* The driver automatically divides the video cache to @ref NUM_USB_VIDEO_TRANSFER USB transfers. The video cache size must be at least
* an image frame size. It's recommended that the size is more the 2 times the maximum expected image size.
*
* A suitable video cache size is calculated by following example:
* Maximum expected image resolution: 1280x960 with 2 bytes/pixel
* Recommended video cache size: 1280*960*2*3 = 3686400 bytes = 3.5 MByte (3 times the image size)
*
* NOTE
* This function must be called before connecting to any O-3000 camera.
*
* @param vid USB vendor ID; use default VID if 0
* @param pid USB product ID; use default PID if 0
* @param video_cache_size Requested video cache size (in bytes); note: might be adjusted by the driver
* @param xml_cb XML receive callback
* @param video_cb Video receive callback
* @param log_cb Logging callback, or NULL if not used
* @param loglevel logging level (from @ref O3000_LOG_NONE to @ref O3000_LOG_VERBOSE)
* @return positive session ID starting from 0, or error code (see @ref o3000.h)
*/
int __stdcall o3000_init(int vid, int pid, unsigned int video_cache_size,
void __stdcall (*xml_cb)(int id, char* buf, int len),
void __stdcall (*video_cb)(int id, unsigned char* buf, struct img_header_t* img_header),
void __stdcall (*log_cb)(int id, char* msg),
int loglevel) {
int i, new_session_id, ret;
struct o3000_session_t *new_session;
int error_code = O3000_ERROR_OTHER;
int transfer_size, total_buffer_size;
// Validate callbacks
if(xml_cb == NULL || video_cb == NULL) {
return O3000_ERROR_NOCALLBACK;
}
/*
* Look for next free session
* Session table access must be atmic
*/
new_session_id = -1;
o3000_mutex_static_lock(&session_lock);
for(i = 0; i < MAX_NUM_SESSION; i++) {
if(session_table[i] == NULL) {
new_session_id = i;
break;
}
}
if(new_session_id == -1) {
return O3000_ERROR_NO_FREE_SESSION;
}
// allocate session and save session data
new_session = calloc(1, sizeof(struct o3000_session_t));
if(new_session == NULL) {
return O3000_ERROR_NOMEM;
}
session_table[new_session_id] = new_session;
o3000_mutex_static_unlock(&session_lock);
new_session->id = new_session_id;
new_session->vid = ((vid == 0) ? O3000_VID : vid); // set default if vid not set
new_session->pid = ((pid == 0) ? O3000_PID : pid); // set default if vid not set
if(loglevel < O3000_LOG_NONE || loglevel > O3000_LOG_VERBOSE) {
new_session->loglevel = O3000_LOG_NONE;
}
else{
new_session->loglevel = loglevel;
}
new_session->xml_cb = xml_cb;
new_session->video_cb = video_cb;
new_session->log_cb = log_cb;
o3000_log(new_session, O3000_LOG_INFO, "%s: O-3000 driver version %s\n", __func__, O3000_VERSION);
// open libusb context
ret = libusb_init(&(new_session->libusb_ctx));
if(ret != 0) {
o3000_log(new_session, O3000_LOG_ERROR, "%s: initializing libusb context failed (code %d)\n", __func__, ret);
error_code = O3000_ERROR_OTHER;
goto _o3000_init_abort;
}
/*
* If loglevel is set to O3000_LOG_VERBOSE, do set libusb loglevel to debug.
* This will flood the logging handler and is useful for debugging purposes only!
*/
if(loglevel == O3000_LOG_VERBOSE) {
libusb_set_debug(new_session->libusb_ctx, LIBUSB_LOG_LEVEL_DEBUG);
}
else {
libusb_set_debug(new_session->libusb_ctx, LIBUSB_LOG_LEVEL_NONE);
}
// allocate XML buffer
if((new_session->xml_in_buf = malloc(XML_IN_BUF_SIZE)) == NULL) {
o3000_log(new_session, O3000_LOG_ERROR, "%s: failed to allocate XML buffer\n", __func__);
error_code = O3000_ERROR_NOMEM;
goto _o3000_init_abort;
}
/*
* The image size must be larger or equal to the maximum image size ever to be expected. The
* video cache is divided in several adjacent bulk transfers.
*/
if(video_cache_size < MIN_VIDEO_CACHE_SIZE) {
video_cache_size = MIN_VIDEO_CACHE_SIZE;
}
// calculate video cache
transfer_size = video_cache_size/NUM_USB_VIDEO_TRANSFER;
if(transfer_size % 512) {
/*
* Do adjust USB bulk transfer size to a multiple of 512 bytes which is
* equal to the bulk payload size (at high-speed).
*/
transfer_size = ((transfer_size/512)+1)*512;
}
video_cache_size = NUM_USB_VIDEO_TRANSFER * transfer_size;
// calculate and allocate total buffer size
total_buffer_size = video_cache_size + MAX_IMAGE_SIZE;
if((new_session->video_cache = malloc(total_buffer_size)) == NULL) {
o3000_log(new_session, O3000_LOG_ERROR, "%s: failed to allocate video cache\n", __func__);
error_code = O3000_ERROR_NOMEM;
goto _o3000_init_abort;
}
new_session->frame_buf = new_session->video_cache + video_cache_size;
new_session->video_cache_size = video_cache_size;
new_session->video_chunk_size = transfer_size;
o3000_log(new_session, O3000_LOG_DEBUG, "%s: video cache %d bytes (divided into %d chunks of %d bytes)\n",
__func__, video_cache_size, NUM_USB_VIDEO_TRANSFER, transfer_size);
// allocate video transfer array
new_session->transfer_data = calloc(1, sizeof(struct libusb_transfer*)*NUM_USB_VIDEO_TRANSFER);
if(new_session->transfer_data == NULL) {
o3000_log(new_session, O3000_LOG_ERROR, "%s: allocating USB video transfers failed\n", __func__);
error_code = O3000_ERROR_NOMEM;
goto _o3000_init_abort;
}
o3000_log(new_session, O3000_LOG_INFO, "%s: new session initialized (ID %d, video cache %d bytes, video chunk %d x %d bytes)\n",
__func__, new_session_id, video_cache_size, NUM_USB_VIDEO_TRANSFER, new_session->video_chunk_size);
// finally return the valid session ID
return new_session_id;
_o3000_init_abort:
cleanup(new_session_id);
return error_code;
}
/**
* Exit O-3000 session.
* This function disconnects and closes an active session. Afterwards it releases all system resources.
*
* @param id session ID
*/
void __stdcall o3000_exit(int id) {
struct o3000_session_t *session;
session = get_session(id);
if(session == NULL) {
return;
}
if(session->running == FALSE) {
o3000_log(session, O3000_LOG_INFO, "%s: driver not at main-loop --> cleanup session\n", __func__, id);
cleanup(id);
}
else {
o3000_log(session, O3000_LOG_DEBUG, "%s: driver at main-loop --> release USB interface\n", __func__, id);
session->release_session = TRUE;
o3000_disconnect(id);
}
}
/** @} */