diff options
-rw-r--r-- | CMakeLists.txt | 32 | ||||
-rw-r--r-- | ChangeLog | 46 | ||||
-rw-r--r-- | LICENSE | 165 | ||||
-rw-r--r-- | cmake/FindLibUSB.cmake | 30 | ||||
-rw-r--r-- | cmake/LibFindMacros.cmake | 266 | ||||
-rw-r--r-- | image_header.h | 157 | ||||
-rw-r--r-- | o3000.c | 1431 | ||||
-rw-r--r-- | o3000.h | 101 | ||||
-rw-r--r-- | o3000_portable.h | 144 | ||||
-rw-r--r-- | o3000_private.h | 102 | ||||
-rw-r--r-- | o3000_xfer_handler.c | 175 | ||||
-rw-r--r-- | o3000_xfer_handler.h | 39 |
12 files changed, 2688 insertions, 0 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..074bf52 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,32 @@ +# +# O-3000 driver +# + +cmake_minimum_required(VERSION 2.4) +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) + +# find libusb library (version 1.0.19 or higher is needed!) +find_package(LibUSB REQUIRED) + +include_directories(.) +include_directories(${LIBUSB_INCLUDE_DIRS}) + +add_library( o3000 SHARED + o3000.c + o3000_private.h + o3000_xfer_handler.c + o3000_xfer_handler.h + ) + +set_target_properties ( o3000 PROPERTIES + OUTPUT_NAME "o3000" + VERSION 2.0.2 + SOVERSION 2 + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" + ) + +target_compile_options(o3000 PRIVATE -Wall -g -ggdb -O3 -fPIC) +target_link_libraries(o3000 pthread ${LIBUSB_LIBRARIES}) + +install(TARGETS o3000 DESTINATION lib) +install(DIRECTORY ../../include/o3000 DESTINATION include FILES_MATCHING PATTERN "*.h*") diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..f1ec48c --- /dev/null +++ b/ChangeLog @@ -0,0 +1,46 @@ +------------------------------------------------------------------------------- +- ChangeLog O-3000 Host Driver +------------------------------------------------------------------------------- + +Version 2.0.2 + 2018-04-11-PR + * Function handle_transfer() rewritten: + The image frame synchronization was a bit hacky. Image frames were passed only to the + overlaying application after the next image frame has been received. But when receiving a single + snapshot, there won't follow an image. This causes a delay of one image until the application + receives it. Now the function passes images to the application after a image is + received completely without checking the preamble of the following image. + * Bug race condition at o3000_init() function: + Accessing the session table for getting next free session and allocation session structure + must be atomic. + * New function o3000_get_num_cam() added: + This function returns the number of connected O-3000 cameras to the system. + * New loglevel O3000_LOG_VERBOSE added: + This loglevel will set the libusb to debugging mode and is very useful for debugging purposes. + +Version 2.0.1 + 2015-08-11 -PR + * Bugfix in function handle_transfer(): A negative byte offset was calculated when the resolution + has changed during wrap-around. A SEGFAULT occured. + * Fields added to image header and image header version update to 4: + - data rate in bytes/seconds + - frames per seconds + - field-of-view added used by lense distortion algorithm + * Short USB bulk transfers are supported now by the driver. Therefore the resolution + doesn't have to be a multiple of 512 bytes anymore and binning is done correctly. + There are following restrictions about image resolution settings: + - Window size must start with an even pixel coordinate (see examples below) + - Minimum image resolution is 640x480. + - Binning is allowed at full-screen only (1280x960) + E. g. valid window: X = 110 to 1111, Y = 32 to 917 + E. g. invalid window: X = 111 to 1111, Y = 33 to 917 --> coordinate 111 and 33 are odd numbers + + 2016-03-01 + * Calling convention added for Win32 and Win64. + +Version 2.0.0 - 2015-02-16 + * PR: Multi camera sessions capability. The host can scan the number of cameras and establish a connection to + each of them. Sending a XML message to the camera uses the synchronous API with OS dependet timeout handling. + +Version 1.0.0 - 2015-02-16 + * PB: Inititial version @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/cmake/FindLibUSB.cmake b/cmake/FindLibUSB.cmake new file mode 100644 index 0000000..6468937 --- /dev/null +++ b/cmake/FindLibUSB.cmake @@ -0,0 +1,30 @@ +# - Try to find libusb-1.0 +# Once done, this will define +# +# LIBUSB_FOUND - system has libusb-1.0 +# LIBUSB_INCLUDE_DIRS - the libusb-1.0 include directories +# LIBUSB_LIBRARIES - link these to use libusb-1.0 + +include(LibFindMacros) + +# Use pkg-config to get hints about paths +# libfind_pkg_check_modules(LIBUSB_PKGCONF libusb-1.0>=1.0.19) +libfind_pkg_check_modules(LIBUSB_PKGCONF libusb-1.0) + +# Include dir +find_path(LIBUSB_INCLUDE_DIR + NAMES libusb.h + PATHS ${LIBUSB_PKGCONF_INCLUDE_DIRS} +) + +# Finally the library itself +find_library(LIBUSB_LIBRARY + NAMES usb-1.0 + PATHS ${LIBUSB_PKGCONF_LIBRARY_DIRS} +) + +# Set the include dir variables and the libraries and let libfind_process do the rest. +# NOTE: Singular variables for this library, plural for libraries this this lib depends on. +set(LIBUSB_PROCESS_INCLUDES LIBUSB_INCLUDE_DIR) +set(LIBUSB_PROCESS_LIBS LIBUSB_LIBRARY) +libfind_process(LIBUSB) diff --git a/cmake/LibFindMacros.cmake b/cmake/LibFindMacros.cmake new file mode 100644 index 0000000..3ef5844 --- /dev/null +++ b/cmake/LibFindMacros.cmake @@ -0,0 +1,266 @@ +# Version 2.2 +# Public Domain, originally written by Lasse Kärkkäinen <tronic> +# Maintained at https://github.com/Tronic/cmake-modules +# Please send your improvements as pull requests on Github. + +# Find another package and make it a dependency of the current package. +# This also automatically forwards the "REQUIRED" argument. +# Usage: libfind_package(<prefix> <another package> [extra args to find_package]) +macro (libfind_package PREFIX PKG) + set(${PREFIX}_args ${PKG} ${ARGN}) + if (${PREFIX}_FIND_REQUIRED) + set(${PREFIX}_args ${${PREFIX}_args} REQUIRED) + endif() + find_package(${${PREFIX}_args}) + set(${PREFIX}_DEPENDENCIES ${${PREFIX}_DEPENDENCIES};${PKG}) + unset(${PREFIX}_args) +endmacro() + +# A simple wrapper to make pkg-config searches a bit easier. +# Works the same as CMake's internal pkg_check_modules but is always quiet. +macro (libfind_pkg_check_modules) + find_package(PkgConfig QUIET) + if (PKG_CONFIG_FOUND) + pkg_check_modules(${ARGN} QUIET) + endif() +endmacro() + +# Avoid useless copy&pasta by doing what most simple libraries do anyway: +# pkg-config, find headers, find library. +# Usage: libfind_pkg_detect(<prefix> <pkg-config args> FIND_PATH <name> [other args] FIND_LIBRARY <name> [other args]) +# E.g. libfind_pkg_detect(SDL2 sdl2 FIND_PATH SDL.h PATH_SUFFIXES SDL2 FIND_LIBRARY SDL2) +function (libfind_pkg_detect PREFIX) + # Parse arguments + set(argname pkgargs) + foreach (i ${ARGN}) + if ("${i}" STREQUAL "FIND_PATH") + set(argname pathargs) + elseif ("${i}" STREQUAL "FIND_LIBRARY") + set(argname libraryargs) + else() + set(${argname} ${${argname}} ${i}) + endif() + endforeach() + if (NOT pkgargs) + message(FATAL_ERROR "libfind_pkg_detect requires at least a pkg_config package name to be passed.") + endif() + # Find library + libfind_pkg_check_modules(${PREFIX}_PKGCONF ${pkgargs}) + if (pathargs) + find_path(${PREFIX}_INCLUDE_DIR NAMES ${pathargs} HINTS ${${PREFIX}_PKGCONF_INCLUDE_DIRS}) + endif() + if (libraryargs) + find_library(${PREFIX}_LIBRARY NAMES ${libraryargs} HINTS ${${PREFIX}_PKGCONF_LIBRARY_DIRS}) + endif() +endfunction() + +# Extracts a version #define from a version.h file, output stored to <PREFIX>_VERSION. +# Usage: libfind_version_header(Foobar foobar/version.h FOOBAR_VERSION_STR) +# Fourth argument "QUIET" may be used for silently testing different define names. +# This function does nothing if the version variable is already defined. +function (libfind_version_header PREFIX VERSION_H DEFINE_NAME) + # Skip processing if we already have a version or if the include dir was not found + if (${PREFIX}_VERSION OR NOT ${PREFIX}_INCLUDE_DIR) + return() + endif() + set(quiet ${${PREFIX}_FIND_QUIETLY}) + # Process optional arguments + foreach(arg ${ARGN}) + if (arg STREQUAL "QUIET") + set(quiet TRUE) + else() + message(AUTHOR_WARNING "Unknown argument ${arg} to libfind_version_header ignored.") + endif() + endforeach() + # Read the header and parse for version number + set(filename "${${PREFIX}_INCLUDE_DIR}/${VERSION_H}") + if (NOT EXISTS ${filename}) + if (NOT quiet) + message(AUTHOR_WARNING "Unable to find ${${PREFIX}_INCLUDE_DIR}/${VERSION_H}") + endif() + return() + endif() + file(READ "${filename}" header) + string(REGEX REPLACE ".*#[ \t]*define[ \t]*${DEFINE_NAME}[ \t]*\"([^\n]*)\".*" "\\1" match "${header}") + # No regex match? + if (match STREQUAL header) + if (NOT quiet) + message(AUTHOR_WARNING "Unable to find \#define ${DEFINE_NAME} \"<version>\" from ${${PREFIX}_INCLUDE_DIR}/${VERSION_H}") + endif() + return() + endif() + # Export the version string + set(${PREFIX}_VERSION "${match}" PARENT_SCOPE) +endfunction() + +# Do the final processing once the paths have been detected. +# If include dirs are needed, ${PREFIX}_PROCESS_INCLUDES should be set to contain +# all the variables, each of which contain one include directory. +# Ditto for ${PREFIX}_PROCESS_LIBS and library files. +# Will set ${PREFIX}_FOUND, ${PREFIX}_INCLUDE_DIRS and ${PREFIX}_LIBRARIES. +# Also handles errors in case library detection was required, etc. +function (libfind_process PREFIX) + # Skip processing if already processed during this configuration run + if (${PREFIX}_FOUND) + return() + endif() + + set(found TRUE) # Start with the assumption that the package was found + + # Did we find any files? Did we miss includes? These are for formatting better error messages. + set(some_files FALSE) + set(missing_headers FALSE) + + # Shorthands for some variables that we need often + set(quiet ${${PREFIX}_FIND_QUIETLY}) + set(required ${${PREFIX}_FIND_REQUIRED}) + set(exactver ${${PREFIX}_FIND_VERSION_EXACT}) + set(findver "${${PREFIX}_FIND_VERSION}") + set(version "${${PREFIX}_VERSION}") + + # Lists of config option names (all, includes, libs) + unset(configopts) + set(includeopts ${${PREFIX}_PROCESS_INCLUDES}) + set(libraryopts ${${PREFIX}_PROCESS_LIBS}) + + # Process deps to add to + foreach (i ${PREFIX} ${${PREFIX}_DEPENDENCIES}) + if (DEFINED ${i}_INCLUDE_OPTS OR DEFINED ${i}_LIBRARY_OPTS) + # The package seems to export option lists that we can use, woohoo! + list(APPEND includeopts ${${i}_INCLUDE_OPTS}) + list(APPEND libraryopts ${${i}_LIBRARY_OPTS}) + else() + # If plural forms don't exist or they equal singular forms + if ((NOT DEFINED ${i}_INCLUDE_DIRS AND NOT DEFINED ${i}_LIBRARIES) OR + ({i}_INCLUDE_DIR STREQUAL ${i}_INCLUDE_DIRS AND ${i}_LIBRARY STREQUAL ${i}_LIBRARIES)) + # Singular forms can be used + if (DEFINED ${i}_INCLUDE_DIR) + list(APPEND includeopts ${i}_INCLUDE_DIR) + endif() + if (DEFINED ${i}_LIBRARY) + list(APPEND libraryopts ${i}_LIBRARY) + endif() + else() + # Oh no, we don't know the option names + message(FATAL_ERROR "We couldn't determine config variable names for ${i} includes and libs. Aieeh!") + endif() + endif() + endforeach() + + if (includeopts) + list(REMOVE_DUPLICATES includeopts) + endif() + + if (libraryopts) + list(REMOVE_DUPLICATES libraryopts) + endif() + + string(REGEX REPLACE ".*[ ;]([^ ;]*(_INCLUDE_DIRS|_LIBRARIES))" "\\1" tmp "${includeopts} ${libraryopts}") + if (NOT tmp STREQUAL "${includeopts} ${libraryopts}") + message(AUTHOR_WARNING "Plural form ${tmp} found in config options of ${PREFIX}. This works as before but is now deprecated. Please only use singular forms INCLUDE_DIR and LIBRARY, and update your find scripts for LibFindMacros > 2.0 automatic dependency system (most often you can simply remove the PROCESS variables entirely).") + endif() + + # Include/library names separated by spaces (notice: not CMake lists) + unset(includes) + unset(libs) + + # Process all includes and set found false if any are missing + foreach (i ${includeopts}) + list(APPEND configopts ${i}) + if (NOT "${${i}}" STREQUAL "${i}-NOTFOUND") + list(APPEND includes "${${i}}") + else() + set(found FALSE) + set(missing_headers TRUE) + endif() + endforeach() + + # Process all libraries and set found false if any are missing + foreach (i ${libraryopts}) + list(APPEND configopts ${i}) + if (NOT "${${i}}" STREQUAL "${i}-NOTFOUND") + list(APPEND libs "${${i}}") + else() + set (found FALSE) + endif() + endforeach() + + # Version checks + if (found AND findver) + if (NOT version) + message(WARNING "The find module for ${PREFIX} does not provide version information, so we'll just assume that it is OK. Please fix the module or remove package version requirements to get rid of this warning.") + elseif (version VERSION_LESS findver OR (exactver AND NOT version VERSION_EQUAL findver)) + set(found FALSE) + set(version_unsuitable TRUE) + endif() + endif() + + # If all-OK, hide all config options, export variables, print status and exit + if (found) + foreach (i ${configopts}) + mark_as_advanced(${i}) + endforeach() + if (NOT quiet) + message(STATUS "Found ${PREFIX} ${${PREFIX}_VERSION}") + if (LIBFIND_DEBUG) + message(STATUS " ${PREFIX}_DEPENDENCIES=${${PREFIX}_DEPENDENCIES}") + message(STATUS " ${PREFIX}_INCLUDE_OPTS=${includeopts}") + message(STATUS " ${PREFIX}_INCLUDE_DIRS=${includes}") + message(STATUS " ${PREFIX}_LIBRARY_OPTS=${libraryopts}") + message(STATUS " ${PREFIX}_LIBRARIES=${libs}") + endif() + set (${PREFIX}_INCLUDE_OPTS ${includeopts} PARENT_SCOPE) + set (${PREFIX}_LIBRARY_OPTS ${libraryopts} PARENT_SCOPE) + set (${PREFIX}_INCLUDE_DIRS ${includes} PARENT_SCOPE) + set (${PREFIX}_LIBRARIES ${libs} PARENT_SCOPE) + set (${PREFIX}_FOUND TRUE PARENT_SCOPE) + endif() + return() + endif() + + # Format messages for debug info and the type of error + set(vars "Relevant CMake configuration variables:\n") + foreach (i ${configopts}) + mark_as_advanced(CLEAR ${i}) + set(val ${${i}}) + if ("${val}" STREQUAL "${i}-NOTFOUND") + set (val "<not found>") + elseif (val AND NOT EXISTS ${val}) + set (val "${val} (does not exist)") + else() + set(some_files TRUE) + endif() + set(vars "${vars} ${i}=${val}\n") + endforeach() + set(vars "${vars}You may use CMake GUI, cmake -D or ccmake to modify the values. Delete CMakeCache.txt to discard all values and force full re-detection if necessary.\n") + if (version_unsuitable) + set(msg "${PREFIX} ${${PREFIX}_VERSION} was found but") + if (exactver) + set(msg "${msg} only version ${findver} is acceptable.") + else() + set(msg "${msg} version ${findver} is the minimum requirement.") + endif() + else() + if (missing_headers) + set(msg "We could not find development headers for ${PREFIX}. Do you have the necessary dev package installed?") + elseif (some_files) + set(msg "We only found some files of ${PREFIX}, not all of them. Perhaps your installation is incomplete or maybe we just didn't look in the right place?") + if(findver) + set(msg "${msg} This could also be caused by incompatible version (if it helps, at least ${PREFIX} ${findver} should work).") + endif() + else() + set(msg "We were unable to find package ${PREFIX}.") + endif() + endif() + + # Fatal error out if REQUIRED + if (required) + set(msg "REQUIRED PACKAGE NOT FOUND\n${msg} This package is REQUIRED and you need to install it or adjust CMake configuration in order to continue building ${CMAKE_PROJECT_NAME}.") + message(FATAL_ERROR "${msg}\n${vars}") + endif() + # Otherwise just print a nasty warning + if (NOT quiet) + message(WARNING "WARNING: MISSING PACKAGE\n${msg} This package is NOT REQUIRED and you may ignore this warning but by doing so you may miss some functionality of ${CMAKE_PROJECT_NAME}. \n${vars}") + endif() +endfunction() + diff --git a/image_header.h b/image_header.h new file mode 100644 index 0000000..dd752e7 --- /dev/null +++ b/image_header.h @@ -0,0 +1,157 @@ +/**
+* @file image_header.h
+* @brief Image header definition
+* @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>
+*
+*/
+
+
+#ifndef _IMAGE_HEADER_H
+#define _IMAGE_HEADER_H
+
+#include "o3000/o3000_portable.h"
+
+#define O3000_IMG_HEADER_VERSION 4 ///< O-3000 image header version
+
+/**
+ * Image header size in bytes
+ *
+ * @note The struct img_header_t defined below must be a multiple of 512 bytes.
+ * The macro @ref IMAGE_HEADER_SIZE just helps to verify the header size, as done with
+ * the assert command in image_header_init. Unfortunately, assertions in C can only
+ * be performed at runtime, not at compile-time...
+ */
+#define IMAGE_HEADER_SIZE 512
+
+/**
+ * Image data format.
+ * These are all possible values for the field format in struct img_header_t.
+ */
+enum enumDataFormat_t {
+ DF_RAW_MONO_8 = 0, ///< monochrome image with 8 bit per pixel
+ DF_RAW_MONO_12, ///< monochrome image with 12 bit per pixel
+ DF_RAW_BAYER_8, ///< bayer image with 8 bit per pixel
+ DF_RAW_BAYER_12, ///< bayer image with 12 bit per pixel
+ DF_HDR_MONO_20_COMP, ///< 20 bit HDR monochrome image compressed to 12 bits
+ DF_HDR_BAYER_20_COMP, ///< 20 bit HDR bayer image compressed to 12 bits
+};
+
+/**
+ * Bayer pattern color order.
+ * These are all possible values for the field bayer pattern in struct img_header_t.
+ * The individual values describe the order of the pixels in the image data array.
+ * Depending on the start coordinate, the pattern starts with a red,
+ * green (2 possibilities), or blue pixel.
+ * The pattern then repeats every 2 pixels in the X- and Y-direction.
+ *
+ * @code
+ * X | Y | 1st row | 2nd row | enum value
+ * -----+-------+--------------+--------------+-----------
+ * even | even | Gr, R, Gr, R | B, Gb, B, Gb | BP_GR
+ * odd | even | R, Gr, R, Gr | Gb, B, Gb, B | BP_RG
+ * even | odd | B, Gb, B, Gb | Gr, R, Gr, R | BP_BG
+ * odd | odd | Gb, B, Gb, B | R, Gr, R, Gr | BP_GB
+ * 1st row: first row data within the region of interest
+ *
+ * R: red pixel
+ * Gr: green pixel, adjacent to red pixel
+ * Gb: green pixel, adjacent to blue pixel
+ * B: blue pixel
+ * @endcode
+ */
+enum enumBayerPattern_t {
+ BP_GR = 0, ///< first row: green-red-green-red-...
+ BP_RG, ///< first row: red-green-red-green-...
+ BP_BG, ///< first row: blue-green-blue-green-...
+ BP_GB, ///< first row: green-blue-green-blue-...
+};
+
+
+/**
+ * Image header (must be a multiple of 512 bytes)
+ *
+ * @code
+ *
+ * X/Y coordinate system
+ *
+ * +---------------------------------> x-axis
+ * |0/0 1/0 2/0
+ * |0/1 1/1 2/1
+ * |
+ * | +-----------------+
+ * | | |
+ * | | field of view |
+ * | | |
+ * | +-----------------+
+ * v
+ * y-axis
+ *
+ * @endcode
+ *
+ * - upper left corner starts with coordinate 0/0
+ * - the image width defines the maximum horizontal view in x-direction
+ * - the image height defines the maximum vertical view y-direction
+ *
+ * As the sensor data may contain embedded statistics which should be ignored
+ * by the application, image_start defines the offset in bytes of the actual image
+ * data. The payload size contains the regular image size plus the size
+ * of the embedded data. If there is no embedded data, image start offset
+ * is 0 and the payload and image size are equal.
+ *
+ * @note Embedded data not only contains statistics lines at the beginning,
+ * but also at the end of the image. The field @ref bayer_pattern is defined for bayer-based
+ * data formats only (e.g. DF_RAW_BAYER_8).
+ */
+struct img_header_t {
+
+ // since image header version 1
+ uint32_t preamble[2]; ///< constant preamble (8 bytes)
+ uint32_t version; ///< header version number (4 bytes)
+ uint32_t payload_size; ///< payload size (in bytes) (4 bytes)
+ uint32_t image_start; ///< image start offset (in bytes) (4 bytes)
+ uint32_t image_size; ///< image size (in bytes) (4 bytes)
+ uint32_t width; ///< current image width (x-direction) (in pixels) (4 bytes)
+ uint32_t height; ///< current image height (y-direction) (in pixels) (4 bytes)
+ uint32_t format; ///< image data format (see @ref enumDataFormat_t) (4 bytes)
+ uint32_t frame_count; ///< frame sequence number (4 bytes)
+
+ // since image header version 2
+ uint32_t bayer_pattern; ///< bayer pattern color order (see @ref enumBayerPattern_t) (4 bytes)
+
+ // since image header version 3
+ uint32_t datarate; ///< USB data rate in bytes per seconds (4 bytes)
+ float fps; ///< frames per seconds (4 bytes)
+
+ // since image header version 4
+ uint16_t fov_x_start; ///< start x-cooredinate of current field of view (2 bytes)
+ uint16_t fov_x_end; ///< end x-cooredinate of current field of view (2 bytes)
+ uint16_t fov_y_start; ///< start y-cooredinate of current field of view (2 bytes)
+ uint16_t fov_y_end; ///< end y-cooredinate of current field of view (2 bytes)
+
+ uint8_t padding[452]; ///< padding bytes to fill header (452 bytes)
+} __packed__;
+
+
+#endif // _IMAGE_HEADER_H
@@ -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); + } +} + +/** @} */ @@ -0,0 +1,101 @@ +/**
+* @file o3000.h
+* @brief O-3000 Camera driver API
+* @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>
+*
+*/
+
+#ifndef _O3000_H
+#define _O3000_H
+
+#include "o3000/o3000_portable.h"
+#include "o3000/image_header.h"
+
+/**
+ * O-3000 library version
+ */
+#define O3000_VERSION "2.0.2"
+
+#define O3000_VID 0x0483 ///< O3000 vendor ID
+#define O3000_PID 0xA098 ///< O3000 product ID
+
+
+/*
+ * All possible error codes
+ */
+#define O3000_SUCCESS 0 ///< success (no error)
+#define O3000_ERROR_NOCALLBACK -1 ///< one or more callback functions are missing
+#define O3000_ERROR_NOMEM -2 ///< failed to allocate memory
+#define O3000_ERROR_NODEV -3 ///< device not found
+#define O3000_ERROR_NO_FREE_SESSION -4 ///< all session are in used
+#define O3000_ERROR_INVALID_SESSION_ID -5 ///< session ID is invalid
+#define O3000_ERROR_ACCESS -6 ///< user has insufficient permissions
+#define O3000_ERROR_BUSY -7 ///< another program or driver has claimed the device
+#define O3000_ERROR_DRV_NOT_CONNECTED -8 ///< O-3000 driver is not running (call o3000_connect() first)
+#define O3000_ERROR_XML_XFER_RUNNING -9 ///< XML transfer to device in progress
+#define O3000_ERROR_USB_TRANSFER_TIMEOUT -10 ///< USB transfer timeout
+#define O3000_ERROR_USB_EP_HALTED -11 ///< USB endpoint halted
+#define O3000_ERROR_LESS_DATA -12 ///< USB received less data than expected
+
+#define O3000_ERROR_OTHER -1000 ///< other (unspecified) error
+
+
+/*
+ * All possible logging levels (don't touch it!!)
+ */
+#define O3000_LOG_NONE 0 ///< nothing is logged
+#define O3000_LOG_ERROR 1 ///< error message (top priority log message)
+#define O3000_LOG_WARNING 2 ///< warning message
+#define O3000_LOG_INFO 3 ///< info message
+#define O3000_LOG_DEBUG 4 ///< debug message
+#define O3000_LOG_VERBOSE 5 ///< verbose debug message, libusb is debugging too (lowest priority log message)
+
+
+#if defined(__cplusplus) || defined(c_plusplus)
+extern "C" {
+#endif
+
+
+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);
+
+void __stdcall o3000_exit(int id);
+
+int __stdcall o3000_get_num_cam(void);
+int __stdcall o3000_device_discovery(int id);
+int __stdcall o3000_connect(int id, int device_nr, char *config_msg, int config_msg_len);
+int __stdcall o3000_disconnect(int id);
+
+int __stdcall o3000_send_xml(int id, const char *msg, int msg_len);
+
+
+#if defined(__cplusplus) || defined(c_plusplus)
+} // extern "C"
+#endif
+
+#endif // _O3000_H
diff --git a/o3000_portable.h b/o3000_portable.h new file mode 100644 index 0000000..4e7da6a --- /dev/null +++ b/o3000_portable.h @@ -0,0 +1,144 @@ +/**
+* @file o3000_portable.h
+* @brief O-3000 definitions to make portable platform-independent code
+* @author Patrick Roth - roth@stettbacher.ch
+* @author Christian Jaeggi - jaeggi@stettbacher.ch
+* @version 1.0
+* @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>
+*
+*/
+
+
+#ifndef _O3000_PORTABLE_H
+#define _O3000_PORTABLE_H
+
+
+/* stdint.h is not available on older MSVC */
+#if defined(_MSC_VER) && (_MSC_VER < 1600) && (!defined(_STDINT)) && (!defined(_STDINT_H))
+
+typedef unsigned __int8 uint8_t;
+typedef unsigned __int16 uint16_t;
+typedef unsigned __int32 uint32_t;
+typedef unsigned __int64 uint64_t;
+
+#else
+
+#include <stdint.h>
+
+#endif // defined(_MSC_VER) && (_MSC_VER < 1600) && (!defined(_STDINT)) && (!defined(_STDINT_H))
+
+
+
+/*
+ * Calling conventions
+ * The System V ABI is one of the major ABIs in use today and is virtually universal among Unix
+ * systems. It is the calling convention used by toolchains such as i686-elf-gcc and x86_64-elf-gcc.
+ *
+ * Windows builds often use "project files" and are less transparent and more ad hoc than Makefiles.
+ * As an example, the driver crashes by calling a callback previously register via .NET. Therfore
+ * on MSVC, the calling ABI is defined. On other system (UNIX), the macros below are left
+ * empty.
+ *
+ * _WIN32 is always defined for Win32 or Win64 applications.
+ */
+#ifndef _WIN32
+
+#define __cdecl /* nothing */
+#define __stdcall /* nothing */
+#define __fastcall /* nothing */
+
+#else
+
+#include <windows.h>
+
+#endif // _WIN32
+
+
+/*
+ * aligned malloc functiions
+ */
+#ifndef _WIN32
+
+#define ALIGNED_ALLOC(align, size) aligned_alloc(align, size)
+#define ALIGNED_FREE(buf) free(buf)
+
+#else
+
+#include <malloc.h>
+#define ALIGNED_ALLOC(align, size) _aligned_malloc(size, align)
+#define ALIGNED_FREE(buf) _aligned_free(buf)
+
+#endif // _WIN32
+
+
+/*
+ * structure packing macros
+ */
+#if defined(_MSC_VER)
+
+#define __func__ __FUNCTION__
+#define __packed__ __packed
+
+#else
+
+/* define nothing for __func__ because it's used as default macro at source code */
+#define __packed__ __attribute__ ((packed))
+
+#endif // defined(_MSC_VER)
+
+
+/*
+ * mutexes
+ *
+ * copied from libusb project see files os/threads_windows.* and os/threads_poxix.*
+ */
+#if defined(_MSC_VER)
+
+#define O3000_MUTEX_INITIALIZER 0L
+typedef LONG o3000_mutex_static_t;
+static inline void o3000_mutex_static_lock(o3000_mutex_static_t *mutex) {
+ while (InterlockedExchange(mutex, 1L) == 1L)
+ SleepEx(0, TRUE);
+}
+static inline void o3000_mutex_static_unlock(o3000_mutex_static_t *mutex) {
+ InterlockedExchange(mutex, 0L);
+}
+
+#else
+
+#include <pthread.h>
+
+#define O3000_MUTEX_INITIALIZER PTHREAD_MUTEX_INITIALIZER
+typedef pthread_mutex_t o3000_mutex_static_t;
+static inline void o3000_mutex_static_lock(o3000_mutex_static_t *mutex) {
+ (void)pthread_mutex_lock(mutex);
+}
+static inline void o3000_mutex_static_unlock(o3000_mutex_static_t *mutex) {
+ (void)pthread_mutex_unlock(mutex);
+}
+
+#endif // defined(_MSC_VER)
+
+
+#endif // _O3000_PORTABLE_H
+
+
diff --git a/o3000_private.h b/o3000_private.h new file mode 100644 index 0000000..7087d46 --- /dev/null +++ b/o3000_private.h @@ -0,0 +1,102 @@ +/** +* @file o3000_private.h +* @brief O-3000 Camera driver API (internal) +* @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> +* +*/ + + +#ifndef __O3000_PRIVATE_H +#define __O3000_PRIVATE_H + +#include <libusb-1.0/libusb.h> +#include "o3000/o3000_portable.h" +#include "o3000/image_header.h" + + +#define TRUE 1 ///< TRUE value (not 0) +#define FALSE 0 ///< FALSE value must be 0 + + + +/** + * Image frame synchronization state + */ +enum image_frame_state_t { + IMG_FRAME_STATE_NOSYNC = 0, ///< not synchronized (preamble not found) + IMG_FRAME_STATE_SYNC, ///< synchronized (preamble found) +}; + + +/** + * O-3000 session description + * + * Each session descripes a connection between the camera and the user application. + * + * @note These data are used internally and are not accessible from the user application. + */ +struct o3000_session_t { + int id; ///< session ID + libusb_context *libusb_ctx; ///< libusb context pointer + struct libusb_device_handle *dev; ///< the USB device handler (a connection to this device is established) + int running; ///< running flag: TRUE if driver is connected and at main-loop (function o3000_connect() is blocking) + int cleanup_transfers; ///< TRUE if driver is cleaning up all USB transfers + int disconnect_dev; ///< TRUE if the user wants to disconnect the USB device by software. This flag is set by o3000_disconnect(). + int release_session; ///< TRUE indicates to release session when leaving main-loop (see function o3000_connect()). This flag is set by o3000_exit(). + + int vid; ///< USB vendor ID used for this session + int pid; ///< USB product ID used for this session + libusb_device **device_list; ///< USB device list with pid and vid defined above + int num_device_list; ///< number of entries at device list + + int loglevel; ///< logging level (from @ref O3000_LOG_NONE to @ref O3000_LOG_DEBUG) + + void __stdcall (*xml_cb)(int id, char* buf, int len); ///< XML callback + void __stdcall (*video_cb)(int id, unsigned char* buf, struct img_header_t* img_header); ///< video callback + void __stdcall (*log_cb)(int id, char* msg); //< logging callback + + + char *xml_in_buf; ///< buffer for inbound XML messages + uint8_t *video_cache; ///< video data buffer + int video_chunk_size; ///< chunk size of one video transfer in bytes + uint8_t *frame_buf; ///< start of frame buffer, memory-adjacent to video cache + unsigned int video_cache_size; ///< actual video cache size in bytes + + struct libusb_transfer **transfer_data; ///< generic USB transfer structures for incoming video data (dyn. allocated array of transfers) + struct libusb_transfer *transfer_xml_in; ///< generic USB transfer structures for incoming XML messages + + enum image_frame_state_t frame_state; ///< current image frame synchronization state + uint8_t *frame_start; ///< start address of image frame (in case if @ref IMG_FRAME_STATE_SYNC) + int frame_rx_cnt; ///< image frame payload counter, image received if @ref frame_payload_cnt is more or equal @ref frame_size + struct img_header_t *frame_img_header; ///< start address of current image header + uint8_t *image_start; ///< start address of pixel data + int frame_size; ///< image frame size (including image header) +}; + + + +void __stdcall o3000_log(struct o3000_session_t *session, const int level, const char *fmt, ...); + +#endif // __O3000_PRIVATE_H diff --git a/o3000_xfer_handler.c b/o3000_xfer_handler.c new file mode 100644 index 0000000..4ae8b2f --- /dev/null +++ b/o3000_xfer_handler.c @@ -0,0 +1,175 @@ +/**
+* @file o3000_xfer_handler.c
+* @brief O-3000 USB transfer handler
+* @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>
+*
+*/
+
+
+#include <string.h>
+
+#include "o3000/o3000.h"
+#include "o3000_private.h"
+
+
+/**
+ * The preamble, used to detect the beginning of a new frame
+ */
+static const uint8_t preamble[] = {0xaa, 0x55, 0xde, 0xad, 0xbe, 0xef, 0x55, 0xaa};
+
+/**
+ * Check whether the argument points to a valid preamble.
+ *
+ * @param p Memory pointer
+ * @return TRUE: points to valid preamble, FALSE: no valid preamble
+ */
+static int is_preamble(uint8_t *p) {
+ if( (*p == preamble[7]) &&
+ (*(p+1) == preamble[6]) &&
+ (*(p+2) == preamble[5]) &&
+ (*(p+3) == preamble[4]) &&
+ (*(p+4) == preamble[3]) &&
+ (*(p+5) == preamble[2]) &&
+ (*(p+6) == preamble[1]) &&
+ (*(p+7) == preamble[0])) {
+ return TRUE;
+ }
+ else {
+ return FALSE;
+ }
+}
+
+
+/**
+ * Scan for the preamble in the buffer specified.
+ *
+ * @param buf Buffer start address
+ * @param size Buffer size
+ * @return preamble offset in bytes (w.r.t. to start address) or -1 if not found
+ */
+static int scan_for_preamble(uint8_t *buf, int size) {
+
+ int i, size_preamble, end;
+
+ size_preamble = sizeof(((struct img_header_t*)0)->preamble);
+ end = size - size_preamble;
+
+ for(i = 0; i < end; i++) {
+ if(is_preamble(buf+i) == TRUE) {
+ return i; // preamble found at offset i
+ }
+ }
+ return -1; // preamble not found
+}
+
+/**
+ * Video transfer handler
+ *
+ * This function recursively scans through a video transfer (video data chunk).
+ * Incomplete image frames are tracked continuously. Whenever an image frame is completed, the video callback handler is called.
+ *
+ * NOTE
+ * The video-cache consists of severalf memory-adjacent USB transfers plus an adjacent framebuffer. In case of a wrap-around
+ * the end of the image frame (located at the start of the video-cache) is copied to the framebuffer. So this frame won't be fragmented.
+ * In case of receiving short USB transfers, two image frames won't be adjacent at the video-cache. Therfore, after handling a image
+ * the state machine is set to not-synced state.
+ *
+ * @param session current session (is never NULL otherwise SEGFAULT)
+ * @param addr Start address of block to be scanned.
+ * @param len Lenght of block.
+ */
+void handle_transfer(struct o3000_session_t *session, uint8_t *addr, int len) {
+
+ int offset;
+ struct img_header_t *img_header; // points to current image header structure
+ int wraparound_chunk_size; // chunk size (bytes) at wrap-around to copy to frame buffer
+ int frame_data_remain; // remaining image frame data to process in bytes
+
+ if(session->frame_state == IMG_FRAME_STATE_NOSYNC) {
+ offset = scan_for_preamble(addr, len);
+ if(offset < 0) {
+ o3000_log(session, O3000_LOG_WARNING, "%s: preamble not found (start address=%p, len=%d)\n",
+ __func__, addr - session->video_cache, len);
+ return;
+ }
+
+ // preamble found, we are synchronized now!
+ session->frame_state = IMG_FRAME_STATE_SYNC;
+ session->frame_start = addr + offset; // set start address of image frame
+ session->frame_rx_cnt = 0; // reset data counter
+ len -= offset; // skip image data located before preamble
+
+ o3000_log(session, O3000_LOG_DEBUG, "%s: preamble found at address offset %d, remaining data of current image frame %d bytes\n",
+ __func__, session->frame_start - session->video_cache, len);
+
+ // parse image header
+ img_header = (struct img_header_t*)(session->frame_start);
+ session->frame_img_header = img_header;
+ session->image_start = session->frame_start + IMAGE_HEADER_SIZE + img_header->image_start;
+ session->frame_size = img_header->payload_size + IMAGE_HEADER_SIZE;
+ }
+
+
+ if(session->frame_state == IMG_FRAME_STATE_SYNC) {
+ session->frame_rx_cnt += len;
+
+ o3000_log(session, O3000_LOG_DEBUG, "%s: sync: %d bytes of %d bytes (image size) received\n",
+ __func__, session->frame_rx_cnt, session->frame_size);
+
+ // checking whether whole image frame is received
+ if(session->frame_rx_cnt >= session->frame_size) {
+
+ o3000_log(session, O3000_LOG_DEBUG, "%s: sync: image frame received\n", __func__);
+
+ if(session->frame_start > addr) {
+ /*
+ * We have a wrap-around to resolve. Do copy part of the image frame which resides at the
+ * start of the video cache to the frame buffer. Note that the frame buffer is memory-adjacent
+ * to the video cache.
+ */
+ wraparound_chunk_size = session->frame_size - (session->frame_buf - session->frame_start);
+ o3000_log(session, O3000_LOG_DEBUG, "%s: wrap-around, copy %d bytes to frame buffer\n", __func__, wraparound_chunk_size);
+ memcpy(session->frame_buf, session->video_cache, wraparound_chunk_size);
+ }
+
+ // delegate image to overlaying application
+ session->video_cb(session->id, session->image_start, session->frame_img_header);
+
+ /*
+ * Image frame is processed.
+ * The following image may won't be memory-adjacent to this one. Because a snapshot
+ * is sent with zero packet. The easiest way is to change state to NOT SYNC to trigger
+ * syncronization part again.
+ */
+ session->frame_state = IMG_FRAME_STATE_NOSYNC;
+ frame_data_remain = session->frame_rx_cnt - session->frame_size;
+ o3000_log(session, O3000_LOG_DEBUG, "%s: sync: %d bytes remaining from next image frame\n", __func__, frame_data_remain);
+ if(frame_data_remain > 0) {
+ handle_transfer(session, addr+(len-frame_data_remain), frame_data_remain);
+ }
+ }
+ }
+}
+
diff --git a/o3000_xfer_handler.h b/o3000_xfer_handler.h new file mode 100644 index 0000000..09fdb3b --- /dev/null +++ b/o3000_xfer_handler.h @@ -0,0 +1,39 @@ +/** +* @file o3000_xfer_handler.h +* @brief O-3000 USB transfer handler +* @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> +* +*/ + + +#ifndef __O3000_XFER_HANDLER_H +#define __O3000_XFER_HANDLER_H + +#include "o3000/o3000_portable.h" +#include "o3000_private.h" + +void handle_transfer(struct o3000_session_t *session, uint8_t *addr, int len); + +#endif // __O3000_XFER_HANDLER_H |