diff options
author | Patrick Roth <roth@stettbacher.ch> | 2019-10-04 10:17:42 +0200 |
---|---|---|
committer | Patrick Roth <roth@stettbacher.ch> | 2019-10-04 10:17:42 +0200 |
commit | e60ccb8965bbc1a460bf85bfc87b885cf1260151 (patch) | |
tree | 5b0564272b5c0637c69edc8e074a6b9e5319c57f /o3000.c | |
download | o3000-driver-e60ccb8965bbc1a460bf85bfc87b885cf1260151.tar.gz o3000-driver-e60ccb8965bbc1a460bf85bfc87b885cf1260151.zip |
Initial commit
import from github
Diffstat (limited to '')
-rw-r--r-- | o3000.c | 1431 |
1 files changed, 1431 insertions, 0 deletions
@@ -0,0 +1,1431 @@ +/** +* @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 +* +* <PRE> +* 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 +* </PRE> +* +*/ + + +/** + +@mainpage O-3000 camera driver + +@section intro Introduction + +This document describes the Application Programming Interface (API) for the +<b>Stettbacher Signal Processing USB O-3000 Camera</b>, further referenced as "the camera".</p> +The driver delivers a generic user interface for accessing the camera and runs on different operating system like: +<ul> +<li> Linux </li> +<li> Windows </li> +<li> MAC </li> +</ul> +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: +<ol> +<li> A set of functions provided by the camera API which can be called from + the user application (see @ref api_fun). </li> +<li> A set of callback functions the user application must implement and + register with the camera API (see @ref cb_fun). </li> +</ol> + +@subsection api_fun API functions + +The basic approach using the camera driver is: +<ol> +<li> Implementing all required callback functions, see @ref cb_fun. </li> +<li> Initialising the camera driver, see o3000_init(). </li> +<li> Connecting to the camera driver, see o3000_connect(). </li> +</ol> + +Once the user application is connected to the driver by calling o3000_connect(), it will block until: +<ul> +<li> The driver exits by calling o3000_exit(). </li> +<li> The camera is disconnected from the system. </li> +</ul> + +XML messages are transfered to the camera by calling 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 o3000_connect() is called from. This means that the 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 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 o3000_connect()! + +@code + + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <o3000/o3000.h> + + +static char xml_msg[] = {"<camera><stream></stream></camera>"}; + +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 <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> + +#include "o3000/o3000_portable.h" +#include "o3000_private.h" +#include "o3000/o3000.h" +#include "o3000_xfer_handler.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 + + +/** + * Maximum image size ever expected. + * + * @note The size must be a multiple of 512 bytes! + */ +#define MAX_IMAGE_SIZE (1280*964*2+IMAGE_HEADER_SIZE) + +/** + * Minimum video cache size. + * It's the double of the maximum image size ever expected. + */ +#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 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)); +} + + +/** + * Disconnect a currently claimed USB connection. + * Use this function if the driver is connected to an USB device. The o3000_connect() function-call is blocking now. + * After disconnecting, the function o3000_connect() returns. The session is not cleaned-up due to the driver is ready the reconnect + * to the same USB device by calling 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 o3000_get_num_device(). + * + * NOTE + * This function will block until the USB device is disconnected manually or the driver is stopped by calling 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 dmm_exit() or 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 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; + + + // 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 closes an active session and 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); + } +} + +/** @} */ |