From 1ff01145a59f8210e9ef1c44e791ba12d586fe79 Mon Sep 17 00:00:00 2001
From: Joseph Mirabel <jmirabel@laas.fr>
Date: Thu, 15 Feb 2018 13:34:53 +0100
Subject: [PATCH] Add SignalWrapper which allows to bind a Python function to a
 signal.

---
 src/CMakeLists.txt      |  1 +
 src/dynamic-graph-py.cc |  4 ++
 src/signal-base-py.cc   | 53 +++++++++++++++++++++++++
 src/signal-wrapper.cc   | 55 ++++++++++++++++++++++++++
 src/signal-wrapper.hh   | 85 +++++++++++++++++++++++++++++++++++++++++
 5 files changed, 198 insertions(+)
 create mode 100644 src/signal-wrapper.cc
 create mode 100644 src/signal-wrapper.hh

diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index fc0f459..65f1962 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -73,6 +73,7 @@ ADD_LIBRARY(${PYTHON_MODULE}
 	factory-py.cc
 	pool-py.cc
 	signal-caster-py.cc
+	signal-wrapper.cc
 )
 
 # Remove prefix lib
diff --git a/src/dynamic-graph-py.cc b/src/dynamic-graph-py.cc
index 99ad52c..ab7519b 100644
--- a/src/dynamic-graph-py.cc
+++ b/src/dynamic-graph-py.cc
@@ -23,6 +23,7 @@
 #include <dynamic-graph/signal-base.h>
 
 #include "exception.hh"
+#include "signal-wrapper.hh"
 
 namespace dynamicgraph {
   namespace python {
@@ -30,6 +31,7 @@ namespace dynamicgraph {
     // Declare functions defined in other source files
     namespace signalBase {
       extern PyObject* create(PyObject* self, PyObject* args);
+      extern PyObject* createSignalWrapper(PyObject* self, PyObject* args);
       extern PyObject* getTime(PyObject* self, PyObject* args);
       extern PyObject* setTime(PyObject* self, PyObject* args);
       extern PyObject* getName(PyObject* self, PyObject* args);
@@ -151,6 +153,8 @@ static PyMethodDef dynamicGraphMethods[] = {
   // Signals
   {"create_signal_base", dynamicgraph::python::signalBase::create, METH_VARARGS,
    "create a SignalBase C++ object"},
+  {"create_signal_wrapper", dynamicgraph::python::signalBase::createSignalWrapper, METH_VARARGS,
+   "create a SignalWrapper C++ object"},
   {"signal_base_get_time", dynamicgraph::python::signalBase::getTime,
    METH_VARARGS, "Get time of  a SignalBase"},
   {"signal_base_set_time", dynamicgraph::python::signalBase::setTime,
diff --git a/src/signal-base-py.cc b/src/signal-base-py.cc
index 2c2115c..6cf5fd7 100644
--- a/src/signal-base-py.cc
+++ b/src/signal-base-py.cc
@@ -25,6 +25,7 @@
 
 #include "convert-dg-to-py.hh"
 #include "exception.hh"
+#include "signal-wrapper.hh"
 
 using dynamicgraph::SignalBase;
 
@@ -56,6 +57,58 @@ namespace dynamicgraph {
 	return PyCObject_FromVoidPtr((void*)obj, destroy);
       }
 
+      template <class T> void* createSignalWrapperTpl (const char* name, PyObject* o, std::string& error)
+      {
+        typedef SignalWrapper<T, int> SignalWrapper_t;
+        if (!SignalWrapper_t::checkCallable (o, error)) {
+          return NULL;
+        }
+
+        SignalWrapper_t* obj = new SignalWrapper_t(name, o);
+        return (void*) obj;
+      }
+
+#define SIGNAL_WRAPPER_TYPE(IF, Enum, Type)                             \
+        IF (command::Value::typeName(command::Value::Enum)              \
+            .compare(type) == 0) {                                      \
+          obj = createSignalWrapperTpl<Type> (name, object, error);     \
+        }
+
+      /**
+	 \brief Create an instance of SignalWrapper
+      */
+      PyObject* createSignalWrapper(PyObject* /*self*/, PyObject* args)
+      {
+	char *name = NULL;
+	char *type = NULL;
+	PyObject* object = NULL;
+
+	if (!PyArg_ParseTuple(args, "ssO", &name, &type, &object))
+	  return NULL;
+
+        void* obj = NULL;
+        std::string error;
+        SIGNAL_WRAPPER_TYPE(     if, BOOL     ,bool)
+        // SIGNAL_WRAPPER_TYPE(else if, UNSIGNED ,bool)
+        SIGNAL_WRAPPER_TYPE(else if, INT      ,int   )
+        SIGNAL_WRAPPER_TYPE(else if, FLOAT    ,float )
+        SIGNAL_WRAPPER_TYPE(else if, DOUBLE   ,double)
+        // SIGNAL_WRAPPER_TYPE(else if, STRING   ,bool)
+        SIGNAL_WRAPPER_TYPE(else if, VECTOR   ,Vector)
+        // SIGNAL_WRAPPER_TYPE(else if, MATRIX   ,bool)
+        // SIGNAL_WRAPPER_TYPE(else if, MATRIX4D ,bool)
+        else {
+          error = "Type not understood";
+        }
+
+        if (obj == NULL) {
+          PyErr_SetString(dgpyError, error.c_str());
+          return NULL;
+        }
+	// Return the pointer
+	return PyCObject_FromVoidPtr(obj, destroy);
+      }
+
       /**
 	 \brief Destroy an instance of InvertedPendulum
       */
diff --git a/src/signal-wrapper.cc b/src/signal-wrapper.cc
new file mode 100644
index 0000000..2e80a9a
--- /dev/null
+++ b/src/signal-wrapper.cc
@@ -0,0 +1,55 @@
+// Copyright (c) 2018, Joseph Mirabel
+// Authors: Joseph Mirabel (joseph.mirabel@laas.fr)
+//
+// This file is part of dynamic-graph-python.
+// dynamic-graph-python 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
+// 3 of the License, or (at your option) any later version.
+//
+// dynamic-graph-python 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
+// General Lesser Public License for more details.  You should have
+// received a copy of the GNU Lesser General Public License along with
+// dynamic-graph-python. If not, see <http://www.gnu.org/licenses/>.
+
+#include <Python.h>
+
+#include <signal-wrapper.hh>
+
+namespace dynamicgraph {
+  namespace python {
+    namespace signalWrapper {
+      void convert (PyObject* o, bool  & v) { v = (o == Py_True); }
+      void convert (PyObject* o, int   & v) { v = (int)PyInt_AS_LONG (o); }
+      void convert (PyObject* o, float & v) { v = (float)PyFloat_AS_DOUBLE (o); }
+      void convert (PyObject* o, double& v) { v =        PyFloat_AS_DOUBLE (o); }
+      void convert (PyObject* o, Vector     & v)
+      {
+        v.resize(PyTuple_Size(o));
+        for (int i = 0; i < v.size(); ++i)
+          convert(PyTuple_GetItem(o,i), v[i]);
+      }
+    }
+
+    template <class T, class Time>
+    bool SignalWrapper<T,Time>::checkCallable (PyObject* c, std::string& error)
+    {
+      if (PyCallable_Check(c) == 0) {
+        PyObject* str = PyObject_Str(c);
+        error = PyString_AsString(str);
+        error += " is not callable";
+        Py_DECREF(str);
+        return false;
+      }
+      return true;
+    }
+
+    template class SignalWrapper<bool  , int>;
+    template class SignalWrapper<int   , int>;
+    template class SignalWrapper<float , int>;
+    template class SignalWrapper<double, int>;
+    template class SignalWrapper<Vector, int>;
+  } // namespace dynamicgraph
+} // namespace python
diff --git a/src/signal-wrapper.hh b/src/signal-wrapper.hh
new file mode 100644
index 0000000..d37d345
--- /dev/null
+++ b/src/signal-wrapper.hh
@@ -0,0 +1,85 @@
+// Copyright (c) 2018, Joseph Mirabel
+// Authors: Joseph Mirabel (joseph.mirabel@laas.fr)
+//
+// This file is part of dynamic-graph-python.
+// dynamic-graph-python 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
+// 3 of the License, or (at your option) any later version.
+//
+// dynamic-graph-python 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
+// General Lesser Public License for more details.  You should have
+// received a copy of the GNU Lesser General Public License along with
+// dynamic-graph-python. If not, see <http://www.gnu.org/licenses/>.
+
+#include <Python.h>
+
+#include <dynamic-graph/linear-algebra.h>
+#include <dynamic-graph/signal.h>
+
+namespace dynamicgraph {
+  namespace python {
+    namespace signalWrapper {
+      void convert (PyObject* o, int   & v);
+      void convert (PyObject* o, bool  & v);
+      void convert (PyObject* o, float & v);
+      void convert (PyObject* o, double& v);
+      // void convert (PyObject* o, std::string& v);
+      void convert (PyObject* o, Vector     & v);
+      // void convert (PyObject* o, Eigen::MatrixXd& v);
+      // void convert (PyObject* o, Eigen::Matrix4d& v);
+    }
+
+    template <class T, class Time>
+    class SignalWrapper : public Signal<T, Time>
+    {
+      public:
+        typedef Signal<T,Time> parent_t;
+
+        static bool checkCallable (PyObject* c, std::string& error);
+
+        SignalWrapper (std::string name, PyObject* _callable) : 
+          parent_t (name)
+          , callable (_callable)
+          // , argsTuple (NULL)
+          // , argTime (NULL)
+          // , argValue (NULL)
+        {
+          typedef boost::function2<T&,T&,Time> function_t;
+          Py_INCREF(callable);
+          function_t f = boost::bind (&SignalWrapper::call, this, _1, _2);
+          this->setFunction (f);
+
+          // argsTuple = PyTuple_New(1);
+          // argTime = Py
+        }
+
+        virtual ~SignalWrapper ()
+        {
+          Py_DECREF(callable);
+          // Py_DECREF(args);
+        };
+
+      private:
+        T& call (T& value, Time t)
+        {
+          char format[] = "i";
+          PyObject* obj = PyObject_CallFunction(callable, format, t);
+          if (obj == NULL)
+            std::cerr << "Could not call callable" << std::endl;
+          else {
+            signalWrapper::convert (obj, value);
+            Py_DECREF(obj);
+          }
+          return value;
+        }
+        PyObject* callable;
+        // PyObject* argsTuple;
+        // PyObject* argTime;
+        // PyObject* argValue;
+    };
+
+  } // namespace dynamicgraph
+} // namespace python
-- 
GitLab