diff options
author | Matthias Vogelgesang <matthias.vogelgesang@kit.edu> | 2016-02-16 11:53:28 +0100 |
---|---|---|
committer | Matthias Vogelgesang <matthias.vogelgesang@kit.edu> | 2016-02-16 11:53:28 +0100 |
commit | f431158ae2412ed23bd4d2336af00d2b5c170d31 (patch) | |
tree | 52c5fbbc7ba26f7444edbe7d0e8d80ed67796384 | |
download | uca-net-f431158ae2412ed23bd4d2336af00d2b5c170d31.tar.gz uca-net-f431158ae2412ed23bd4d2336af00d2b5c170d31.tar.bz2 uca-net-f431158ae2412ed23bd4d2336af00d2b5c170d31.tar.xz uca-net-f431158ae2412ed23bd4d2336af00d2b5c170d31.zip |
Initial commit
-rw-r--r-- | CMakeLists.txt | 34 | ||||
-rw-r--r-- | README.md | 22 | ||||
-rw-r--r-- | cmake/ConfigurePaths.cmake | 90 | ||||
-rw-r--r-- | cmake/PkgConfigVars.cmake | 30 | ||||
-rw-r--r-- | uca-net-camera.c | 296 | ||||
-rw-r--r-- | uca-net-camera.h | 76 | ||||
-rw-r--r-- | uca-net-client.c | 207 | ||||
-rw-r--r-- | uca-net-protocol.h | 85 | ||||
-rw-r--r-- | uca-net-server.c | 174 | ||||
-rw-r--r-- | ucad.c | 223 |
10 files changed, 1237 insertions, 0 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..bfd96d6 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required(VERSION 2.6) +project(ucanet C) + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") + +include(FindPackageHandleStandardArgs) +include(PkgConfigVars) +include(ConfigurePaths) + +find_package(PkgConfig) + +add_definitions("-std=c99 -Wall") +add_definitions(-DG_LOG_DOMAIN="Uca-Net") + +pkg_check_modules(GIO gio-2.0>=2.22 REQUIRED) +pkg_check_modules(UCA libuca>=2.1.0 REQUIRED) +pkg_check_variable(libuca plugindir) + +include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${UCA_INCLUDE_DIRS} ${GIO_INCLUDE_DIRS} ${MSGPACK_INCLUDE_DIRS}) +link_directories(${UCA_LIBRARY_DIRS} ${GIO_LIBRARY_DIRS}) + +# uca-net client camera +add_library(ucanet SHARED uca-net-camera.c uca-net-client.c) +target_link_libraries(ucanet ${UCA_LIBRARIES} ${MSGPACK_LIBRARIES}) + +install(TARGETS ucanet LIBRARY DESTINATION ${LIBUCA_PLUGINDIR}) + +# uca-net server +configure_paths(UCAD) +add_executable(ucad ucad.c uca-net-server.c) + +target_link_libraries(ucad ${UCA_LIBRARIES} ${GIO_LIBRARIES}) + +install(TARGETS ucad RUNTIME DESTINATION ${UCAD_BINDIR} COMPONENT executables) diff --git a/README.md b/README.md new file mode 100644 index 0000000..2352ac1 --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +## uca-net + +A transparent TCP-based network bridge camera for remote access of libuca +cameras. + +### Installation and usage + +The only dependency is libuca itself and any camera you wish to access. Build +the server and client with + + $ mkdir build && cd build + $ cmake .. + $ make && (sudo) make install + +Now, you can start a server on a remote machine with + + $ ucad mock + +and connect to it from any other machine, e.g. + + $ uca-grab -n 10 net # grab ten frames + $ uca-camera-control -c net # control graphically diff --git a/cmake/ConfigurePaths.cmake b/cmake/ConfigurePaths.cmake new file mode 100644 index 0000000..df9c03c --- /dev/null +++ b/cmake/ConfigurePaths.cmake @@ -0,0 +1,90 @@ +# - pre-configured paths for CMake +# +# Usage: +# configure_paths(<PREFIX>) +# +# Checks if configure-like prefix and installation paths were passed by the user +# and sets up corresponding variables for use in install() commands and to fill +# out .pc files: +# +# PREFIX_PREFIX defaults to ... CMAKE_INSTALL_PREFIX +# PREFIX_EPREFIX PREFIX_PREFIX +# PREFIX_SBINDIR PREFIX_EPREFIX/sbin +# PREFIX_SYSCONFDIR PREFIX_PREFIX/etc +# PREFIX_LOCALSTATEDIR PREFIX_PREFIX/var +# PREFIX_BINDIR PREFIX_EPREFIX/bin +# PREFIX_LIBDIR PREFIX_EPREFIX/lib +# PREFIX_INCLUDEDIR PREFIX_PREFIX/include +# PREFIX_PKGCONFIGDIR PREFIX_LIBDIR/pkgconfig +# PREFIX_TYPELIBDIR PREFIX_LIBDIR/girepository-1.0 +# PREFIX_DATAROOTDIR PREFIX_PREFIX/share +# PREFIX_DATADIR PREFIX_DATAROOTDIR +# PREFIX_INFODIR PREFIX_DATAROOTDIR/info +# PREFIX_MANDIR PREFIX_DATAROOTDIR/man +# PREFIX_LOCALEDIR PREFIX_DATAROOTDIR/locale +# PREFIX_GIRDIR PREFIX_DATAROOTDIR/gir-1.0 + +# Copyright (C) 2013 Matthias Vogelgesang <matthias.vogelgesang@gmail.com> +# +# Redistribution and use, with or without modification, are permitted +# provided that the following conditions are met: +# +# 1. Redistributions must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# 2. The name of the author may not be used to endorse or promote +# products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +if(__configure_paths) + return() +endif() + +set(__configure_paths YES) + +macro(_set_var _prefix _var _user _override _description) + set(_name "${_prefix}_${_var}") + + set("${_name}" "${_user}") + + if("${_name}" STREQUAL "") + set("${_name}" "${_override}") + endif() + + set(${_name} "${${_name}}" CACHE PATH "${_description}") + mark_as_advanced(${_name}) +endmacro() + +function(configure_paths _prefix) + _set_var("${_prefix}" "PREFIX" "${PREFIX}" "${CMAKE_INSTALL_PREFIX}" "install architecture-independent files in PREFIX") + _set_var("${_prefix}" "EPREFIX" "${EXEC_PREFIX}" "${${_prefix}_PREFIX}" "install architecture-dependent files in EPREFIX") + + _set_var("${_prefix}" "SBINDIR" "${SBINDIR}" "${${_prefix}_EPREFIX}/sbin" "system admin executabls") + _set_var("${_prefix}" "SYSCONFDIR" "${SYSCONFDIR}" "${${_prefix}_PREFIX}/etc" "read-only single-machine data") + _set_var("${_prefix}" "LOCALSTATEDIR" "${LOCALSTATEDIR}" "${${_prefix}_PREFIX}/var" "modifiable single-machine data") + _set_var("${_prefix}" "BINDIR" "${BINDIR}" "${${_prefix}_EPREFIX}/bin" "user executables") + _set_var("${_prefix}" "LIBDIR" "${LIBDIR}" "${${_prefix}_EPREFIX}/lib" "object code libraries") + _set_var("${_prefix}" "INCLUDEDIR" "${INCLUDEDIR}" "${${_prefix}_PREFIX}/include" "C header files") + _set_var("${_prefix}" "PKGCONFIGDIR" "${PKGCONFIGDIR}" "${${_prefix}_LIBDIR}/pkgconfig" "pkg-config files") + _set_var("${_prefix}" "TYPELIBDIR" "${TYPELIBDIR}" "${${_prefix}_LIBDIR}/girepository-1.0" "GObject run-time introspection data") + _set_var("${_prefix}" "DATAROOTDIR" "${DATAROOTDIR}" "${${_prefix}_PREFIX}/share" "read-only arch.-independent data root") + _set_var("${_prefix}" "DATADIR" "${DATADIR}" "${${_prefix}_DATAROOTDIR}" "read-only architecture-independent data") + _set_var("${_prefix}" "INFODIR" "${INFODIR}" "${${_prefix}_DATAROOTDIR}/info" "info documentation") + _set_var("${_prefix}" "MANDIR" "${MANDIR}" "${${_prefix}_DATAROOTDIR}/man" "man documentation") + _set_var("${_prefix}" "LOCALEDIR" "${LOCALEDIR}" "${${_prefix}_DATAROOTDIR}/locale" "locale-dependent data") + _set_var("${_prefix}" "GIRDIR" "${GIRDIR}" "${${_prefix}_DATAROOTDIR}/gir-1.0" "GObject introspection data") +endfunction() + +# vim: tw=0: diff --git a/cmake/PkgConfigVars.cmake b/cmake/PkgConfigVars.cmake new file mode 100644 index 0000000..f295457 --- /dev/null +++ b/cmake/PkgConfigVars.cmake @@ -0,0 +1,30 @@ +# - determine variables defined in pkg-config files +# +# Usage: +# pkg_check_variable(<PKG_NAME> <VARIABLE_NAME>) +# +# Checks for a variable in the given package and translates to a call such as +# `pkg-config --variable=<VARIABLE_NAME> <PKG_NAME>`. The output is a cached +# variable named +# +# <PKG_NAME>_<VARIABLE_NAME> +# +# Note that both names are uppercased and any dashes replaced by underscores. +# + +find_package(PkgConfig REQUIRED) + +function(pkg_check_variable _pkg _name) + string(TOUPPER ${_pkg} _pkg_upper) + string(TOUPPER ${_name} _name_upper) + string(REPLACE "-" "_" _pkg_upper ${_pkg_upper}) + string(REPLACE "-" "_" _name_upper ${_name_upper}) + set(_output_name "${_pkg_upper}_${_name_upper}") + + execute_process(COMMAND ${PKG_CONFIG_EXECUTABLE} --variable=${_name} ${_pkg} + OUTPUT_VARIABLE _pkg_result + OUTPUT_STRIP_TRAILING_WHITESPACE) + + set("${_output_name}" "${_pkg_result}" CACHE STRING "pkg-config variable + ${_name} of ${_pkg}") +endfunction() diff --git a/uca-net-camera.c b/uca-net-camera.c new file mode 100644 index 0000000..6088ef3 --- /dev/null +++ b/uca-net-camera.c @@ -0,0 +1,296 @@ +/* Copyright (C) 2011-2013 Matthias Vogelgesang <matthias.vogelgesang@kit.edu> + (Karlsruhe Institute of Technology) + + 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 St, Fifth Floor, Boston, MA 02110, USA */ + +#include <stdlib.h> +#include <gio/gio.h> +#include <gmodule.h> +#include <string.h> + +#include <uca/uca-camera.h> +#include "uca-net-camera.h" +#include "uca-net-protocol.h" + + +#define UCA_NET_CAMERA_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), UCA_TYPE_NET_CAMERA, UcaNetCameraPrivate)) + +static void uca_net_camera_initable_iface_init (GInitableIface *iface); + +G_DEFINE_TYPE_WITH_CODE (UcaNetCamera, uca_net_camera, UCA_TYPE_CAMERA, + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, + uca_net_camera_initable_iface_init)) + +GQuark uca_net_camera_error_quark () +{ + return g_quark_from_static_string("uca-net-camera-error-quark"); +} + +enum { + PROP_HOST = N_BASE_PROPERTIES, + N_PROPERTIES +}; + +static GParamSpec *net_properties[N_PROPERTIES] = { NULL, }; + +struct _UcaNetCameraPrivate { + GError *construct_error; + gchar *host; + GSocketConnection *connection; + GSocketClient *client; + gsize size; +}; + +static void +uca_net_camera_start_recording (UcaCamera *camera, + GError **error) +{ + UcaNetCameraPrivate *priv; + guint width; + guint height; + guint bits; + + g_return_if_fail (UCA_IS_NET_CAMERA (camera)); + + g_object_get (G_OBJECT (camera), + "roi-width", &width, + "roi-height", &height, + "sensor-bitdepth", &bits, + NULL); + + priv = UCA_NET_CAMERA_GET_PRIVATE (camera); + priv->size = width * height * (bits > 8 ? 2 : 1); + + uca_net_client_start_recording (priv->connection, error); +} + +static void +uca_net_camera_stop_recording (UcaCamera *camera, + GError **error) +{ + g_return_if_fail (UCA_IS_NET_CAMERA (camera)); + uca_net_client_stop_recording (UCA_NET_CAMERA_GET_PRIVATE (camera)->connection, error); +} + +static void +uca_net_camera_start_readout (UcaCamera *camera, + GError **error) +{ + g_return_if_fail (UCA_IS_NET_CAMERA (camera)); +} + +static void +uca_net_camera_stop_readout (UcaCamera *camera, + GError **error) +{ + g_return_if_fail (UCA_IS_NET_CAMERA (camera)); +} + +static void +uca_net_camera_write (UcaCamera *camera, + const gchar *name, + gpointer data, + gsize size, + GError **error) +{ + g_return_if_fail (UCA_IS_NET_CAMERA (camera)); +} + +static gboolean +uca_net_camera_grab (UcaCamera *camera, + gpointer data, + GError **error) +{ + UcaNetCameraPrivate *priv; + + g_return_val_if_fail (UCA_IS_NET_CAMERA (camera), FALSE); + priv = UCA_NET_CAMERA_GET_PRIVATE (camera); + return uca_net_client_grab (priv->connection, data, priv->size, error); +} + +static void +uca_net_camera_trigger (UcaCamera *camera, + GError **error) +{ + g_return_if_fail (UCA_IS_NET_CAMERA (camera)); +} + +static void +uca_net_camera_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + UcaNetCameraPrivate *priv; + const gchar *name; + GError *error = NULL; + + priv = UCA_NET_CAMERA_GET_PRIVATE (object); + + if (property_id == PROP_HOST) { + g_free (priv->host); + priv->host = g_value_dup_string (value); + return; + } + + name = g_param_spec_get_name (pspec); + + if (!uca_net_client_set_property (priv->connection, name, value, &error)) + g_warning ("Could not set property: %s", error->message); +} + +static void +uca_net_camera_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + UcaNetCameraPrivate *priv; + const gchar *name; + GError *error = NULL; + + priv = UCA_NET_CAMERA_GET_PRIVATE (object); + + if (property_id == PROP_HOST) { + g_value_set_string (value, priv->host); + return; + } + + name = g_param_spec_get_name (pspec); + + if (!uca_net_client_get_property (priv->connection, name, value, &error)) + g_warning ("Could not get property: %s", error->message); +} + +static void +uca_net_camera_dispose (GObject *object) +{ + UcaNetCameraPrivate *priv; + + priv = UCA_NET_CAMERA_GET_PRIVATE (object); + + if (priv->connection != NULL) { + uca_net_client_close (priv->connection, NULL); + g_object_unref (priv->connection); + } + + g_object_unref (priv->client); + G_OBJECT_CLASS (uca_net_camera_parent_class)->dispose (object); +} + +static void +uca_net_camera_finalize (GObject *object) +{ + UcaNetCameraPrivate *priv; + + priv = UCA_NET_CAMERA_GET_PRIVATE (object); + g_clear_error (&priv->construct_error); + + g_free (priv->host); + + G_OBJECT_CLASS (uca_net_camera_parent_class)->finalize (object); +} + +static gboolean +ufo_net_camera_initable_init (GInitable *initable, + GCancellable *cancellable, + GError **error) +{ + UcaNetCamera *camera; + UcaNetCameraPrivate *priv; + + g_return_val_if_fail (UCA_IS_NET_CAMERA (initable), FALSE); + + if (cancellable != NULL) { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + "Cancellable initialization not supported"); + return FALSE; + } + + camera = UCA_NET_CAMERA (initable); + priv = camera->priv; + + if (priv->construct_error != NULL) { + if (error) + *error = g_error_copy (priv->construct_error); + + return FALSE; + } + + return TRUE; +} + +static void +uca_net_camera_initable_iface_init (GInitableIface *iface) +{ + iface->init = ufo_net_camera_initable_init; +} + +static void +uca_net_camera_class_init (UcaNetCameraClass *klass) +{ + GObjectClass *oclass = G_OBJECT_CLASS (klass); + UcaCameraClass *camera_class = UCA_CAMERA_CLASS (klass); + + oclass->set_property = uca_net_camera_set_property; + oclass->get_property = uca_net_camera_get_property; + oclass->dispose = uca_net_camera_dispose; + oclass->finalize = uca_net_camera_finalize; + + camera_class->start_recording = uca_net_camera_start_recording; + camera_class->stop_recording = uca_net_camera_stop_recording; + camera_class->start_readout = uca_net_camera_start_readout; + camera_class->stop_readout = uca_net_camera_stop_readout; + camera_class->write = uca_net_camera_write; + camera_class->grab = uca_net_camera_grab; + camera_class->trigger = uca_net_camera_trigger; + + net_properties[PROP_HOST] = + g_param_spec_string ("host", + "Host name and optional port", + "Host name and optional port", + "localhost", + G_PARAM_READWRITE | G_PARAM_CONSTRUCT); + + for (guint i = PROP_0 + 1; i < N_BASE_PROPERTIES; i++) + g_object_class_override_property (oclass, i, uca_camera_props[i]); + + for (guint id = N_BASE_PROPERTIES; id < N_PROPERTIES; id++) + g_object_class_install_property (oclass, id, net_properties[id]); + + g_type_class_add_private (klass, sizeof(UcaNetCameraPrivate)); +} + +static void +uca_net_camera_init (UcaNetCamera *self) +{ + UcaNetCameraPrivate *priv; + + self->priv = priv = UCA_NET_CAMERA_GET_PRIVATE (self); + + priv->construct_error = NULL; + priv->client = g_socket_client_new (); + + if (priv->host == NULL) + priv->host = g_strdup ("localhost"); + + priv->connection = g_socket_client_connect_to_host (priv->client, priv->host, 8989, NULL, &priv->construct_error); +} + +G_MODULE_EXPORT GType +uca_camera_get_type (void) +{ + return UCA_TYPE_NET_CAMERA; +} diff --git a/uca-net-camera.h b/uca-net-camera.h new file mode 100644 index 0000000..865fb4b --- /dev/null +++ b/uca-net-camera.h @@ -0,0 +1,76 @@ +/* Copyright (C) 2011, 2012 Matthias Vogelgesang <matthias.vogelgesang@kit.edu> + (Karlsruhe Institute of Technology) + + 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 St, Fifth Floor, Boston, MA 02110, USA */ + +#ifndef __UCA_NET_CAMERA_H +#define __UCA_NET_CAMERA_H + +#include <glib-object.h> +#include <uca/uca-camera.h> + +G_BEGIN_DECLS + +#define UCA_TYPE_NET_CAMERA (uca_net_camera_get_type()) +#define UCA_NET_CAMERA(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), UCA_TYPE_NET_CAMERA, UcaNetCamera)) +#define UCA_IS_NET_CAMERA(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), UCA_TYPE_NET_CAMERA)) +#define UCA_NET_CAMERA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), UCA_TYPE_NET_CAMERA, UcaNetCameraClass)) +#define UCA_IS_NET_CAMERA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), UCA_TYPE_NET_CAMERA)) +#define UCA_NET_CAMERA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), UCA_TYPE_NET_CAMERA, UcaNetCameraClass)) + +#define UCA_NET_CAMERA_ERROR uca_net_camera_error_quark() +typedef enum { + UCA_NET_CAMERA_ERROR_INIT, + UCA_NET_CAMERA_ERROR_START_RECORDING, + UCA_NET_CAMERA_ERROR_STOP_RECORDING, + UCA_NET_CAMERA_ERROR_TRIGGER, + UCA_NET_CAMERA_ERROR_NEXT_EVENT, + UCA_NET_CAMERA_ERROR_NO_DATA, + UCA_NET_CAMERA_ERROR_MAYBE_CORRUPTED +} UcaNetCameraError; + +typedef struct _UcaNetCamera UcaNetCamera; +typedef struct _UcaNetCameraClass UcaNetCameraClass; +typedef struct _UcaNetCameraPrivate UcaNetCameraPrivate; + +/** + * UcaNetCamera: + * + * Creates #UcaNetCamera instances by loading corresponding shared objects. The + * contents of the #UcaNetCamera structure are private and should only be + * accessed via the provided API. + */ +struct _UcaNetCamera { + /*< private >*/ + UcaCamera parent; + + UcaNetCameraPrivate *priv; +}; + +/** + * UcaNetCameraClass: + * + * #UcaNetCamera class + */ +struct _UcaNetCameraClass { + /*< private >*/ + UcaCameraClass parent; +}; + +GType uca_net_camera_get_type(void); + +G_END_DECLS + +#endif diff --git a/uca-net-client.c b/uca-net-client.c new file mode 100644 index 0000000..c92eedd --- /dev/null +++ b/uca-net-client.c @@ -0,0 +1,207 @@ +#include <string.h> +#include <stdlib.h> +#include "uca-net-protocol.h" + +static gboolean +send_default_message (GSocketConnection *connection, UcaNetMessageType type, GError **error) +{ + GOutputStream *output; + UcaNetMessageDefault request; + + output = g_io_stream_get_output_stream (G_IO_STREAM (connection)); + request.type = type; + + if (!g_output_stream_write_all (output, &request, sizeof (request), NULL, NULL, error)) + return FALSE; + + if (!g_output_stream_flush (output, NULL, error)) + return FALSE; + + return TRUE; +} + +static gboolean +handle_default_reply (GSocketConnection *connection, UcaNetMessageType type, GError **error) +{ + GInputStream *input; + UcaNetDefaultReply reply; + + input = g_io_stream_get_input_stream (G_IO_STREAM (connection)); + + if (g_input_stream_read_all (input, &reply, sizeof (reply), NULL, NULL, error)) { + g_assert (reply.type == type); + + if (reply.error.occurred) { + g_set_error_literal (error, g_quark_from_string (reply.error.domain), reply.error.code, reply.error.message); + return FALSE; + } + + return TRUE; + } + + return FALSE; +} + +gboolean +uca_net_client_get_property (GSocketConnection *connection, const gchar *name, GValue *value, GError **error) +{ + UcaNetMessageGetPropertyRequest request; + UcaNetMessageGetPropertyReply reply; + GInputStream *input; + GOutputStream *output; + + input = g_io_stream_get_input_stream (G_IO_STREAM (connection)); + output = g_io_stream_get_output_stream (G_IO_STREAM (connection)); + + if (g_input_stream_has_pending (input)) + g_input_stream_clear_pending (input); + + /* request */ + request.type = UCA_NET_MESSAGE_GET_PROPERTY; + strncpy (request.property_name, name, sizeof (request.property_name)); + + if (!g_output_stream_write_all (output, &request, sizeof (request), NULL, NULL, error)) + return FALSE; + + /* reply */ + if (g_input_stream_read (input, &reply, sizeof (reply), NULL, error) < 0) + return FALSE; + + if (reply.type != request.type) { + if (*error != NULL) + /* FIXME: replace with correct error codes */ + *error = g_error_new_literal (G_FILE_ERROR, G_FILE_ERROR_NOENT, "Reply does not match request"); + return FALSE; + } + + if (g_type_is_a (G_VALUE_TYPE (value), G_TYPE_ENUM)) { + g_value_set_enum (value, atoi (reply.property_value)); + } + else { + /* XXX: I'd like to avoid this and rather use g_value_transform(), however + * that call fails with Python and uca-camera-control but succeeds with + * uca-grab ... */ + switch (G_VALUE_TYPE (value)) { + case G_TYPE_UINT: + g_value_set_uint (value, atol (reply.property_value)); + break; + case G_TYPE_DOUBLE: + g_value_set_double (value, atof (reply.property_value)); + break; + case G_TYPE_BOOLEAN: + g_value_set_boolean (value, g_strcmp0 (reply.property_value, "TRUE")); + break; + case G_TYPE_STRING: + g_value_set_string (value, reply.property_value); + break; + default: + g_warning ("Unsupported property type %s", G_VALUE_TYPE_NAME (value)); + } + } + + return TRUE; +} + +gboolean +uca_net_client_set_property (GSocketConnection *connection, const gchar *name, const GValue *value, GError **error) +{ + GOutputStream *output; + const gchar *str; + GValue str_value = {0}; + UcaNetMessageSetPropertyRequest request = { .type = UCA_NET_MESSAGE_SET_PROPERTY }; + + output = g_io_stream_get_output_stream (G_IO_STREAM (connection)); + g_value_init (&str_value, G_TYPE_STRING); + + if (g_type_is_a (G_VALUE_TYPE (value), G_TYPE_ENUM)) { + GValue int_value = {0}; + + g_value_init (&int_value, G_TYPE_INT); + g_value_transform (value, &int_value); + g_value_transform (&int_value, &str_value); + } + else { + g_value_transform (value, &str_value); + } + + str = g_value_get_string (&str_value); + strncpy (request.property_name, name, sizeof (request.property_name)); + strncpy (request.property_value, str, sizeof (request.property_value)); + + if (!g_output_stream_write_all (output, &request, sizeof (request), NULL, NULL, error)) + return FALSE; + + return handle_default_reply (connection, UCA_NET_MESSAGE_SET_PROPERTY, error); +} + +void +uca_net_client_start_recording (GSocketConnection *connection, GError **error) +{ + if (!send_default_message (connection, UCA_NET_MESSAGE_START_RECORDING, error)) + return; + + handle_default_reply (connection, UCA_NET_MESSAGE_START_RECORDING, error); +} + +void +uca_net_client_stop_recording (GSocketConnection *connection, GError **error) +{ + if (!send_default_message (connection, UCA_NET_MESSAGE_STOP_RECORDING, error)) + return; + + handle_default_reply (connection, UCA_NET_MESSAGE_STOP_RECORDING, error); +} + +gboolean +uca_net_client_grab (GSocketConnection *connection, gpointer data, gsize size, GError **error) +{ + GInputStream *input; + GOutputStream *output; + gsize transmitted; + gsize bytes_left; + UcaNetMessageGrabRequest request = { .type = UCA_NET_MESSAGE_GRAB, .size = size }; + + input = g_io_stream_get_input_stream (G_IO_STREAM (connection)); + output = g_io_stream_get_output_stream (G_IO_STREAM (connection)); + + /* request */ + if (!g_output_stream_write_all (output, &request, sizeof (request), &transmitted, NULL, error)) { + return FALSE; + } + + /* error reply */ + if (handle_default_reply (connection, UCA_NET_MESSAGE_GRAB, error)) { + bytes_left = size; + + while (bytes_left > 0) { + gssize read; + gchar *buffer; + + buffer = (gchar *) data; + read = g_input_stream_read (input, &buffer[size - bytes_left], bytes_left, NULL, error); + + if (read < 0) + return FALSE; + + bytes_left -= read; + } + + return TRUE; + } + + return FALSE; +} + +gboolean +uca_net_client_close (GSocketConnection *connection, GError **error) +{ + GOutputStream *output; + UcaNetMessageDefault request = { .type = UCA_NET_MESSAGE_CLOSE_CONNECTION }; + + output = g_io_stream_get_output_stream (G_IO_STREAM (connection)); + + if (!g_output_stream_write_all (output, &request, sizeof (request), NULL, NULL, error)) + return FALSE; + + return TRUE; +} diff --git a/uca-net-protocol.h b/uca-net-protocol.h new file mode 100644 index 0000000..3df8c37 --- /dev/null +++ b/uca-net-protocol.h @@ -0,0 +1,85 @@ +#ifndef PROTOCOL_H +#define PROTOCOL_H + +#include <gio/gio.h> + +typedef enum { + UCA_NET_MESSAGE_GET_PROPERTY = 0, + UCA_NET_MESSAGE_SET_PROPERTY, + UCA_NET_MESSAGE_START_RECORDING, + UCA_NET_MESSAGE_STOP_RECORDING, + UCA_NET_MESSAGE_GRAB, + UCA_NET_MESSAGE_CLOSE_CONNECTION, +} UcaNetMessageType; + +typedef struct { + gboolean occurred; + gchar domain[64]; + gint code; + gchar message[512]; +} UcaNetErrorReply; + +typedef struct { + UcaNetMessageType type; + UcaNetErrorReply error; +} UcaNetDefaultReply; + +typedef struct { + UcaNetMessageType type; +} UcaNetMessageDefault; + +typedef struct { + UcaNetMessageType type; + gchar property_name[128]; +} UcaNetMessageGetPropertyRequest; + +typedef struct { + UcaNetMessageType type; + gchar property_value[128]; +} UcaNetMessageGetPropertyReply; + +typedef struct { + UcaNetMessageType type; + gchar property_name[128]; + gchar property_value[128]; +} UcaNetMessageSetPropertyRequest; + +typedef struct { + UcaNetMessageType type; + gsize size; +} UcaNetMessageGrabRequest; + + +typedef struct { + gpointer user_data; + + void (*get_property) (gpointer user_data, const gchar *name, gchar *value); + void (*set_property) (gpointer user_data, const gchar *name, const gchar *value, GError **error); + void (*start_recording) (gpointer user_data, GError **error); + void (*stop_recording) (gpointer user_data, GError **error); + gboolean (*grab) (gpointer data, gpointer user_data, GError **error); +} UcaNetHandlers; + +gboolean uca_net_client_get_property (GSocketConnection *connection, + const gchar *name, + GValue *value, + GError **error); +gboolean uca_net_client_set_property (GSocketConnection *connection, + const gchar *name, + const GValue *value, + GError **error); +void uca_net_client_start_recording (GSocketConnection *connection, + GError **error); +void uca_net_client_stop_recording (GSocketConnection *connection, + GError **error); +gboolean uca_net_client_grab (GSocketConnection *connection, + gpointer data, + gsize size, + GError **error); +gboolean uca_net_client_close (GSocketConnection *connection, + GError **error); + +void uca_net_server_register_handlers (UcaNetHandlers *handlers); +void uca_net_server_handle (GSocketConnection *connection); + +#endif diff --git a/uca-net-server.c b/uca-net-server.c new file mode 100644 index 0000000..f038cd6 --- /dev/null +++ b/uca-net-server.c @@ -0,0 +1,174 @@ +#include <string.h> +#include "uca-net-protocol.h" + +static UcaNetHandlers handlers; + +static void +send_reply (GOutputStream *output, gpointer data, gsize size, GError **error) +{ + gsize written; + + if (!g_output_stream_write_all (output, data, size, &written, NULL, error)) + return; + + if (!g_output_stream_flush (output, NULL, error)) + return; +} + +static void +prepare_error_reply (GError *error, UcaNetErrorReply *reply) +{ + if (error != NULL) { + reply->occurred = TRUE; + reply->code = error->code; + strncpy (reply->domain, g_quark_to_string (error->domain), sizeof (error->domain)); + strncpy (reply->message, error->message, sizeof (reply->message)); + g_error_free (error); + } + else { + reply->occurred = FALSE; + } +} + +static void +uca_net_server_handle_get_property (GOutputStream *output, UcaNetMessageGetPropertyRequest *request, GError **error) +{ + UcaNetMessageGetPropertyReply reply = { .type = UCA_NET_MESSAGE_GET_PROPERTY }; + + handlers.get_property (handlers.user_data, request->property_name, reply.property_value); + send_reply (output, &reply, sizeof (reply), error); +} + +static void +uca_net_server_handle_set_property (GOutputStream *output, UcaNetMessageSetPropertyRequest *request, GError **stream_error) +{ + UcaNetDefaultReply reply = { .type = UCA_NET_MESSAGE_SET_PROPERTY }; + GError *error = NULL; + + handlers.set_property (handlers.user_data, request->property_name, request->property_value, &error); + prepare_error_reply (error, &reply.error); + send_reply (output, &reply, sizeof (reply), stream_error); +} + +static void +uca_net_server_handle_start_recording (GOutputStream *output, GError **stream_error) +{ + UcaNetDefaultReply reply = { .type = UCA_NET_MESSAGE_START_RECORDING }; + GError *error = NULL; + + handlers.start_recording (handlers.user_data, &error); + prepare_error_reply (error, &reply.error); + send_reply (output, &reply, sizeof (reply), stream_error); +} + +static void +uca_net_server_handle_stop_recording (GOutputStream *output, GError **stream_error) +{ + UcaNetDefaultReply reply = { .type = UCA_NET_MESSAGE_STOP_RECORDING }; + GError *error = NULL; + + handlers.stop_recording (handlers.user_data, &error); + prepare_error_reply (error, &reply.error); + send_reply (output, &reply, sizeof (reply), stream_error); +} + +static void +uca_net_server_handle_grab (GOutputStream *output, UcaNetMessageGrabRequest *request, GError **stream_error) +{ + UcaNetDefaultReply reply = { .type = UCA_NET_MESSAGE_GRAB }; + gsize bytes_left; + GError *error = NULL; + static gsize size = 0; + static gchar *buffer = NULL; + + if (buffer == NULL || size != request->size) { + buffer = g_realloc (buffer, request->size); + size = request->size; + } + + handlers.grab (buffer, handlers.user_data, &error); + prepare_error_reply (error, &reply.error); + send_reply (output, &reply, sizeof (reply), stream_error); + + /* send data if no error occured during grab */ + if (!reply.error.occurred) { + bytes_left = size; + + while (bytes_left > 0) { + gssize written; + + written = g_output_stream_write (output, &buffer[size - bytes_left], bytes_left, NULL, stream_error); + + if (written < 0) + return; + + bytes_left -= written; + } + } +} + +void +uca_net_server_register_handlers (UcaNetHandlers *new_handlers) +{ + memcpy (&handlers, new_handlers, sizeof (UcaNetHandlers)); +} + +void +uca_net_server_handle (GSocketConnection *connection) +{ + GInputStream *input; + GOutputStream *output; + gchar *buffer; + gboolean active; + + buffer = g_malloc0 (4096); + input = g_io_stream_get_input_stream (G_IO_STREAM (connection)); + output = g_io_stream_get_output_stream (G_IO_STREAM (connection)); + active = TRUE; + + while (active) { + UcaNetMessageDefault *message; + GError *error = NULL; + + /* looks dangerous */ + g_input_stream_read (input, buffer, 4096, NULL, &error); + message = (UcaNetMessageDefault *) buffer; + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_BROKEN_PIPE)) { + g_error_free (error); + active = FALSE; + break; + } + + switch (message->type) { + case UCA_NET_MESSAGE_GET_PROPERTY: + uca_net_server_handle_get_property (output, (UcaNetMessageGetPropertyRequest *) buffer, &error); + break; + case UCA_NET_MESSAGE_SET_PROPERTY: + uca_net_server_handle_set_property (output, (UcaNetMessageSetPropertyRequest *) buffer, &error); + break; + case UCA_NET_MESSAGE_START_RECORDING: + uca_net_server_handle_start_recording (output, &error); + break; + case UCA_NET_MESSAGE_STOP_RECORDING: + uca_net_server_handle_stop_recording (output, &error); + break; + case UCA_NET_MESSAGE_GRAB: + uca_net_server_handle_grab (output, (UcaNetMessageGrabRequest *) buffer, &error); + break; + case UCA_NET_MESSAGE_CLOSE_CONNECTION: + active = FALSE; + break; + default: + g_warning ("Message type not known"); + } + + if (error != NULL) { + g_warning ("Error handling requests: %s", error->message); + g_error_free (error); + active = FALSE; + } + } + + g_free (buffer); +} @@ -0,0 +1,223 @@ +/* Copyright (C) 2011-2016 Matthias Vogelgesang <matthias.vogelgesang@kit.edu> + (Karlsruhe Institute of Technology) + + 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 St, Fifth Floor, Boston, MA 02110, USA */ + +#include <glib-object.h> +#include <gio/gio.h> +#include <string.h> +#include <uca/uca-camera.h> +#include <uca/uca-plugin-manager.h> +#include "uca-net-protocol.h" + +static gchar * +get_camera_list (UcaPluginManager *manager) +{ + GList *types; + GString *str; + + manager = uca_plugin_manager_new (); + types = uca_plugin_manager_get_available_cameras (manager); + str = g_string_new ("[ "); + + if (types != NULL) { + for (GList *it = g_list_first (types); it != NULL; it = g_list_next (it)) { + gchar *name = (gchar *) it->data; + + if (g_list_next (it) == NULL) + g_string_append_printf (str, "%s ]", name); + else + g_string_append_printf (str, "%s, ", name); + } + } + else { + g_string_append (str, "]"); + } + + g_list_free_full (types, g_free); + g_object_unref (manager); + return g_string_free (str, FALSE); +} + +static GOptionContext * +uca_option_context_new (UcaPluginManager *manager) +{ + GOptionContext *context; + gchar *camera_list; + + camera_list = get_camera_list (manager); + context = g_option_context_new (camera_list); + g_free (camera_list); + return context; +} + +static void +handle_get_property_request (gpointer user_data, const gchar *name, gchar *value) +{ + UcaCamera *camera; + GParamSpec *pspec; + GValue prop_value = {0}; + GValue str_value = {0}; + + camera = user_data; + pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (camera), name); + + g_value_init (&prop_value, g_type_is_a (pspec->value_type, G_TYPE_ENUM) ? G_TYPE_INT : pspec->value_type); + g_object_get_property (G_OBJECT (camera), name, &prop_value); + + g_value_init (&str_value, G_TYPE_STRING); + g_value_transform (&prop_value, &str_value); + + strncpy (value, g_value_get_string (&str_value), sizeof (g_value_get_string (&str_value))); +} + +static void +handle_set_property_request (gpointer user_data, const gchar *name, const gchar *value, GError **error) +{ + UcaCamera *camera; + GParamSpec *pspec; + GValue prop_value = {0}; + GValue str_value = {0}; + + camera = user_data; + pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (camera), name); + + g_value_init (&prop_value, g_type_is_a (pspec->value_type, G_TYPE_ENUM) ? G_TYPE_INT : pspec->value_type); + g_value_init (&str_value, G_TYPE_STRING); + g_value_set_string (&str_value, value); + g_value_transform (&str_value, &prop_value); + + g_object_set_property (G_OBJECT (camera), name, &prop_value); +} + +static void +handle_start_recording_request (gpointer user_data, GError **error) +{ + uca_camera_start_recording (UCA_CAMERA (user_data), error); +} + +static void +handle_stop_recording_request (gpointer user_data, GError **error) +{ + uca_camera_stop_recording (UCA_CAMERA (user_data), error); +} + +static gboolean +handle_grab_request (gpointer data, gpointer user_data, GError **error) +{ + return uca_camera_grab (UCA_CAMERA (user_data), data, error); +} + +static gboolean +run_callback (GSocketService *service, GSocketConnection *connection, GObject *source, gpointer user_data) +{ + GInetSocketAddress *sock_address; + GInetAddress *address; + gchar *address_string; + + UcaNetHandlers handlers = { + .user_data = user_data, + .get_property = handle_get_property_request, + .set_property = handle_set_property_request, + .start_recording = handle_start_recording_request, + .stop_recording = handle_stop_recording_request, + .grab = handle_grab_request, + }; + + sock_address = G_INET_SOCKET_ADDRESS (g_socket_connection_get_remote_address (connection, NULL)); + address = g_inet_socket_address_get_address (sock_address); + address_string = g_inet_address_to_string (address); + g_message ("Connection accepted from %s:%u", address_string, g_inet_socket_address_get_port (sock_address)); + + g_free (address_string); + g_object_unref (sock_address); + + uca_net_server_register_handlers (&handlers); + uca_net_server_handle (connection); + + return FALSE; +} + +static void +serve (UcaCamera *camera, GError **error) +{ + GMainLoop *loop; + GSocketService *service; + + service = g_threaded_socket_service_new (1); + + if (!g_socket_listener_add_inet_port (G_SOCKET_LISTENER (service), 8989, NULL, error)) + return; + + g_signal_connect (service, "run", G_CALLBACK (run_callback), camera); + + loop = g_main_loop_new (NULL, TRUE); + g_main_loop_run (loop); +} + +int +main (int argc, char **argv) +{ + GOptionContext *context; + UcaPluginManager *manager; + UcaCamera *camera; + GError *error = NULL; + + static GOptionEntry entries[] = { { NULL } }; + +#if !(GLIB_CHECK_VERSION (2, 36, 0)) + g_type_init(); +#endif + + manager = uca_plugin_manager_new (); + context = uca_option_context_new (manager); + g_option_context_add_main_entries (context, entries, NULL); + + if (!g_option_context_parse (context, &argc, &argv, &error)) { + g_print ("Failed parsing arguments: %s\n", error->message); + goto cleanup_manager; + } + + if (argc < 2) { + g_print ("%s\n", g_option_context_get_help (context, TRUE, NULL)); + goto cleanup_manager; + } + + camera = uca_plugin_manager_get_camera (manager, argv[argc - 1], &error, NULL); + + if (camera == NULL) { + g_print ("Error during initialization: %s\n", error->message); + goto cleanup_camera; + } + + if (!uca_camera_parse_arg_props (camera, argv, argc - 1, &error)) { + g_print ("Error setting properties: %s\n", error->message); + goto cleanup_manager; + } + if (error != NULL) + g_print ("Error: %s\n", error->message); + + g_option_context_free (context); + + serve (camera, &error); + +cleanup_camera: + g_object_unref (camera); + +cleanup_manager: + g_object_unref (manager); + + return error != NULL ? 1 : 0; +} |