diff --git a/pyopendds/DataReader.py b/pyopendds/DataReader.py index f411ce7..1259fb1 100644 --- a/pyopendds/DataReader.py +++ b/pyopendds/DataReader.py @@ -3,27 +3,48 @@ from .Topic import Topic from .constants import StatusKind from .util import TimeDurationType, normalize_time_duration +from .Qos import DataReaderQos + +from typing import TYPE_CHECKING, Callable, Optional, Any -from typing import TYPE_CHECKING if TYPE_CHECKING: from .Subscriber import Subscriber class DataReader: - def __init__(self, subscriber: Subscriber, topic: Topic, qos=None, listener=None): + def __init__(self, subscriber: Subscriber, topic: Topic, qos=DataReaderQos(), listener: Optional[Callable[..., None]] = None): self.topic = topic - self.qos = qos self.listener = listener self.subscriber = subscriber + self.qos = qos subscriber.readers.append(self) - from _pyopendds import create_datareader - create_datareader(self, subscriber, topic) - - def wait_for(self, status: StatusKind, timeout: TimeDurationType): - from _pyopendds import datareader_wait_for - return datareader_wait_for(self, status, *normalize_time_duration(timeout)) - - def take_next_sample(self): - return self.topic._ts_package.take_next_sample(self) + from _pyopendds import create_datareader # noqa + #verify if callback is None + if self.listener == None : + create_datareader(self, subscriber, topic, None, self.qos) + else : + create_datareader(self, subscriber, topic, self.on_data_available_callback, self.qos) + + + def wait_for(self, timeout: TimeDurationType, status: StatusKind = StatusKind.SUBSCRIPTION_MATCHED): + from _pyopendds import datareader_wait_for # noqa + datareader_wait_for(self, status, *normalize_time_duration(timeout)) + + def take_next_sample(self) -> Any: + return self.topic.ts_package.take_next_sample(self) + + def on_data_available_callback(self): + sample = self.take_next_sample() + topicname = self.topic.name + print("on data available callback") + + if sample is None: + # print("on_data_available_callback error: sample is None") + pass + elif self.listener is not None: + try: # if callback have 2 arguments + self.listener(sample,topicname) + except : # if callback have 1 arguments + self.listener(sample) diff --git a/pyopendds/DataWriter.py b/pyopendds/DataWriter.py index a6beee8..8c77d8e 100644 --- a/pyopendds/DataWriter.py +++ b/pyopendds/DataWriter.py @@ -1,2 +1,32 @@ +from __future__ import annotations + +from .Topic import Topic +from .constants import StatusKind +from .util import TimeDurationType, normalize_time_duration +from .Qos import DataWriterQos + +from typing import TYPE_CHECKING + + +if TYPE_CHECKING: + from .Publisher import Publisher + + class DataWriter: - pass + + def __init__(self, publisher: Publisher, topic: Topic, qos=DataWriterQos()): + self.topic = topic + self.publisher = publisher + self.qos = qos + publisher.writers.append(self) + + from _pyopendds import create_datawriter # noqa + create_datawriter(self, publisher, topic,self.qos) + + + def wait_for(self, timeout: TimeDurationType, status: StatusKind = StatusKind.PUBLICATION_MATCHED): + from _pyopendds import datawriter_wait_for # noqa + datawriter_wait_for(self, status, *normalize_time_duration(timeout)) + + def write(self, sample) -> int: + return self.topic.ts_package.write(self, sample) diff --git a/pyopendds/DomainParticipant.py b/pyopendds/DomainParticipant.py index 862b423..40b4df7 100644 --- a/pyopendds/DomainParticipant.py +++ b/pyopendds/DomainParticipant.py @@ -2,8 +2,15 @@ from .Subscriber import Subscriber from .Publisher import Publisher +try: + from _pyopendds import participant_cleanup # noqa +except ImportError as e: + def participant_cleanup(*args): + pass + pass -class DomainParticipant: + +class DomainParticipant(object): def __init__(self, domain: int, qos=None, listener=None): self.domain = int(domain) @@ -14,19 +21,17 @@ def __init__(self, domain: int, qos=None, listener=None): self.publishers = [] self._registered_typesupport = [] - from _pyopendds import create_participant + from _pyopendds import create_participant # noqa create_participant(self, domain) def __del__(self): - from _pyopendds import participant_cleanup participant_cleanup(self) - def create_topic(self, - name: str, topic_type: type, qos=None, listener=None) -> Topic: + def create_topic(self, name: str, topic_type: type, qos=None, listener=None) -> Topic: return Topic(self, name, topic_type, qos, listener) def create_subscriber(self, qos=None, listener=None) -> Subscriber: return Subscriber(self, qos, listener) - def create_publisher(self, qos=None, listener=None) -> Publisher: - return Publisher(self, qos, listener) + def create_publisher(self, qos=None) -> Publisher: + return Publisher(self, qos) diff --git a/pyopendds/Publisher.py b/pyopendds/Publisher.py index a292b85..e5ade46 100644 --- a/pyopendds/Publisher.py +++ b/pyopendds/Publisher.py @@ -1,5 +1,6 @@ from __future__ import annotations +from .DataWriter import DataWriter from .Topic import Topic from typing import TYPE_CHECKING @@ -7,16 +8,16 @@ from .DomainParticipant import DomainParticipant -class Publisher: +class Publisher(object): - def __init__(self, participant: DomainParticipant, qos=None, listener=None): + def __init__(self, participant: DomainParticipant, qos=None): participant.publishers.append(self) self.qos = qos - self.listener = listener self.writers = [] - - from _pyopendds import create_publisher + from _pyopendds import create_publisher # noqa create_publisher(self, participant) - def create_datawriter(self, topic: Topic, qos=None, listener=None): - pass + def create_datawriter(self, topic: Topic, qos=None) -> DataWriter: + writer = DataWriter(self, topic, qos) + self.writers.append(writer) + return writer diff --git a/pyopendds/Qos.py b/pyopendds/Qos.py new file mode 100644 index 0000000..f504a17 --- /dev/null +++ b/pyopendds/Qos.py @@ -0,0 +1,49 @@ +from enum import IntEnum + + +class DurabilityQosPolicyKind(IntEnum): + VOLATILE_DURABILITY_QOS = 0, + TRANSIENT_LOCAL_DURABILITY_QOS = 1, + TRANSIENT_DURABILITY_QOS = 2, + PERSISTENT_DURABILITY_QOS = 3 + + +class ReliabilityQosPolicyKind(IntEnum): + BEST_EFFORT_RELIABILITY_QOS = 0, + RELIABLE_RELIABILITY_QOS = 1 + + +class HistoryQosPolicyKind(IntEnum): + KEEP_LAST_HISTORY_QOS = 0, + KEEP_ALL_HISTORY_QOS = 1 + + +class DurabilityQosPolicy: + def __init__(self): + self.kind = DurabilityQosPolicyKind.VOLATILE_DURABILITY_QOS + + +class ReliabilityQosPolicy: + def __init__(self): + self.kind = ReliabilityQosPolicyKind.BEST_EFFORT_RELIABILITY_QOS + self.max_blocking_time = 0 + + +class HistoryQosPolicy: + def __init__(self): + self.kind = HistoryQosPolicyKind.KEEP_LAST_HISTORY_QOS + self.depth = 1 + + +class DataWriterQos: + def __init__(self): + self.durability = DurabilityQosPolicy() + self.reliability = ReliabilityQosPolicy() + self.history = HistoryQosPolicy() + + +class DataReaderQos: + def __init__(self): + self.durability = DurabilityQosPolicy() + self.reliability = ReliabilityQosPolicy() + self.history = HistoryQosPolicy() diff --git a/pyopendds/Subscriber.py b/pyopendds/Subscriber.py index 25ee2d3..9efc004 100644 --- a/pyopendds/Subscriber.py +++ b/pyopendds/Subscriber.py @@ -15,9 +15,10 @@ def __init__(self, participant: DomainParticipant, qos=None, listener=None): self.qos = qos self.listener = listener self.readers = [] - - from _pyopendds import create_subscriber + from _pyopendds import create_subscriber # noqa create_subscriber(self, participant) - def create_datareader(self, topic: Topic, qos=None, listener=None): - return DataReader(self, topic, qos, listener) + def create_datareader(self, topic: Topic, qos=None, listener=None) -> DataReader: + reader = DataReader(self, topic, qos, listener) + self.readers.append(reader) + return reader diff --git a/pyopendds/Topic.py b/pyopendds/Topic.py index 0290e3c..72899fd 100644 --- a/pyopendds/Topic.py +++ b/pyopendds/Topic.py @@ -7,9 +7,7 @@ class Topic: - def __init__(self, - participant: DomainParticipant, name: str, topic_type: type, - qos=None, listener=None): + def __init__(self, participant: DomainParticipant, name: str, topic_type: type, qos=None, listener=None): participant.topics[name] = self self.name = name self.type = topic_type @@ -18,12 +16,14 @@ def __init__(self, # Get OpenDDS Topic Type Name import importlib - self._ts_package = \ - importlib.import_module( - topic_type._pyopendds_typesupport_packge_name) - if topic_type not in participant._registered_typesupport: - self._ts_package.register_type(participant, topic_type) - self.type_name = self._ts_package.type_name(topic_type) + self._ts_package = importlib.import_module(topic_type._pyopendds_typesupport_packge_name) # noqa + if topic_type not in participant._registered_typesupport: # noqa + self._ts_package.register_type(participant, topic_type) # noqa - from _pyopendds import create_topic + self.type_name = self._ts_package.type_name(topic_type) # noqa + from _pyopendds import create_topic # noqa create_topic(self, participant, name, self.type_name) + + @property + def ts_package(self): + return self._ts_package diff --git a/pyopendds/dev/include/pyopendds/basictype.hpp b/pyopendds/dev/include/pyopendds/basictype.hpp new file mode 100644 index 0000000..5fd88e1 --- /dev/null +++ b/pyopendds/dev/include/pyopendds/basictype.hpp @@ -0,0 +1,309 @@ +#ifndef PYOPENDDS_BASICTYPE_HEADER +#define PYOPENDDS_BASICTYPE_HEADER + +#include + +#include + +#include +#include +#include + +namespace pyopendds { + +template +class Type; + +template +class BooleanType { +public: + static PyObject* get_python_class() + { + return Py_False; + } + + static void cpp_to_python(const T& cpp, PyObject*& py) + { + if ( !cpp ) { + py = Py_False; + } else { + py = Py_True; + } + } + + static void python_to_cpp(PyObject* py, T& cpp) + { + // PyBool_Check always return true + //if (PyBool_Check(py)) throw Exception("Not a boolean", PyExc_ValueError); + + if (py == Py_True) { + cpp = true; + } else { + cpp = false; + } + } +}; + + +PyObject* pyopendds_mod_str = NULL; +PyObject* pyopendds_mod = NULL; +PyObject* pyopendds_byte_func = NULL; +PyObject* pyopendds_ubyte_func = NULL; + +template +class IntegerType { +public: + typedef std::numeric_limits limits; +// typedef std::conditional LongType; + + static PyObject* get_python_class() + { + return PyLong_FromLong(0); + } + + static PyObject* PyUByte_FromUInt8(uint8_t value) + { + initPyopenddsModule(); + PyObject *args = PyTuple_Pack(1, PyLong_FromUnsignedLong(static_cast(value))); + PyObject *py = PyObject_CallObject(pyopendds_ubyte_func, args); + if (!py) throw Exception("Cannot create Byte object from value", PyExc_ValueError); + + return py; + } + + static PyObject* PyByte_FromInt8(int8_t value) + { + initPyopenddsModule(); + PyObject *args = PyTuple_Pack(1, PyLong_FromLong(static_cast(value))); + PyObject *py = PyObject_CallObject(pyopendds_byte_func, args); + if (!py) throw Exception("Cannot create Byte object from value", PyExc_ValueError); + + return py; + } + + static long PyUByte_AsUnsignedLong(PyObject* value) + { + PyObject *py_int = PyObject_GetAttrString(value, "value"); + if (!py_int) throw Exception("Error in getting obj.value", PyExc_ValueError); + + return PyLong_AsLong(py_int); + } + + static unsigned long PyByte_AsLong(PyObject* value) + { + PyObject *py_int = PyObject_GetAttrString(value, "value"); + if (!py_int) throw Exception("Error in getting obj.value", PyExc_ValueError); + + return PyLong_AsUnsignedLong(py_int); + } + + static void cpp_to_python(const T& cpp, PyObject*& py) + { + if (limits::is_signed) { + if (sizeof(cpp) > sizeof(long)) { + py = PyLong_FromLongLong(cpp); + } else { + if (sizeof(cpp) <= sizeof(int8_t)) { + py = PyByte_FromInt8(cpp); + } else { + py = PyLong_FromLong(cpp); + } + } + } else { + if (sizeof(cpp) > sizeof(long)) { + py = PyLong_FromUnsignedLongLong(cpp); + } else { + if (sizeof(cpp) <= sizeof(uint8_t)) { + py = PyUByte_FromUInt8(cpp); + } else { + py = PyLong_FromUnsignedLong(cpp); + } + } + } + } + + static void python_to_cpp(PyObject* py, T& cpp) + { + T value; + if (limits::is_signed) { + if (sizeof(T) == sizeof(long long)) { + value = PyLong_AsLongLong(py); + } else { + if (sizeof(T) <= sizeof(int8_t)) { + value = static_cast(PyByte_AsLong(py)); + } else { + value = PyLong_AsLong(py); + } + } + } else { + if (sizeof(T) == sizeof(long long)) { + value = PyLong_AsUnsignedLongLong(py); + } else { + if (sizeof(T) <= sizeof(uint8_t)) { + value = static_cast(PyUByte_AsUnsignedLong(py)); + } else { + value = PyLong_AsUnsignedLong(py); + } + } + } + if (value < limits::lowest() || value > limits::max()) { + throw Exception("Integer Value is Out of Range for IDL Type", PyExc_ValueError); + } + if (value == -1 && PyErr_Occurred()) + throw Exception(); + + cpp = T(value); + } + +private: + static bool initPyopenddsModule() + { + // Creating python string for module + if (!pyopendds_mod_str) { + pyopendds_mod_str = PyUnicode_FromString((char*) "pyopendds.util"); + if (!pyopendds_mod_str) + throw Exception("Cannot create Python string \"pyopendds.util\"", PyExc_NameError); + } + + // Importing pyopendds.util module + if (!pyopendds_mod) { + pyopendds_mod = PyImport_Import(pyopendds_mod_str); + if (!pyopendds_mod) + throw Exception("Cannot import \"pyopendds.util\"", PyExc_ImportError); + } + + // Getting a reference to the Byte object initializer + if (!pyopendds_byte_func) { + pyopendds_byte_func = PyObject_GetAttrString(pyopendds_mod, (char*)"Byte"); + if (!pyopendds_byte_func) + throw Exception("Cannot find \"Byte()\" in \"pyopendds.util\"", PyExc_NameError); + } + + // Getting a reference to the UByte object initializer + if (!pyopendds_ubyte_func) { + pyopendds_ubyte_func = PyObject_GetAttrString(pyopendds_mod, (char*)"UByte"); + if (!pyopendds_ubyte_func) + throw Exception("Cannot find \"UByte()\" in \"pyopendds.util\"", PyExc_NameError); + } + + return true; + } +}; + +template +class FloatingType { +public: + typedef std::numeric_limits limits; + + static PyObject* get_python_class() + { + return PyFloat_FromDouble(0.0); + } + + static void cpp_to_python(const T& cpp, PyObject*& py) + { + py = PyFloat_FromDouble((double)cpp); + } + + static void python_to_cpp(PyObject* py, T& cpp) + { + double value; + value = PyFloat_AsDouble(py); + if (value < limits::lowest() || value > limits::max()) { + throw Exception("Floating Value is Out of Range for IDL Type", PyExc_ValueError); + } + + if (value == -1 && PyErr_Occurred()) throw Exception(); + cpp = value; + } +}; + +const char* string_data(const std::string& cpp) { return cpp.data(); } + +const char* string_data(const char* cpp) { return cpp; } + +size_t string_length(const std::string& cpp) { return cpp.size(); } + +size_t string_length(const char* cpp) { return std::strlen(cpp); } + +template +class StringType { +public: + static PyObject* get_python_class() + { + // TODO: Add seperate RawStringType where get_python_class returns PyBytes_Type + return PyUnicode_FromString(""); + } + + static void cpp_to_python(const T& cpp, PyObject*& py, const char* encoding) + { + PyObject* o = PyUnicode_Decode(string_data(cpp), string_length(cpp), encoding, "strict"); + if (!o) throw Exception(); + py = o; + } + + static void python_to_cpp(PyObject* py, T& cpp, const char* encoding) + { + PyObject* repr = PyObject_Str(py); + if (!repr) throw Exception(); + + PyObject* str = PyUnicode_AsEncodedString(repr, encoding, NULL); + if (!str) throw Exception(); + + const char *bytes = PyBytes_AS_STRING(str); + cpp = T(bytes); + + Py_XDECREF(repr); + Py_XDECREF(str); + } +}; + +typedef ::CORBA::Boolean b; +template<> class Type: public BooleanType {}; + +typedef ::CORBA::LongLong i64; +template<> class Type: public IntegerType {}; + +typedef ::CORBA::ULongLong u64; +template<> class Type: public IntegerType {}; + +typedef ::CORBA::Long i32; +template<> class Type: public IntegerType {}; + +typedef ::CORBA::ULong u32; +template<> class Type: public IntegerType {}; + +typedef ::CORBA::Short i16; +template<> class Type: public IntegerType {}; + +typedef ::CORBA::UShort u16; +template<> class Type: public IntegerType {}; + +typedef ::CORBA::Char c8; +template<> class Type: public IntegerType {}; + +typedef ::CORBA::WChar c16; +template<> class Type: public IntegerType {}; + +typedef ::CORBA::Octet u8; +template<> class Type: public IntegerType {}; + +typedef ::CORBA::Float f32; +template<> class Type: public FloatingType {}; + +typedef ::CORBA::Double f64; +template<> class Type: public FloatingType {}; + +typedef +#ifdef CPP11_IDL +std::string +#else +::TAO::String_Manager +#endif +s8; +template<> class Type: public StringType {}; +// TODO: Put Other String/Char Types Here + +} // namesapce pyopendds + +#endif // PYOPENDDS_BASICTYPE_HEADER diff --git a/pyopendds/dev/include/pyopendds/common.hpp b/pyopendds/dev/include/pyopendds/common.hpp index 9998ebb..b4bacd9 100644 --- a/pyopendds/dev/include/pyopendds/common.hpp +++ b/pyopendds/dev/include/pyopendds/common.hpp @@ -5,143 +5,7 @@ #define PY_SSIZE_T_CLEAN #include -#include +#include "exception.hpp" +#include "utils.hpp" -namespace pyopendds { - -class Exception : public std::exception { -public: - Exception() - : message_(nullptr) - , pyexc_(nullptr) - { - assert(PyErr_Occurred()); - } - - Exception(const char* message, PyObject* pyexc) - : message_(message) - , pyexc_(pyexc) - { - } - - PyObject* set() const - { - if (pyexc_ && message_) { - PyErr_SetString(pyexc_, message_); - } - return nullptr; - } - - virtual const char* what() const noexcept - { - return message_ ? message_ : "Python Exception Occurred"; - } - -private: - const char* message_; - PyObject* pyexc_; -}; - -/** - * Simple Manager For PyObjects that are to be decremented when the instance is - * deleted. - */ -class Ref { -public: - Ref(PyObject* o = nullptr) : o_(o) {} - ~Ref() { Py_XDECREF(o_); } - PyObject*& operator*() { return o_; } - Ref& operator=(PyObject* o) - { - Py_XDECREF(o_); - o_ = o; - return *this; - } - operator bool() const { return o_; } - void operator++(int) { Py_INCREF(o_); } - -private: - PyObject* o_; -}; - -/// Name of PyCapule Attribute Holding the C++ Object -const char* capsule_name = "_cpp_object"; - -/** - * Get Contents of Capsule from a PyObject - */ -template -T* get_capsule(PyObject* obj) -{ - T* rv = nullptr; - PyObject* capsule = PyObject_GetAttrString(obj, capsule_name); // nr - if (capsule) { - if (PyCapsule_IsValid(capsule, nullptr)) { - rv = static_cast(PyCapsule_GetPointer(capsule, nullptr)); - } - Py_DECREF(capsule); - } else { - PyErr_Clear(); - } - if (!rv) { - PyErr_SetString(PyExc_TypeError, - "Python object does not have a valid capsule pointer"); - } - return rv; -} - -template -bool set_capsule(PyObject* py, T* cpp, PyCapsule_Destructor cb) -{ - PyObject* capsule = PyCapsule_New(cpp, nullptr, cb); - if (!capsule) return true; - const bool error = PyObject_SetAttrString(py, capsule_name, capsule); - Py_DECREF(capsule); - return error; -} - -class Errors { -public: - static PyObject* pyopendds() - { - return pyopendds_; - } - - static PyObject* PyOpenDDS_Error() - { - return PyOpenDDS_Error_; - } - - static PyObject* ReturnCodeError() - { - return ReturnCodeError_; - } - - static bool cache() - { - pyopendds_ = PyImport_ImportModule("pyopendds"); - if (!pyopendds_) return true; - - PyOpenDDS_Error_ = PyObject_GetAttrString(pyopendds_, "PyOpenDDS_Error"); - if (!PyOpenDDS_Error_) return true; - - ReturnCodeError_ = PyObject_GetAttrString(pyopendds_, "ReturnCodeError"); - if (!ReturnCodeError_) return true; - - return false; - } - - static bool check_rc(DDS::ReturnCode_t rc) - { - return !PyObject_CallMethod(ReturnCodeError_, "check", "k", rc); - } - -private: - static PyObject* pyopendds_; - static PyObject* PyOpenDDS_Error_; - static PyObject* ReturnCodeError_; -}; - -} // namesapce pyopendds - -#endif +#endif // PYOPENDDS_COMMON_HEADER diff --git a/pyopendds/dev/include/pyopendds/exception.hpp b/pyopendds/dev/include/pyopendds/exception.hpp new file mode 100644 index 0000000..de31010 --- /dev/null +++ b/pyopendds/dev/include/pyopendds/exception.hpp @@ -0,0 +1,88 @@ +#ifndef PYOPENDDS_EXCEPTION_HEADER +#define PYOPENDDS_EXCEPTION_HEADER + +#include + +#include +#include + +namespace pyopendds { + +class Exception: public std::exception { +public: + Exception() + : message_(nullptr) + , pyexc_(nullptr) + { + assert(PyErr_Occurred()); + } + + Exception(const char* message, PyObject* pyexc) + : message_(message) + , pyexc_(pyexc) + { } + + PyObject* set() const + { + if (pyexc_ && message_) { + PyErr_SetString(pyexc_, message_); + } + return nullptr; + } + + virtual const char* what() const noexcept + { + return message_ ? message_ : "Python Exception Occurred"; + } + +private: + const char* message_; + PyObject* pyexc_; +}; + + +class Errors { +public: + static PyObject* pyopendds() + { + return pyopendds_; + } + + static PyObject* PyOpenDDS_Error() + { + return PyOpenDDS_Error_; + } + + static PyObject* ReturnCodeError() + { + return ReturnCodeError_; + } + + static bool cache() + { + pyopendds_ = PyImport_ImportModule("pyopendds"); + if (!pyopendds_) return true; + + PyOpenDDS_Error_ = PyObject_GetAttrString(pyopendds_, "PyOpenDDS_Error"); + if (!PyOpenDDS_Error_) return true; + + ReturnCodeError_ = PyObject_GetAttrString(pyopendds_, "ReturnCodeError"); + if (!ReturnCodeError_) return true; + + return false; + } + + static bool check_rc(DDS::ReturnCode_t rc) + { + return !PyObject_CallMethod(ReturnCodeError_, "check", "k", rc); + } + +private: + static PyObject* pyopendds_; + static PyObject* PyOpenDDS_Error_; + static PyObject* ReturnCodeError_; +}; + +} // namesapce pyopendds + +#endif // PYOPENDDS_EXCEPTION_HEADER diff --git a/pyopendds/dev/include/pyopendds/topictype.hpp b/pyopendds/dev/include/pyopendds/topictype.hpp new file mode 100644 index 0000000..e1cfffe --- /dev/null +++ b/pyopendds/dev/include/pyopendds/topictype.hpp @@ -0,0 +1,206 @@ +#ifndef PYOPENDDS_TOPICTYPE_HEADER +#define PYOPENDDS_TOPICTYPE_HEADER + +#include + +#include "utils.hpp" + +#include +#include +#include + +#include +#include +#include + +namespace pyopendds { + +template +class Type; +class TopicTypeBase; + +class Global { +public: + typedef std::shared_ptr Ptr; + typedef std::map TopicTypes; + + TopicTypes& topic_types_() { return _d; } + +private: + TopicTypes _d; +}; + +class TopicTypeBase { +public: + typedef std::shared_ptr Ptr; + typedef std::map TopicTypes; + + virtual PyObject* get_python_class() = 0; + + virtual const char* type_name() = 0; + + virtual void register_type(PyObject* pyparticipant) = 0; + + virtual PyObject* take_next_sample(PyObject* pyreader) = 0; + + virtual PyObject* write(PyObject* pywriter, PyObject* pysample) = 0; + + static TopicTypeBase* find(PyObject* pytype) + { + Global* global = &Singleton::getInstance(); + + TopicTypes::iterator i = global->topic_types_().find(pytype); + if (i == global->topic_types_().end()) { + throw Exception("Not a Valid PyOpenDDS Type", PyExc_TypeError); + } + return i->second.get(); + } +}; + +template +class TopicType: public TopicTypeBase { +public: + typedef typename OpenDDS::DCPS::DDSTraits Traits; + + typedef T IdlType; + typedef typename Traits::MessageSequenceType IdlTypeSequence; + + typedef typename Traits::TypeSupportType TypeSupport; + typedef typename Traits::TypeSupportTypeImpl TypeSupportImpl; + typedef typename Traits::DataWriterType DataWriter; + typedef typename Traits::DataReaderType DataReader; + + static void init() + { + Global* global = &Singleton::getInstance(); + + Ptr type{ new TopicType }; + global->topic_types_().insert(TopicTypes::value_type(Type::get_python_class(), type)); + } + + PyObject* get_python_class() + { + return Type::get_python_class(); + } + + const char* type_name() + { + return Traits::type_name(); + } + + /** + * Callback for Python to call when the TypeSupport capsule is deleted + */ + static void delete_typesupport(PyObject* capsule) + { + if (PyCapsule_CheckExact(capsule)) { + DDS::TypeSupport_var ts = static_cast(PyCapsule_GetPointer(capsule, nullptr)); + if (ts) ts = nullptr; + } + } + + void register_type(PyObject* pyparticipant) + { + // Get DomainParticipant_var + DDS::DomainParticipant* participant = + get_capsule(pyparticipant); + if (!participant) { + throw Exception("Could not get native participant", PyExc_TypeError); + } + + // Register with OpenDDS + TypeSupportImpl* type_support = new TypeSupportImpl; + if (type_support->register_type(participant, "") != DDS::RETCODE_OK) { + delete type_support; + type_support = nullptr; + throw Exception("Could not create register type", Errors::PyOpenDDS_Error()); + } + + // Store TypeSupport in Python Participant + Ref capsule = PyCapsule_New(type_support, nullptr, delete_typesupport); + if (!capsule) throw Exception(); + + Ref list { PyObject_GetAttrString(pyparticipant, "_registered_typesupport") }; + if (!list || PyList_Append(*list, *capsule)) + throw Exception(); + } + + PyObject* take_next_sample(PyObject* pyreader) + { + DDS::DataReader* reader = get_capsule(pyreader); + if (!reader) throw Exception(); + + DataReader* reader_impl = DataReader::_narrow(reader); + if (!reader_impl) { + throw Exception("Could not narrow reader implementation", Errors::PyOpenDDS_Error()); + } + +// #ifndef __APPLE__ +// // TODO: wait causes segmentation fault +// DDS::ReadCondition_var read_condition = reader_impl->create_readcondition( +// DDS::ANY_SAMPLE_STATE, DDS::ANY_VIEW_STATE, DDS::ANY_SAMPLE_STATE); +// DDS::WaitSet_var ws = new DDS::WaitSet; +// ws->attach_condition(read_condition); + +// DDS::ConditionSeq active; +// const DDS::Duration_t max_wait_time = {60, 0}; + +// if (Errors::check_rc(ws->wait(active, max_wait_time))) { +// throw Exception(); +// } +// ws->detach_condition(read_condition); +// reader_impl->delete_readcondition(read_condition); + +// IdlType sample; +// DDS::SampleInfo info; +// if (Errors::check_rc(reader_impl->take_next_sample(sample, info))) { +// throw Exception(); +// } +// #else + // TODO: fallback to naive implementation + + IdlType sample; + DDS::SampleInfo info; + DDS::ReturnCode_t rc = reader_impl->take_next_sample(sample, info); + if (rc != DDS::RETCODE_OK) { + // TODO: Temporarily inhibit this error and let the user check for its return code + // PyErr_SetString(Errors::PyOpenDDS_Error(), "reader_impl->take_next_sample() failed"); + return Py_None; + } +// #endif + PyObject* rv = nullptr; + if (info.valid_data) { + Type::cpp_to_python(sample, rv); + } else { + rv = Py_None; + } + return rv; + } + + PyObject* write(PyObject* pywriter, PyObject* pysample) + { + DDS::DataWriter* writer = get_capsule(pywriter); + if (!writer) throw Exception(); + + DataWriter* writer_impl = DataWriter::_narrow(writer); + if (!writer_impl) { + throw Exception("Could not narrow writer implementation", Errors::PyOpenDDS_Error()); + } + + IdlType rv; + Type::python_to_cpp(pysample, rv); + + DDS::ReturnCode_t rc = writer_impl->write(rv, DDS::HANDLE_NIL); + if (rc != DDS::RETCODE_OK) { + // TODO: Temporarily inhibit this exception and let the user check for its return code + // throw Exception("Writer could not write sample", Errors::PyOpenDDS_Error()); + } + // if (Errors::check_rc(rc)) return nullptr; + + return PyLong_FromLong(rc); + } +}; + +} // namesapce pyopendds + +#endif // PYOPENDDS_TOPICTYPE_HEADER diff --git a/pyopendds/dev/include/pyopendds/user.hpp b/pyopendds/dev/include/pyopendds/user.hpp index aa7c7fd..93a9bc8 100644 --- a/pyopendds/dev/include/pyopendds/user.hpp +++ b/pyopendds/dev/include/pyopendds/user.hpp @@ -2,250 +2,7 @@ #define PYOPENDDS_USER_HEADER #include "common.hpp" - -#include -#include -#include - -#include -#include -#include -#include - -namespace pyopendds { - -template -class Type /*{ -public: - static PyObject* get_python_class(); - static void cpp_to_python(const T& cpp, PyObject*& py); - static void python_to_cpp(PyObject* py, T& cpp); -}*/; - -template -class IntegerType { -public: - typedef std::numeric_limits limits; - typedef std::conditional LongType; - - static PyObject* get_python_class() - { - return PyLong_Type; - } - - static void cpp_to_python(const T& cpp, PyObject*& py) - { - if (limits::is_signed) { - py = PyLong_FromLong(cpp); - } else { - py = PyLong_FromUnsignedLong(cpp); - } - if (!py) throw Exception(); - } - - static void python_to_cpp(PyObject* py, T& cpp) - { - LongType value; - if (limits::is_signed) { - value = PyLong_AsLong(py); - } else { - value = PyLong_AsUnsignedLong(py); - } - if (value < limits::min() || value > limits::max()) { - throw Exception( - "Integer Value is Out of Range for IDL Type", PyExc_ValueError); - } - if (value == -1 && PyErr_Occurred()) throw Exception(); - cpp = value; - } -}; - -typedef ::CORBA::Long i32; -template<> class Type: public IntegerType {}; - -// TODO: Put Other Integer Types Here - -const char* string_data(const std::string& cpp) -{ - return cpp.data(); -} - -const char* string_data(const char* cpp) -{ - return cpp; -} - -size_t string_length(const std::string& cpp) -{ - return cpp.size(); -} - -size_t string_length(const char* cpp) -{ - return std::strlen(cpp); -} - -template -class StringType { -public: - static PyObject* get_python_class() - { - return PyUnicode_Type; - } - - static void cpp_to_python(const T& cpp, PyObject*& py, const char* encoding) - { - PyObject* o = PyUnicode_Decode( - string_data(cpp), string_length(cpp), encoding, "strict"); - if (!o) throw Exception(); - py = o; - } - - static void python_to_cpp(PyObject* py, T& cpp) - { - // TODO: Encode or Throw Unicode Error - } -}; - -// TODO: Add seperate RawStringType where get_python_class returns PyBytes_Type - -typedef -#ifdef CPP11_IDL - std::string -#else - ::TAO::String_Manager -#endif - s8; -template<> class Type: public StringType {}; -// TODO: Put Other String/Char Types Here - -// TODO: FloatingType for floating point type - -class TopicTypeBase { -public: - virtual PyObject* get_python_class() = 0; - virtual const char* type_name() = 0; - virtual void register_type(PyObject* pyparticipant) = 0; - virtual PyObject* take_next_sample(PyObject* pyreader) = 0; - - typedef std::shared_ptr Ptr; - typedef std::map TopicTypes; - - static TopicTypeBase* find(PyObject* pytype) - { - TopicTypes::iterator i = topic_types_.find(pytype); - if (i == topic_types_.end()) { - throw Exception("Not a Valid PyOpenDDS Type", PyExc_TypeError); - } - return i->second.get(); - } - -private: - static TopicTypes topic_types_; -}; - -template -class TopicType : public TopicTypeBase { -public: - typedef typename OpenDDS::DCPS::DDSTraits Traits; - - typedef T IdlType; - typedef typename Traits::MessageSequenceType IdlTypeSequence; - - typedef typename Traits::TypeSupportType TypeSupport; - typedef typename Traits::TypeSupportTypeImpl TypeSupportImpl; - typedef typename Traits::DataWriterType DataWriter; - typedef typename Traits::DataReaderType DataReader; - - const char* type_name() - { - return Traits::type_name(); - } - - /** - * Callback for Python to call when the TypeSupport capsule is deleted - */ - static void delete_typesupport(PyObject* capsule) - { - if (PyCapsule_CheckExact(capsule)) { - delete static_cast( - PyCapsule_GetPointer(capsule, nullptr)); - } - } - - void register_type(PyObject* pyparticipant) - { - // Get DomainParticipant_var - DDS::DomainParticipant* participant = - get_capsule(pyparticipant); - if (!participant) { - throw Exception("Could not get native participant", PyExc_TypeError); - } - - // Register with OpenDDS - TypeSupportImpl* type_support = new TypeSupportImpl; - if (type_support->register_type(participant, "") != DDS::RETCODE_OK) { - delete type_support; - type_support = 0; - throw Exception( - "Could not create register type", Errors::PyOpenDDS_Error()); - } - - // Store TypeSupport in Python Participant - Ref capsule = PyCapsule_New( - type_support, nullptr, delete_typesupport); - if (!capsule) throw Exception(); - Ref list{PyObject_GetAttrString(pyparticipant, "_registered_typesupport")}; - if (!list || PyList_Append(*list, *capsule)) throw Exception(); - } - - PyObject* take_next_sample(PyObject* pyreader) - { - DDS::DataReader* reader = get_capsule(pyreader); - if (!reader) throw Exception(); - - DataReader* reader_impl = DataReader::_narrow(reader); - if (!reader_impl) { - throw Exception( - "Could not narrow reader implementation", Errors::PyOpenDDS_Error()); - } - - DDS::ReadCondition_var read_condition = reader_impl->create_readcondition( - DDS::ANY_SAMPLE_STATE, DDS::ANY_VIEW_STATE, DDS::ANY_SAMPLE_STATE); - DDS::WaitSet_var ws = new DDS::WaitSet; - ws->attach_condition(read_condition); - DDS::ConditionSeq active; - const DDS::Duration_t max_wait_time = {10, 0}; - if (Errors::check_rc(ws->wait(active, max_wait_time))) { - throw Exception(); - } - ws->detach_condition(read_condition); - reader_impl->delete_readcondition(read_condition); - - IdlType sample; - DDS::SampleInfo info; - if (Errors::check_rc(reader_impl->take_next_sample(sample, info))) { - throw Exception(); - } - - PyObject* rv = nullptr; - Type::cpp_to_python(sample, rv); - - return rv; - } - - PyObject* get_python_class() - { - return Type::get_python_class(); - } - - static void init() - { - Ptr type{new TopicType}; - topic_types_.insert(TopicTypes::value_type(Type::get_python_class(), type)); - } -}; - -} // namespace pyopendds +#include "basictype.hpp" +#include "topictype.hpp" #endif diff --git a/pyopendds/dev/include/pyopendds/utils.hpp b/pyopendds/dev/include/pyopendds/utils.hpp new file mode 100644 index 0000000..ba61c6a --- /dev/null +++ b/pyopendds/dev/include/pyopendds/utils.hpp @@ -0,0 +1,123 @@ +#ifndef PYOPENDDS_UTILS_HEADER +#define PYOPENDDS_UTILS_HEADER + +#include + +namespace pyopendds { + + /// Name of PyCapule Attribute Holding the C++ Object + const char* capsule_name = "_cpp_object"; + + /** + * Simple Manager For PyObjects that are to be decremented when the instance is + * deleted. + */ + class Ref { + public: + + Ref(PyObject* o = nullptr) + : object_(o) + { } + + ~Ref() + { + Py_XDECREF(object_); + } + + PyObject*& operator*() + { + return object_; + } + + Ref& operator=(PyObject* o) + { + Py_XDECREF(object_); + object_ = o; + return *this; + } + + void operator++(int /*unused*/) + { + Py_INCREF(object_); + } + + operator bool() const + { + return object_; + } + + + private: + PyObject* object_; + + }; + + /** + * Gets contents of Capsule from a PyObject + */ + template + T* get_capsule(PyObject* obj) + { + T* return_value = nullptr; + PyObject* capsule = PyObject_GetAttrString(obj, capsule_name); // nr + + if (capsule) { + if (PyCapsule_IsValid(capsule, nullptr)) { + return_value = static_cast(PyCapsule_GetPointer(capsule, nullptr)); + } + Py_DECREF(capsule); + } else { + PyErr_Clear(); + } + if (!return_value) + PyErr_SetString(PyExc_TypeError, "Python object does not have a valid capsule pointer"); + + return return_value; + } + + /** + * Sets contents of Capsule + */ + template + bool set_capsule(PyObject* py, T* cpp, PyCapsule_Destructor dtor) + { + PyObject* capsule = PyCapsule_New(cpp, nullptr, dtor); + if (!capsule) + return true; + + const bool error = PyObject_SetAttrString(py, capsule_name, capsule); + // if (error==false){ printf("not good");} + Py_DECREF(capsule); + + return error; + } + + template + class Singleton + { + public: + static T& getInstance(); + + // so we cannot accidentally delete it via pointers + Singleton(){}; + + // no copies + Singleton(const Singleton&) = delete; + + // no self-assignments + Singleton& operator=(const Singleton&) = delete; + }; + + template + T& Singleton::getInstance() { + + // Guaranteed to be destroyed. Instantiated on first use. Thread safe in C++11 + static T instance; + return instance; + } + + + +} // namesapce pyopendds + +#endif // PYOPENDDS_UTILS_HEADER diff --git a/pyopendds/dev/itl2py/CppOutput.py b/pyopendds/dev/itl2py/CppOutput.py index b922eb0..869d7e4 100644 --- a/pyopendds/dev/itl2py/CppOutput.py +++ b/pyopendds/dev/itl2py/CppOutput.py @@ -1,6 +1,6 @@ from jinja2 import Environment -from .ast import PrimitiveType, StructType, EnumType +from .ast import PrimitiveType, StructType, EnumType, SequenceType, ArrayType from .Output import Output @@ -13,6 +13,10 @@ def cpp_type_name(type_node): return type_node.kind.name elif isinstance(type_node, (StructType, EnumType)): return cpp_name(type_node.name.parts) + elif isinstance(type_node, (SequenceType)): + return cpp_name(type_node.name.parts); + elif isinstance(type_node, (ArrayType)): + return cpp_name(type_node.name.parts); else: raise NotImplementedError @@ -44,13 +48,16 @@ def visit_struct(self, struct_type): struct_to_lines = [ 'Ref field_value;', ] - struct_from_lines = [] + struct_from_lines = [ + 'Ref field_value;', + ] for field_name, field_node in struct_type.fields.items(): to_lines = [] from_lines = [] pyopendds_type = '' is_string = isinstance(field_node.type_node, PrimitiveType) and \ field_node.type_node.is_string() + is_sequence = isinstance(field_node.type_node, SequenceType) to_lines = [ 'Type<{pyopendds_type}>::cpp_to_python(cpp.{field_name}', @@ -61,16 +68,35 @@ def visit_struct(self, struct_type): + (', "{default_encoding}"' if is_string else '') + ');', ] + from_lines = [ + 'if (PyObject_HasAttrString(py, "{field_name}")) {{', + ' *field_value = PyObject_GetAttrString(py, "{field_name}");', + '}}', + 'if (!field_value) {{', + ' throw Exception();', + '}}' + ] + pyopendds_type = cpp_type_name(field_node.type_node) if to_lines: to_lines.extend([ - 'if (!field_value || PyObject_SetAttrString(' + 'if (!field_value || PyObject_SetAttrString(', 'py, "{field_name}", *field_value)) {{', ' throw Exception();', '}}' ]) + if from_lines: + from_lines.extend([ + 'Type<{pyopendds_type}>::python_to_cpp(*field_value, cpp.{field_name}', + '#ifdef CPP11_IDL', + ' ()', + '#endif', + ' ' + + (', "{default_encoding}"' if is_string else '') + ');' + ]) + def line_process(lines): return [''] + [ s.format( @@ -107,9 +133,65 @@ def visit_enum(self, enum_type): 'args = PyTuple_Pack(1, PyLong_FromLong(static_cast(cpp)));', ]), 'to_lines': '', - 'from_lines': '\n'.join([ - '', - '// left unimplemented' - ]), + 'from_lines': '', 'is_topic_type': False, }) + + def visit_sequence(self, sequence_type): + sequence_to_lines = [ + 'Ref field_value;', + ] + sequence_from_lines = [] + to_lines = [ + 'Ref field_elem;', + 'field_value = PyList_New(0);', + 'for (int i = 0; i < cpp.length(); i++) {{', + ' {pyopendds_type} elem = cpp[i];', + ' field_elem = nullptr;', + ' Type<{pyopendds_type}>::cpp_to_python(elem', + ' #ifdef CPP11_IDL', + ' ()', + ' #endif', + ' , *field_elem);', + ' PyList_Append(py, *field_elem);', + '}}' + ] + + pyopendds_type = cpp_type_name(sequence_type.base_type) + from_lines = [ + 'cpp.length(PyList_Size(py));', + 'for (int i = 0; i < PyList_Size(py); i++) {{', + ' {pyopendds_type} elem = cpp[i];', + ' Type<{pyopendds_type}>::python_to_cpp(PyList_GetItem(py, i), elem', + '#ifdef CPP11_IDL', + ' ()', + '#endif', + ' );', + ' cpp[i] = elem;', + '}}' + ] + + def line_process(lines): + return [''] + [ + s.format( + default_encoding=self.context['default_encoding'], + pyopendds_type=pyopendds_type, + ) for s in lines + ] + + sequence_to_lines.extend(line_process(to_lines)) + sequence_from_lines.extend(line_process(from_lines)) + + self.context['types'].append({ + 'cpp_name': cpp_name(sequence_type.name.parts), + 'name_parts': sequence_type.parent_name().parts, + 'local_name': sequence_type.local_name(), + 'to_lines': '\n'.join(sequence_to_lines), + 'from_lines': '\n'.join(sequence_from_lines), + 'new_lines': '\n'.join([ + 'args = nullptr;' + ]), + 'is_topic_type': sequence_type.is_topic_type, + 'sequence': True, + 'to_replace': False, + }) diff --git a/pyopendds/dev/itl2py/PythonOutput.py b/pyopendds/dev/itl2py/PythonOutput.py index 42920d3..c6fe202 100644 --- a/pyopendds/dev/itl2py/PythonOutput.py +++ b/pyopendds/dev/itl2py/PythonOutput.py @@ -1,4 +1,4 @@ -from .ast import PrimitiveType, StructType, EnumType +from .ast import PrimitiveType, StructType, EnumType, SequenceType, ArrayType from .Output import Output @@ -11,8 +11,9 @@ class PythonOutput(Output): primitive_types = { # (Python Type, Default Default Value) PrimitiveType.Kind.bool: ('bool', 'False'), - PrimitiveType.Kind.u8: ('int', '0'), - PrimitiveType.Kind.i8: ('int', '0'), + PrimitiveType.Kind.byte: ('UByte', 'UByte(0x00)'), + PrimitiveType.Kind.u8: ('UByte', 'UByte(0x00)'), + PrimitiveType.Kind.i8: ('Byte', 'Byte(0x00)'), PrimitiveType.Kind.u16: ('int', '0'), PrimitiveType.Kind.i16: ('int', '0'), PrimitiveType.Kind.u32: ('int', '0'), @@ -61,7 +62,8 @@ def get_python_type_string(self, field_type): elif self.is_local_type(field_type): return field_type.local_name() else: - return field_type.name.join() + return self.context['package_name'] + '.' + field_type.name.join() + # return field_type.name.join() def get_python_default_value_string(self, field_type): if isinstance(field_type, PrimitiveType): @@ -72,6 +74,10 @@ def get_python_default_value_string(self, field_type): return type_name + '()' elif isinstance(field_type, EnumType): return type_name + '.' + field_type.default_member + elif isinstance(field_type, SequenceType): + return 'field(default_factory=list)' + elif isinstance(field_type, ArrayType): + return 'field(default_factory=list)' else: raise NotImplementedError(repr(field_type) + " is not supported") @@ -99,3 +105,14 @@ def visit_enum(self, enum_type): ], ), )) + + def visit_sequence(self, sequence_type): + self.context['has_sequence'] = True + self.context['types'].append(dict( + local_name=sequence_type.local_name(), + type_support=self.context['native_package_name'] if sequence_type.is_topic_type else None, + sequence=dict( + type=sequence_type.base_type, + len=sequence_type.max_count, + ), + )) diff --git a/pyopendds/dev/itl2py/ast.py b/pyopendds/dev/itl2py/ast.py index 28a59a4..3ea9435 100644 --- a/pyopendds/dev/itl2py/ast.py +++ b/pyopendds/dev/itl2py/ast.py @@ -195,6 +195,7 @@ def __init__(self, base_type, dimensions): def accept(self, visitor): visitor.visit_array(self) + pass def __repr__(self): return self.repr_template( @@ -210,11 +211,15 @@ def __init__(self, base_type, max_count): def accept(self, visitor): visitor.visit_sequence(self) + pass def __repr__(self): return self.repr_template(repr(self.base_type) + ("max " + str(self.max_count) if self.max_count else "no max")) + def repr_name(self): + if self.name: + return '::' + self.name.join('::') + '::_tao_seq_' + repr(self.base_type) + '_' class NodeVisitor: @@ -231,10 +236,12 @@ def visit_enum(self, enum_type): raise NotImplementedError def visit_array(self, array_type): - raise NotImplementedError + pass + #array_type.accept(self) def visit_sequence(self, sequence_type): raise NotImplementedError + #sequence_type.accept(self) def get_ast(types: dict) -> Module: diff --git a/pyopendds/dev/itl2py/itl.py b/pyopendds/dev/itl2py/itl.py index d447ee3..efb9ca0 100644 --- a/pyopendds/dev/itl2py/itl.py +++ b/pyopendds/dev/itl2py/itl.py @@ -86,9 +86,16 @@ def parse_sequence(types, details): def parse_record(types, details): struct_type = StructType() for field_dict in details['fields']: - struct_type.add_field( - field_dict['name'], parse_type(types, field_dict['type']), - field_dict.get('optional', False)) + if 'sequence' in field_dict['type']: + sequence = parse_sequence(types, {'type': field_dict['type'], 'capacity': 1, 'size': None}) + sequence.set_name(itl_name=sequence.base_type.name.itl_name) + struct_type.add_field( + field_dict['name'], sequence, + field_dict.get('optional', False)) + else: + struct_type.add_field( + field_dict['name'], parse_type(types, field_dict['type']), + field_dict.get('optional', False)) return struct_type diff --git a/pyopendds/dev/itl2py/templates/user.cpp b/pyopendds/dev/itl2py/templates/user.cpp index e57c9ca..2e3bb4a 100644 --- a/pyopendds/dev/itl2py/templates/user.cpp +++ b/pyopendds/dev/itl2py/templates/user.cpp @@ -2,6 +2,8 @@ /*{% for name in idl_names %}*/ #include /*{%- endfor %}*/ +#include +#include namespace pyopendds { @@ -9,7 +11,7 @@ PyObject* Errors::pyopendds_ = nullptr; PyObject* Errors::PyOpenDDS_Error_ = nullptr; PyObject* Errors::ReturnCodeError_ = nullptr; -TopicTypeBase::TopicTypes TopicTypeBase::topic_types_; +//TopicTypeBase::TopicTypes TopicTypeBase::topic_types_; /*{%- for type in types %}*/ @@ -20,16 +22,28 @@ class Type { { PyObject* python_class = nullptr; if (!python_class) { - Ref module = PyImport_ImportModule("/*{{ package_name }}*/"); - if (!module) throw Exception(); - + std::stringstream mod_ss; + mod_ss << "/*{{ package_name }}*/"; /*{% for name in type.name_parts -%}*/ - module = PyObject_GetAttrString(*module, "/*{{ name }}*/"); - if (!module) throw Exception(); - /*{%- endfor %}*/ + mod_ss << "./*{{name}}*/"; + /*{% endfor -%}*/ + Ref module = PyImport_ImportModule(mod_ss.str().c_str()); + + if (!module) { + std::stringstream msg; + msg << "Could not import module "; + msg << mod_ss.str(); + throw Exception(msg.str().c_str(), PyExc_ImportError); + } python_class = PyObject_GetAttrString(*module, "/*{{ type.local_name }}*/"); - if (!python_class) throw Exception(); + if (!python_class) { + std::stringstream msg; + msg << "/*{{ type.local_name }}*/ "; + msg << "does not exist in "; + msg << mod_ss.str(); + throw Exception(msg.str().c_str(), PyExc_ImportError); + } } return python_class; } @@ -37,30 +51,47 @@ class Type { static void cpp_to_python(const /*{{ type.cpp_name }}*/& cpp, PyObject*& py) { PyObject* cls = get_python_class(); - /*{% if type.to_replace %}*/ + /*{%- if type.to_replace %}*/ if (py) Py_DECREF(py); PyObject* args; /*{{ type.new_lines | indent(4) }}*/ py = PyObject_CallObject(cls, args); /*{% else %}*/ + /*{% if type.sequence %}*/ + if (py) Py_DECREF(py); + py = nullptr; + /*{% endif %}*/ + PyObject* args; + /*{{ type.new_lines | indent(6) }}*/ + py = PyObject_CallObject(cls, args); + /*{% if type.to_lines %}*//*{{ type.to_lines | indent(4) }}*//*{% endif %}*/ + /*{%- endif %}*/ + } + + static void python_to_cpp(PyObject* py, /*{{ type.cpp_name }}*/& cpp) + { + PyObject* cls = get_python_class(); + /*{% if type.to_replace %}*/ + cpp = static_cast(PyLong_AsLong(py)); + /*{% else %}*/ if (py) { - if (PyObject_IsInstance(cls, py) != 1) { - throw Exception("Not a {{ type.py_name }}", PyExc_TypeError); + if (PyObject_IsInstance(py, cls) != 1 && PyObject_IsSubclass(cls, PyObject_Type(py)) != 1) { + const char * actual_type = PyUnicode_AsUTF8(PyObject_GetAttrString(PyObject_Type(py),"__name__")); + std::stringstream msg; + msg << "python_to_cpp: PyObject("; + msg << actual_type; + msg << ") is not of type"; + msg << "/*{{ type.local_name }}*/ nor is not parent class."; + throw Exception(msg.str().c_str(), PyExc_TypeError); } } else { PyObject* args; /*{{ type.new_lines | indent(6) }}*/ py = PyObject_CallObject(cls, args); } - /*{% if type.to_lines %}*//*{{ type.to_lines | indent(4) }}*//*{% endif %}*/ + /*{% if type.from_lines %}*//*{{ type.from_lines | indent(4) }}*//*{% endif %}*/ /*{% endif %}*/ } - - static void python_to_cpp(PyObject* py, /*{{ type.cpp_name }}*/& cpp) - { - PyObject* cls = get_python_class(); - /*{{ type.from_lines | indent(4) }}*/ - } }; /*{%- endfor %}*/ @@ -119,9 +150,31 @@ PyObject* pytake_next_sample(PyObject* self, PyObject* args) } } +PyObject* pywrite(PyObject* self, PyObject* args) +{ + Ref pywriter; + Ref pysample; + if (!PyArg_ParseTuple(args, "OO", &*pywriter, &*pysample)) return nullptr; + pywriter++; + pysample++; + + // Try to Get Reading Type and Do write + Ref pytopic = PyObject_GetAttrString(*pywriter, "topic"); + if (!pytopic) return nullptr; + Ref pytype = PyObject_GetAttrString(*pytopic, "type"); + if (!pytype) return nullptr; + + try { + return TopicTypeBase::find(*pytype)->write(*pywriter, *pysample); + } catch (const Exception& e) { + return e.set(); + } +} + PyMethodDef /*{{ native_package_name }}*/_Methods[] = { {"register_type", pyregister_type, METH_VARARGS, ""}, {"type_name", pytype_name, METH_VARARGS, ""}, + {"write", pywrite, METH_VARARGS, ""}, {"take_next_sample", pytake_next_sample, METH_VARARGS, ""}, {nullptr, nullptr, 0, nullptr} }; diff --git a/pyopendds/dev/itl2py/templates/user.py b/pyopendds/dev/itl2py/templates/user.py index 63aaf37..f662d0e 100644 --- a/pyopendds/dev/itl2py/templates/user.py +++ b/pyopendds/dev/itl2py/templates/user.py @@ -1,10 +1,16 @@ {% if has_struct -%} from dataclasses import dataclass as _pyopendds_struct +from dataclasses import field +from pyopendds.util import Byte, UByte +import {{ package_name }} {%- endif %} {% if has_enum -%} from enum import IntFlag as _pyopendds_enum {%- endif %} +{% if has_sequence -%} +{%- endif %} {% for type in types -%} + {%- if type.struct %} @_pyopendds_struct @@ -20,6 +26,10 @@ class {{ type.local_name }}(_pyopendds_enum): {%- for member in type.enum.members %} {{ member.name }} = {{ member.value }} {%- endfor %} +{%- elif type.sequence %} + +class {{ type.local_name }}(list): + pass {%- else %} # {{ type.local_name }} was left unimplmented {% endif -%} diff --git a/pyopendds/dev/pyidl/__init__.py b/pyopendds/dev/pyidl/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pyopendds/dev/pyidl/__main__.py b/pyopendds/dev/pyidl/__main__.py new file mode 100644 index 0000000..397e1ad --- /dev/null +++ b/pyopendds/dev/pyidl/__main__.py @@ -0,0 +1,199 @@ +import argparse +import glob +import os +import os.path +import subprocess +import sys + +from zipfile import ZipFile +from .gencmakefile import gen_cmakelist +from .gensetupfile import gen_setup + + +def prompt(question): + yes = {'yes', 'y'} + no = {'no', 'n', ''} + + choice = input(question).lower() + if choice in yes: + return True + elif choice in no: + return False + else: + sys.stdout.write("Please respond with 'yes' or 'no'") + + +def get_base_prefix_compat(): + return getattr(sys, "base_prefix", None) or getattr(sys, "real_prefix", None) or sys.prefix + + +def in_virtualenv(): + return get_base_prefix_compat() != sys.prefix + + +def resolve_wildcard(expr, dir_name) -> list: + files = glob.glob(f'{dir_name}/{expr}') + rem_part = (len(dir_name)+1) + return list(map(lambda s: s[rem_part:], files)) + + +def extract_include_path_from_egg(output_dir: str): + # potentially is into a egg archive + script_path = os.path.dirname(os.path.realpath(__file__)) + root_path = os.path.realpath(f'{script_path}/../../..') + include_dir = f'{output_dir}/include' + sub_path = 'pyopendds/dev/include' + + if os.path.isfile(root_path) and root_path.lower().endswith('.egg'): + with ZipFile(root_path, 'r') as zipObj: + source_dir_path = f'{output_dir}/pyopendds/dev/include' + for fileName in zipObj.namelist(): + if fileName.startswith(sub_path): + zipObj.extract(fileName, output_dir) + subprocess.run(['mv', source_dir_path, include_dir]) + subprocess.run(['rm', '-r', f'{output_dir}/pyopendds']) + return include_dir + else: + return f'{root_path}/{sub_path}' + + +def add_include_path(args: argparse.Namespace, filepath: str): + dirname = os.path.dirname(os.path.realpath(filepath)) + if dirname not in args.include_paths: + args.include_paths.append(dirname) + + +def mk_tmp_package_proj(args: argparse.Namespace): + # Create CMakeLists.txt + mk_tmp_file(f"{args.output_dir}/CMakeLists.txt", + gen_cmakelist(target_name=args.package_name, + pyopendds_ldir=args.pyopendds_ld, + idl_files=args.input_files, + include_dirs=args.include_paths)) + + # Create setup.py + mk_tmp_file(f"{args.output_dir}/setup.py", + gen_setup(target_name=args.package_name)) + + # Create a the empty __init__.py to indicate the project is a package + subprocess.run(['mkdir', args.package_name], + cwd=args.output_dir) + mk_tmp_file(f"{args.output_dir}/{args.package_name}/__init__.py", "") + + # Install a dummy python package [package_name] + print(f"Init dummy '{args.package_name}' Python package...") + subprocess.run(['python3', 'setup.py', 'install'], + cwd=args.output_dir) + + # Run cmake to prepare the python to cpp bindings + subprocess.run(['cmake', '..'], cwd=f"{args.output_dir}/build") + subprocess.run(['make'], cwd=f"{args.output_dir}/build") + + # Build the python IDL package + itl_files = resolve_wildcard('*.itl', f'{args.output_dir}/build') + subprocess.run(['itl2py', '-o', f"{args.package_name}_ouput", + f"{args.package_name}_idl", *itl_files, + '--package-name', f'py{args.package_name}'], + cwd=f"{args.output_dir}/build") + + # Install the python package py[package_name] + tmp_env = os.environ.copy() + tmp_env[f"{args.package_name}_idl_DIR"] = f"{os.path.abspath(args.output_dir)}/build" + subprocess.run(['python3', 'setup.py', 'install'], + cwd=f"{args.output_dir}/build/{args.package_name}_ouput", + env=tmp_env) + + # Cleanup temporary folder + if not args.user_defined_output: + subprocess.run(['rm', '-r', args.output_dir]) + + +def mk_tmp_file(pathname: str, content: str): + file = open(pathname, 'w') + file.write(content) + file.close() + + +def run(): + parser = argparse.ArgumentParser(description='Generate and install IDL Python class(es) from IDL file(s).' + ' If no input file is given, all the idl files present in the current' + ' directory will be embed into the output package.') + parser.add_argument('input_files', nargs='*', + help='the .idl source files') + parser.add_argument('-p', '--package-name', metavar='', + help='the python generated package name ' + '(default: the basename of the first input file)') + parser.add_argument('-d', '--pyopendds-ld', metavar='', + help='the path to pyopendds project. You can also define PYOPENDDS_LD as environment variable') + parser.add_argument('-o', '--output-dir', metavar='', + help='create a directory for the generated sources.') + parser.add_argument('-i', '--include-paths', nargs='*', metavar='', + help='the include paths needed by the IDL files, if any') + + args = parser.parse_args() + current_dir = os.getcwd() + + # Check if an environment is sourced + if not in_virtualenv(): + prompt('No virtual environment seems to be sourced. Would you like to continue ?') + + # Initialize include paths or convert directories names into absolute paths + if not args.include_paths: + args.__setattr__('include_paths', []) + for idx, include_path in enumerate(args.include_paths): + args.include_paths[idx] = os.path.realpath(include_path) + + # Convert file names into absolute filepath + for idx, input_file in enumerate(args.input_files): + abs_filepath = os.path.realpath(input_file) + args.input_files[idx] = abs_filepath + add_include_path(args, abs_filepath) + + # Discover all .idl files in the current dir if no input given + if not args.input_files: + for filename in os.listdir(current_dir): + f = os.path.join(current_dir, filename) + if os.path.isfile(f) and filename.lower().endswith('.idl'): + args.input_files.append(f) + add_include_path(args, f) + if len(args.input_files) == 0: + print("Error: no IDL file to compile in the current directory.") + sys.exit(1) + + # Check the ouput_dir (default will be $CWD/temp) + args.__setattr__('user_defined_output', True) + if not args.output_dir: + default_output_dir = 'temp' + args.__setattr__('output_dir', f'{os.getcwd()}/{default_output_dir}') + args.__setattr__('user_defined_output', False) + else: + output_dir_abspath = os.path.realpath(args.output_dir) + if os.path.isdir(output_dir_abspath) and len(os.listdir(output_dir_abspath)) != 0: + print(f"Error: {args.output_dir} is not empty.") + sys.exit(1) + args.__setattr__('output_dir', os.path.realpath(args.output_dir)) + + # Check pyopendds include path (which is required in further CMake process) + # Order of discovery is: + # 1- Folder name or path given as input + # 2- Environment variable named PYOPENDDS_LD + # 1- Direct reference to include directory installed in pyopendds .egg archive (always successes) + if not args.pyopendds_ld: + env_pyopendds_ld = os.getenv('PYOPENDDS_LD') + if not env_pyopendds_ld: + args.__setattr__('pyopendds_ld', extract_include_path_from_egg(args.output_dir)) + else: + args.__setattr__('pyopendds_ld', env_pyopendds_ld) + args.__setattr__('pyopendds_ld', os.path.realpath(args.pyopendds_ld)) + + # Parse package name. If no name is given, the basename of the first .idl file will be taken + if not args.package_name: + args.__setattr__('package_name', os.path.splitext(os.path.basename(args.input_files[0]))[0]) + + # Process generation and packaging + mk_tmp_package_proj(args=args) + sys.exit(0) + + +if __name__ == "__main__": + run() diff --git a/pyopendds/dev/pyidl/gencmakefile.py b/pyopendds/dev/pyidl/gencmakefile.py new file mode 100644 index 0000000..3d6050a --- /dev/null +++ b/pyopendds/dev/pyidl/gencmakefile.py @@ -0,0 +1,50 @@ + +def gen_cmakelist(target_name: str, + pyopendds_ldir: str, + idl_files: list, + include_dirs: list): + statement_0 = "" + for idx, include_dir in enumerate(include_dirs): + statement_0 += f'set(inc_dir_{idx} "{include_dir}")\n' + + statement_1 = "" + for idx, _ in enumerate(include_dirs): + statement_1 += f'target_include_directories({target_name}_idl PUBLIC "${{inc_dir_{idx}}}")\n' + + statement_3 = "" + for idl_file in idl_files: + statement_3 += f' {idl_file}' + statement_3 = f'PUBLIC{statement_3}' + + statement_4 = "" + for idx, _ in enumerate(include_dirs): + statement_4 += f' -I${{inc_dir_{idx}}}' + + return f""" +cmake_minimum_required(VERSION 3.10) + +project({target_name}) + +list(APPEND CMAKE_MODULE_PATH "$ENV{{DDS_ROOT}}/cmake") +find_package(OpenDDS REQUIRED) +{statement_0} +add_library({target_name}_idl SHARED) +target_include_directories({target_name}_idl PUBLIC "${{CMAKE_CURRENT_SOURCE_DIR}}/build") +{statement_1} +if(${{CPP11_IDL}}) + set(opendds_idl_mapping_option "-Lc++11") +endif() + +set(OPENDDS_FILENAME_ONLY_INCLUDES ON) +OPENDDS_TARGET_SOURCES({target_name}_idl {statement_3} + OPENDDS_IDL_OPTIONS "-Gitl" "${{opendds_idl_mapping_option}}"{statement_4} +) + +target_link_libraries({target_name}_idl PUBLIC OpenDDS::Dcps) +export( + TARGETS {target_name}_idl + FILE "${{CMAKE_CURRENT_BINARY_DIR}}/{target_name}_idlConfig.cmake" +) + +target_include_directories({target_name}_idl PUBLIC "{pyopendds_ldir}") +""" diff --git a/pyopendds/dev/pyidl/gensetupfile.py b/pyopendds/dev/pyidl/gensetupfile.py new file mode 100644 index 0000000..0ae653c --- /dev/null +++ b/pyopendds/dev/pyidl/gensetupfile.py @@ -0,0 +1,16 @@ + +def gen_setup(target_name: str): + return f""" +from setuptools import setup + +setup( + name='{target_name}', + version='0.1', + packages=['{target_name}'], + url='', + license='', + author='Andrea Ruffino', + author_email='andrea.ruffino@skyconseil.fr', + description='Python IDL generated with GenPyIDL.' +) +""" diff --git a/pyopendds/exceptions.py b/pyopendds/exceptions.py index 1ac9a8f..341788f 100644 --- a/pyopendds/exceptions.py +++ b/pyopendds/exceptions.py @@ -2,17 +2,17 @@ class PyOpenDDS_Error(Exception): - '''Base for all errors in PyOpenDDS - ''' + """ Base for all errors in PyOpenDDS + """ class ReturnCodeError(PyOpenDDS_Error): - '''Raised when a ReturnCode_t other than RETURNCODE_OK was returned from a + """ Raised when a ReturnCode_t other than RETURNCODE_OK was returned from a OpenDDS function that returns ReturnCode_t. There are subclasses for each ReturnCode, for example ImmutablePolicyReturnCodeError for ReturnCode.IMMUTABLE_POLICY. - ''' + """ return_code = None dds_name = None @@ -44,7 +44,7 @@ def __str__(self): if self.return_code: return 'OpenDDS has returned ' + self.dds_name return 'OpenDDS has returned an ReturnCode_t unkown to PyOpenDDS: ' + \ - self.unknown_code + repr(self.unknown_code) ReturnCodeError.generate_subclasses() diff --git a/pyopendds/ext/_pyopendds.cpp b/pyopendds/ext/_pyopendds.cpp index 0f16aaa..5529260 100644 --- a/pyopendds/ext/_pyopendds.cpp +++ b/pyopendds/ext/_pyopendds.cpp @@ -10,6 +10,9 @@ #include #include +#include +#include + using namespace pyopendds; PyObject* Errors::pyopendds_ = nullptr; @@ -18,420 +21,816 @@ PyObject* Errors::ReturnCodeError_ = nullptr; namespace { +class DataReaderListenerImpl : public virtual OpenDDS::DCPS::LocalObject { + +public: + DataReaderListenerImpl(PyObject * self, PyObject *callback); + + + + virtual void on_requested_deadline_missed( + DDS::DataReader_ptr reader, + const DDS::RequestedDeadlineMissedStatus& status) { } + + virtual void on_requested_incompatible_qos( + DDS::DataReader_ptr reader, + const DDS::RequestedIncompatibleQosStatus& status) { } + + virtual void on_sample_rejected( + DDS::DataReader_ptr reader, + const DDS::SampleRejectedStatus& status) { } + + virtual void on_liveliness_changed( + DDS::DataReader_ptr reader, + const DDS::LivelinessChangedStatus& status) { } + + virtual void on_data_available( + DDS::DataReader_ptr reader); + + virtual void on_subscription_matched( + DDS::DataReader_ptr reader, + const DDS::SubscriptionMatchedStatus& status) { } + + virtual void on_sample_lost( + DDS::DataReader_ptr reader, + const DDS::SampleLostStatus& status) { } + +private: + PyObject *_callback; + PyObject * _self; +}; + +DataReaderListenerImpl::DataReaderListenerImpl(PyObject* self, PyObject* callback) : + OpenDDS::DCPS::LocalObject() +{ + _self = self; + Py_XINCREF(_self); + _callback = callback; + Py_XINCREF(_callback); +} + +void DataReaderListenerImpl::on_data_available(DDS::DataReader_ptr reader) +{ + PyObject *callable = _callback; + PyObject *result = NULL; + + PyGILState_STATE gstate; + gstate = PyGILState_Ensure(); + try { + if (PyCallable_Check(callable)) { + result = PyObject_CallFunctionObjArgs(callable, nullptr); + if (result == NULL) { + PyErr_Print(); + } + Py_XDECREF(result); + } else { + throw Exception("Pyobject is not a callable", PyExc_TypeError); + } + } catch (Exception& e) { + // Py_XDECREF(callable); + PyGILState_Release(gstate); + throw e; + } + //Py_XDECREF(callable); + PyGILState_Release(gstate); +} + /// Global Participant Factory DDS::DomainParticipantFactory_var participant_factory; /** - * init_opendds_impl(*args[str], **kw) -> None - * - * Initialize participant_factory by passing args to - * TheParticipantFactoryWithArgs. Also perform some custom configuration based - * on keyword arguments: - * - * default_rtps: bool=True - * Set default transport and discovery to RTPS unless default_rtps=False was - * passed. - */ +* init_opendds_impl(*args[str], **kw) -> None +* +* Initialize participant_factory by passing args to +* TheParticipantFactoryWithArgs. Also perform some custom configuration based +* on keyword arguments: +* +* default_rtps: bool=True +* Set default transport and discovery to RTPS unless default_rtps=False was +* passed. +*/ PyObject* init_opendds_impl(PyObject* self, PyObject* args, PyObject* kw) { - /* - * In addition to the need to convert the arguments into an argv array, - * OpenDDS will mess with argv and argc so we need to create copies that we - * can cleanup properly afterwords. - */ - int original_argc = PySequence_Size(args); - int argc = original_argc; - char** original_argv = new char*[argc]; - char** argv = new char*[argc]; - if (!original_argv || !argv) return PyErr_NoMemory(); - for (int i = 0; i < argc; i++) { - // Get str object - Ref obj{PySequence_GetItem(args, i)}; - if (!obj) return nullptr; - Ref string_obj{PyObject_Str(*obj)}; - if (!string_obj) return nullptr; - - // Get UTF8 char* String - ssize_t string_len; - const char* string = PyUnicode_AsUTF8AndSize(*string_obj, &string_len); - if (!string) return nullptr; - - // Copy It - char* duplicate = new char[string_len + 1]; - if (!duplicate) return PyErr_NoMemory(); - duplicate[string_len] = '\0'; - argv[i] = original_argv[i] = strncpy(duplicate, string, string_len); - } - - /* - * Process default_rtps - */ - bool default_rtps = true; - Ref default_rtps_obj{PyMapping_GetItemString(kw, "default_rtps")}; - if (default_rtps_obj) { - int result = PyObject_IsTrue(*default_rtps_obj); - if (result == -1) return nullptr; - default_rtps = result; - } else { - PyErr_Clear(); - } - if (default_rtps) { - TheServiceParticipant->set_default_discovery( - OpenDDS::DCPS::Discovery::DEFAULT_RTPS); - OpenDDS::DCPS::TransportConfig_rch transport_config = - TheTransportRegistry->create_config("default_rtps_transport_config"); - OpenDDS::DCPS::TransportInst_rch transport_inst = - TheTransportRegistry->create_inst("default_rtps_transport", "rtps_udp"); - transport_config->instances_.push_back(transport_inst); - TheTransportRegistry->global_config(transport_config); - } - - // Initialize OpenDDS - participant_factory = TheParticipantFactoryWithArgs(argc, argv); - if (!participant_factory) { - PyErr_SetString(Errors::PyOpenDDS_Error(), "Failed to Initialize OpenDDS"); - return nullptr; - } - - // Cleanup args - for (int i = 0; i < original_argc; i++) { - delete original_argv[i]; - } - delete [] original_argv; - delete [] argv; - - Py_RETURN_NONE; + /* + * In addition to the need to convert the arguments into an argv array, + * OpenDDS will mess with argv and argc so we need to create copies that we + * can cleanup properly afterwords. + */ + int original_argc = PySequence_Size(args); + int argc = original_argc; + char** original_argv = new char*[argc]; + char** argv = new char*[argc]; + if (!original_argv || !argv) return PyErr_NoMemory(); + + for (int i = 0; i < argc; i++) { + // Get str object + Ref obj{PySequence_GetItem(args, i)}; + if (!obj) return nullptr; + Ref string_obj{PyObject_Str(*obj)}; + if (!string_obj) return nullptr; + + // Get UTF8 char* String + ssize_t string_len; + const char* string = PyUnicode_AsUTF8AndSize(*string_obj, &string_len); + if (!string) return nullptr; + + // Copy It + char* duplicate = new char[string_len + 1]; + if (!duplicate) return PyErr_NoMemory(); + duplicate[string_len] = '\0'; + argv[i] = original_argv[i] = strncpy(duplicate, string, string_len); + } + + /* + * Process default_rtps + */ + bool default_rtps = true; + Ref default_rtps_obj{PyMapping_GetItemString(kw, "default_rtps")}; + if (default_rtps_obj) { + int result = PyObject_IsTrue(*default_rtps_obj); + if (result == -1) return nullptr; + default_rtps = result; + } else { + PyErr_Clear(); + } + if (default_rtps) { + TheServiceParticipant->set_default_discovery(OpenDDS::DCPS::Discovery::DEFAULT_RTPS); + + OpenDDS::DCPS::TransportConfig_rch transport_config = + TheTransportRegistry->create_config("default_rtps_transport_config"); + + OpenDDS::DCPS::TransportInst_rch transport_inst = + TheTransportRegistry->create_inst("default_rtps_transport", "rtps_udp"); + + transport_config->instances_.push_back(transport_inst); + TheTransportRegistry->global_config(transport_config); + } + + // Initialize OpenDDS + participant_factory = TheParticipantFactoryWithArgs(argc, argv); + if (!participant_factory) { + PyErr_SetString(Errors::PyOpenDDS_Error(), "Failed to Initialize OpenDDS"); + return nullptr; + } + + // Cleanup args + for (int i = 0; i < original_argc; i++) { + delete original_argv[i]; + } + + delete [] original_argv; + delete [] argv; + + Py_RETURN_NONE; } /** - * Callback for Python to Call when the Python Participant is Deleted - */ +* Callback for Python to Call when the Python Participant is Deleted +*/ void delete_participant_var(PyObject* part_capsule) { - if (PyCapsule_CheckExact(part_capsule)) { - DDS::DomainParticipant_var participant = static_cast( - PyCapsule_GetPointer(part_capsule, nullptr)); - participant = nullptr; - } + if (PyCapsule_CheckExact(part_capsule)) { + DDS::DomainParticipant_var participant = static_cast( + PyCapsule_GetPointer(part_capsule, nullptr)); + + if (participant) { + //participant->delete_contained_entities(); + participant = nullptr; + } + } } /** - * create_participant(participant: DomainParticipant, domain: int) -> None - */ +* create_participant(participant: DomainParticipant, domain: int) -> None +*/ PyObject* create_participant(PyObject* self, PyObject* args) { - Ref pyparticipant; - unsigned domain; - if (!PyArg_ParseTuple(args, "OI", &*pyparticipant, &domain)) { - return nullptr; - } - pyparticipant++; - - // Create Participant - DDS::DomainParticipantQos qos; - participant_factory->get_default_participant_qos(qos); - DDS::DomainParticipant* participant = - participant_factory->create_participant( - domain, qos, - DDS::DomainParticipantListener::_nil(), - OpenDDS::DCPS::DEFAULT_STATUS_MASK); - if (!participant) { - PyErr_SetString(Errors::PyOpenDDS_Error(), "Failed to Create Participant"); - return nullptr; - } - - // Attach OpenDDS Participant to Participant Python Object - if (set_capsule(*pyparticipant, participant, delete_participant_var)) { - return nullptr; - } - - Py_RETURN_NONE; + Ref pyparticipant; + unsigned domain; + if (!PyArg_ParseTuple(args, "OI", &*pyparticipant, &domain)) { + return nullptr; + } + pyparticipant++; + + // Create Participant + DDS::DomainParticipantQos qos; + participant_factory->get_default_participant_qos(qos); + + DDS::DomainParticipant* participant = participant_factory->create_participant( + domain, qos, DDS::DomainParticipantListener::_nil(), + OpenDDS::DCPS::DEFAULT_STATUS_MASK); + + if (!participant) { + PyErr_SetString(Errors::PyOpenDDS_Error(), "Failed to Create Participant"); + return nullptr; + } + + // Attach OpenDDS Participant to Participant Python Object + if (set_capsule(*pyparticipant, participant, delete_participant_var)) { + return nullptr; + } + + Py_RETURN_NONE; } PyObject* participant_cleanup(PyObject* self, PyObject* args) { - Ref pyparticipant; - if (!PyArg_ParseTuple(args, "O", &*pyparticipant)) { - return nullptr; - } - pyparticipant++; - - // Get DomainParticipant_var - DDS::DomainParticipant* participant = - get_capsule(*pyparticipant); - if (!participant) return nullptr; - - participant->delete_contained_entities(); - Py_RETURN_NONE; + Ref pyparticipant; + if (!PyArg_ParseTuple(args, "O", &*pyparticipant)) { + return nullptr; + } + pyparticipant++; + + // Get DomainParticipant_var + DDS::DomainParticipant* participant = + get_capsule(*pyparticipant); + + if (!participant) return nullptr; + + participant->delete_contained_entities(); + participant_factory->delete_participant(participant); + + Py_RETURN_NONE; } /** - * Callback for Python to Call when the Topic Capsule is Deleted - */ +* Callback for Python to Call when the Topic Capsule is Deleted +*/ void delete_topic_var(PyObject* topic_capsule) { - if (PyCapsule_CheckExact(topic_capsule)) { - DDS::Topic_var topic = static_cast( - PyCapsule_GetPointer(topic_capsule, nullptr)); - topic = nullptr; - } + if (PyCapsule_CheckExact(topic_capsule)) { + DDS::Topic_var topic = static_cast(PyCapsule_GetPointer(topic_capsule, nullptr)); + if (topic) topic = nullptr; + } } /* - * create_topic(topic: Topic, participant: DomainParticipant, topic_name: str, topic_type: str) -> None - * - * Assumes all the arguments are the types listed above and the participant has - * a OpenDDS DomainParticipant with the type named by topic_type has already - * been registered with it. - */ +* create_topic(topic: Topic, participant: DomainParticipant, topic_name: str, topic_type: str) -> None +* +* Assumes all the arguments are the types listed above and the participant has +* a OpenDDS DomainParticipant with the type named by topic_type has already +* been registered with it. +*/ PyObject* create_topic(PyObject* self, PyObject* args) { - Ref pytopic; - Ref pyparticipant; - char* name; - char* type; - if (!PyArg_ParseTuple(args, "OOss", - &*pytopic, &*pyparticipant, &name, &type)) { - return nullptr; - } - pytopic++; - pyparticipant++; - - // Get DomainParticipant - DDS::DomainParticipant* participant = - get_capsule(*pyparticipant); - if (!participant) return nullptr; - - // Create Topic - DDS::Topic* topic = participant->create_topic( - name, type, TOPIC_QOS_DEFAULT, nullptr, - OpenDDS::DCPS::DEFAULT_STATUS_MASK); - if (!topic) { - PyErr_SetString(Errors::PyOpenDDS_Error(), "Failed to Create Topic"); - return nullptr; - } - - // Attach OpenDDS Topic to Topic Python Object - if (set_capsule(*pytopic, topic, delete_topic_var)) { - return nullptr; - } - - Py_RETURN_NONE; + Ref pytopic; + Ref pyparticipant; + char* name; + char* type; + if (!PyArg_ParseTuple(args, "OOss", + &*pytopic, &*pyparticipant, &name, &type)) { + return nullptr; + } + pytopic++; + pyparticipant++; + + // Get DomainParticipant + DDS::DomainParticipant* participant = + get_capsule(*pyparticipant); + if (!participant) return nullptr; + + // Create Topic + DDS::Topic* topic = participant->create_topic( + name, type, TOPIC_QOS_DEFAULT, nullptr, + OpenDDS::DCPS::DEFAULT_STATUS_MASK); + + if (!topic) { + PyErr_SetString(Errors::PyOpenDDS_Error(), "Failed to Create Topic"); + return nullptr; + } + + // Attach OpenDDS Topic to Topic Python Object + if (set_capsule(*pytopic, topic, delete_topic_var)) { + return nullptr; + } + + Py_RETURN_NONE; } /** - * Callback for Python to Call when the Subscriber Capsule is Deleted - */ +* Callback for Python to Call when the Subscriber Capsule is Deleted +*/ void delete_subscriber_var(PyObject* subscriber_capsule) { - if (PyCapsule_CheckExact(subscriber_capsule)) { - DDS::Subscriber_var subscriber = static_cast( - PyCapsule_GetPointer(subscriber_capsule, nullptr)); - subscriber = nullptr; - } + if (PyCapsule_CheckExact(subscriber_capsule)) { + DDS::Subscriber_var subscriber = static_cast( + PyCapsule_GetPointer(subscriber_capsule, nullptr)); + if (subscriber) subscriber = nullptr; + } } /** - * create_subscriber(subscriber: Subscriber, participant: DomainParticipant) -> None - */ +* create_subscriber(subscriber: Subscriber, participant: DomainParticipant) -> None +*/ PyObject* create_subscriber(PyObject* self, PyObject* args) { - Ref pyparticipant; - Ref pysubscriber; - if (!PyArg_ParseTuple(args, "OO", &*pysubscriber, &*pyparticipant)) { - return nullptr; - } - pyparticipant++; - pysubscriber++; - - // Get DomainParticipant_var - DDS::DomainParticipant* participant = - get_capsule(*pyparticipant); - if (!participant) { - return nullptr; - } - - // Create Subscriber - DDS::Subscriber* subscriber = participant->create_subscriber( - SUBSCRIBER_QOS_DEFAULT, nullptr, OpenDDS::DCPS::DEFAULT_STATUS_MASK); - if (!subscriber) { - PyErr_SetString(Errors::PyOpenDDS_Error(), "Failed to Create Subscriber"); - return nullptr; - } - - // Attach OpenDDS Subscriber to Subscriber Python Object - if (set_capsule(*pysubscriber, subscriber, delete_subscriber_var)) { - return nullptr; - } - - Py_RETURN_NONE; + Ref pyparticipant; + Ref pysubscriber; + if (!PyArg_ParseTuple(args, "OO", &*pysubscriber, &*pyparticipant)) { + return nullptr; + } + pyparticipant++; + pysubscriber++; + + // Get DomainParticipant_var + DDS::DomainParticipant* participant = + get_capsule(*pyparticipant); + if (!participant) return nullptr; + + // Create Subscriber + DDS::Subscriber* subscriber = participant->create_subscriber( + SUBSCRIBER_QOS_DEFAULT, nullptr, OpenDDS::DCPS::DEFAULT_STATUS_MASK); + + if (!subscriber) { + PyErr_SetString(Errors::PyOpenDDS_Error(), "Failed to Create Subscriber"); + return nullptr; + } + + // Attach OpenDDS Subscriber to Subscriber Python Object + if (set_capsule(*pysubscriber, subscriber, delete_subscriber_var)) { + return nullptr; + } + + Py_RETURN_NONE; } /** - * Callback for Python to Call when the Publisher Capsule is Deleted - */ +* Callback for Python to Call when the Publisher Capsule is Deleted +*/ void delete_publisher_var(PyObject* publisher_capsule) { - if (PyCapsule_CheckExact(publisher_capsule)) { - DDS::Publisher_var publisher = static_cast( - PyCapsule_GetPointer(publisher_capsule, nullptr)); - publisher = nullptr; - } + if (PyCapsule_CheckExact(publisher_capsule)) { + DDS::Publisher_var publisher = static_cast( + PyCapsule_GetPointer(publisher_capsule, nullptr)); + if (publisher) publisher = nullptr; + } } /** - * create_publisher(publisher: Publisher, participant: DomainParticipant) -> None - */ +* create_publisher(publisher: Publisher, participant: DomainParticipant) -> None +*/ PyObject* create_publisher(PyObject* self, PyObject* args) { - Ref pyparticipant; - Ref pypublisher; - if (!PyArg_ParseTuple(args, "OO", &*pypublisher, &*pyparticipant)) { - return nullptr; - } - pyparticipant++; - pypublisher++; - - // Get DomainParticipant_var - DDS::DomainParticipant* participant = - get_capsule(*pyparticipant); - if (!participant) return nullptr; - - // Create Publisher - DDS::Publisher* publisher = participant->create_publisher( - PUBLISHER_QOS_DEFAULT, nullptr, OpenDDS::DCPS::DEFAULT_STATUS_MASK); - if (!publisher) { - PyErr_SetString(Errors::PyOpenDDS_Error(), "Failed to Create Publisher"); - return nullptr; - } - - // Attach OpenDDS Publisher to Publisher Python Object - if (set_capsule(*pypublisher, publisher, delete_publisher_var)) { - return nullptr; - } - - Py_RETURN_NONE; + Ref pyparticipant; + Ref pypublisher; + if (!PyArg_ParseTuple(args, "OO", &*pypublisher, &*pyparticipant)) { + return nullptr; + } + pyparticipant++; + pypublisher++; + + // Get DomainParticipant_var + DDS::DomainParticipant* participant = + get_capsule(*pyparticipant); + if (!participant) return nullptr; + + // Create Publisher + DDS::Publisher* publisher = participant->create_publisher( + PUBLISHER_QOS_DEFAULT, nullptr, OpenDDS::DCPS::DEFAULT_STATUS_MASK); + + if (!publisher) { + PyErr_SetString(Errors::PyOpenDDS_Error(), "Failed to Create Publisher"); + return nullptr; + } + + // Attach OpenDDS Publisher to Publisher Python Object + if (set_capsule(*pypublisher, publisher, delete_publisher_var)) { + return nullptr; + } + + Py_RETURN_NONE; } /** - * Callback for Python to Call when the DataReader Capsule is Deleted - */ +* Callback for Python to Call when the DataReader Capsule is Deleted +*/ void delete_datareader_var(PyObject* reader_capsule) { - if (PyCapsule_CheckExact(reader_capsule)) { - DDS::DataReader_var reader = static_cast( - PyCapsule_GetPointer(reader_capsule, nullptr)); - reader = nullptr; - } + if (PyCapsule_CheckExact(reader_capsule)) { + DDS::DataReader_var reader = static_cast( + PyCapsule_GetPointer(reader_capsule, nullptr)); + + if (reader) { + DDS::DataReaderListener_ptr listener = reader->get_listener(); + /*DDS::DataReader::_narrow(reader)->get_listener();*/ + free(listener); + listener = nullptr; + reader = nullptr; + } + } +} + +bool update_writer_qos(Ref pyQos, DDS::DataWriterQos &qos) +{ + + Ref pydurability; + Ref pyreliability; + Ref pyhistory; + Ref pydurabilityKind; + Ref pyreliabilityKind; + Ref pyhistoryKind; + Ref pyhistorydepth; + + + // std::cerr << "get durability" << std::endl; + pydurability = PyObject_GetAttrString(*pyQos, "durability"); + if (!pydurability) return false; + pydurability ++; + + // std::cerr << "get reliability" << std::endl; + pyreliability = PyObject_GetAttrString(*pyQos, "reliability"); + if (!pyreliability) return false; + pyreliability ++; + + // std::cerr << "get history" << std::endl; + pyhistory = PyObject_GetAttrString(*pyQos, "history"); + if (!pyhistory) return false; + pyhistory ++; + + // std::cerr << "get dura kind" << std::endl; + pydurabilityKind = PyObject_GetAttrString(*pydurability, "kind"); + if (!pydurabilityKind) return false; + pydurabilityKind ++; + // std::cerr << "AsLong" << std::endl; + qos.durability.kind = (DDS::DurabilityQosPolicyKind) PyLong_AsLong(*pydurabilityKind); + + // std::cerr << "get rela kind" << std::endl; + pyreliabilityKind = PyObject_GetAttrString(*pyreliability, "kind"); + if (!pyreliabilityKind) return false; + pyreliabilityKind ++; + // std::cerr << "AsLong" << std::endl; + qos.reliability.kind = (DDS::ReliabilityQosPolicyKind) PyLong_AsLong(*pyreliabilityKind); + + // std::cerr << "get histo kind" << std::endl; + pyhistoryKind = PyObject_GetAttrString(*pyhistory, "kind"); + if (!pyhistoryKind) return false; + pyhistoryKind ++; + + // std::cerr << "AsLong" << std::endl; + qos.history.kind = (DDS::HistoryQosPolicyKind) PyLong_AsLong(*pyhistoryKind); + + pyhistorydepth = PyObject_GetAttrString(*pyhistory, "depth"); + if (!pyhistorydepth) return false; + pyhistorydepth ++; + qos.history.depth = PyLong_AsLong(*pyhistorydepth); + // std::cout <<"qos.history.depth : "< None +*/ +PyObject* create_datareader(PyObject* self, PyObject* args ) +{ + Ref pydatareader; + Ref pysubscriber; + Ref pytopic; + Ref pycallback; + Ref pyqos; + + if (!PyArg_ParseTuple(args, "OOOOO", + &*pydatareader, &*pysubscriber, &*pytopic, &*pycallback,&*pyqos )) { + return nullptr; + } + pydatareader++; + pysubscriber++; + pytopic++; + pycallback++; + pyqos++; + + + // Get Subscriber + DDS::Subscriber* subscriber = get_capsule(*pysubscriber); + if (!subscriber) return nullptr; + + // Get Topic + DDS::Topic* topic = get_capsule(*pytopic); + if (!topic) return nullptr; + + DataReaderListenerImpl * listener = nullptr; + if (*pycallback != Py_None) { + if (PyCallable_Check(*pycallback)) { + listener = new DataReaderListenerImpl(*pydatareader, *pycallback); + } + else { + throw Exception("Callback provided is not a callable object", PyExc_TypeError); + } + } + + + // Create QoS + DDS::DataReaderQos qos; + + subscriber->get_default_datareader_qos(qos); + bool isgoodqos = update_reader_qos(*pyqos,qos); + // std::cout <<"after update qos reader, qos.history.depth "<create_datareader( + topic, qos, listener, + OpenDDS::DCPS::DEFAULT_STATUS_MASK); + + if (!datareader) { + PyErr_SetString(Errors::PyOpenDDS_Error(), "Failed to Create DataReader"); + return nullptr; + } + + + // Attach OpenDDS DataReader to DataReader Python Object + if (set_capsule(*pydatareader, datareader, delete_datareader_var)) { + return nullptr; + } + + Py_RETURN_NONE; +} + +/** +* Callback for Python to Call when the DataWriter Capsule is Deleted +*/ +void delete_datawriter_var(PyObject* writer_capsule) +{ + if (PyCapsule_CheckExact(writer_capsule)) { + DDS::DataWriter_var writer = static_cast( + PyCapsule_GetPointer(writer_capsule, nullptr)); + if (writer) writer = nullptr; + } } /** - * create_datareader(datareader: DataReader, subscriber: Subscriber, topic: Topic) -> None - */ -PyObject* create_datareader(PyObject* self, PyObject* args) +* create_datawriter(datawriter: DataWriter, publisher: Publisher, topic: Topic) -> None +*/ +PyObject* create_datawriter(PyObject* self, PyObject* args) { - Ref pydatareader; - Ref pysubscriber; - Ref pytopic; - if (!PyArg_ParseTuple(args, "OOO", - &*pydatareader, &*pysubscriber, &*pytopic)) { - return nullptr; - } - pydatareader++; - pysubscriber++; - pytopic++; - - // Get Subscriber - DDS::Subscriber* subscriber = get_capsule(*pysubscriber); - if (!subscriber) return nullptr; - - // Get Topic - DDS::Topic* topic = get_capsule(*pytopic); - if (!topic) return nullptr; - - // Create DataReader - DDS::DataReader* datareader = subscriber->create_datareader( - topic, DATAREADER_QOS_DEFAULT, nullptr, - OpenDDS::DCPS::DEFAULT_STATUS_MASK); - if (!datareader) { - PyErr_SetString(Errors::PyOpenDDS_Error(), "Failed to Create DataReader"); - return nullptr; - } - - // Attach OpenDDS DataReader to DataReader Python Object - if (set_capsule(*pydatareader, datareader, delete_datareader_var)) { - return nullptr; - } - - Py_RETURN_NONE; + Ref pydatawriter; + Ref pypublisher; + Ref pytopic; + Ref pyqos; + if (!PyArg_ParseTuple(args, "OOOO", + &*pydatawriter, &*pypublisher, &*pytopic,&*pyqos)) { + return nullptr; + } + pydatawriter++; + pypublisher++; + pytopic++; + pyqos++; + + // Get Publisher + DDS::Publisher* publisher = get_capsule(*pypublisher); + if (!publisher) return nullptr; + + // Get Topic + DDS::Topic* topic = get_capsule(*pytopic); + if (!topic) return nullptr; + + // Create QoS + DDS::DataWriterQos qos; + publisher->get_default_datawriter_qos(qos); + // qos.reliability.kind = DDS::RELIABLE_RELIABILITY_QOS; + bool isgoodwriterqos = update_writer_qos(pyqos,qos); + // std::cout <<"after update qos writer, qos.history.depth "<create_datawriter( + topic, qos, nullptr, + OpenDDS::DCPS::DEFAULT_STATUS_MASK); + + if (!datawriter) { + PyErr_SetString(Errors::PyOpenDDS_Error(), "Failed to Create DataWriter"); + return nullptr; + } + + // Attach OpenDDS DataWriter to DataWriter Python Object + if (set_capsule(*pydatawriter, datawriter, delete_datawriter_var)) { + return nullptr; + } + + Py_RETURN_NONE; } /** - * datareader_wait_for( - * datareader: DataReader, status: StatusKind, - * seconds: int, nanoseconds: int) -> None - */ +* datareader_wait_for( +* datareader: DataReader, status: StatusKind, +* seconds: int, nanoseconds: int) -> None +*/ PyObject* datareader_wait_for(PyObject* self, PyObject* args) { - Ref pydatareader; - unsigned status; - int seconds; - unsigned nanoseconds; - if (!PyArg_ParseTuple(args, "OIiI", - &*pydatareader, &status, &seconds, &nanoseconds)) { - return nullptr; - } - pydatareader++; - - // Get DataReader - DDS::DataReader* reader = get_capsule(*pydatareader); - if (!reader) return nullptr; - - // Wait - DDS::StatusCondition_var condition = reader->get_statuscondition(); - condition->set_enabled_statuses(status); - DDS::WaitSet_var waitset = new DDS::WaitSet; - if (!waitset) return PyErr_NoMemory(); - waitset->attach_condition(condition); - DDS::ConditionSeq active; - DDS::Duration_t max_duration = {seconds, nanoseconds}; - if (Errors::check_rc(waitset->wait(active, max_duration))) return nullptr; - - Py_RETURN_NONE; + Ref pydatareader; + unsigned status; + int seconds; + unsigned nanoseconds; + if (!PyArg_ParseTuple(args, "OIiI", + &*pydatareader, &status, &seconds, &nanoseconds)) { + return nullptr; + } + pydatareader++; + + // Get DataReader + DDS::DataReader* reader = get_capsule(*pydatareader); + if (!reader) { + PyErr_SetString(Errors::PyOpenDDS_Error(), "Failed to retrieve DataReader Capsule"); + return nullptr; + } + + // Wait + DDS::StatusCondition_var condition = reader->get_statuscondition(); + condition->set_enabled_statuses(status); + +#ifndef __APPLE__ + DDS::WaitSet_var waitset = new DDS::WaitSet; + if (!waitset) return PyErr_NoMemory(); + waitset->attach_condition(condition); + DDS::ConditionSeq active; + DDS::Duration_t max_duration = {seconds, nanoseconds}; + if (Errors::check_rc(waitset->wait(active, max_duration))) return nullptr; +#else + // TODO: wait() causes segmentation fault + // TODO: fallback to naive implementation + auto t_now = std::chrono::steady_clock::now(); + auto t_secs = std::chrono::seconds(seconds); + auto t_nanosecs = std::chrono::nanoseconds(nanoseconds); + auto t_timeout = t_now + t_secs + t_nanosecs; + + while (t_now < t_timeout) { + DDS::SubscriptionMatchedStatus matches; + if (reader->get_subscription_matched_status(matches) != DDS::RETCODE_OK) { + PyErr_SetString(Errors::PyOpenDDS_Error(), "get_subscription_matched_status failed"); + return nullptr; + } + if (matches.current_count >= 1) { + break; + } + // wait for 1 second anyway, and update clock + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + t_now = std::chrono::steady_clock::now(); + } +#endif + Py_RETURN_NONE; +} + +/** +* datawriter_wait_for( +* datawriter: DataWriter, status: StatusKind, +* seconds: int, nanoseconds: int) -> None +*/ +PyObject* datawriter_wait_for(PyObject* self, PyObject* args) +{ + Ref pydatawriter; + unsigned status; + int seconds; + unsigned nanoseconds; + if (!PyArg_ParseTuple(args, "OIiI", + &*pydatawriter, &status, &seconds, &nanoseconds)) { + return nullptr; + } + pydatawriter++; + + // Get DataWriter + DDS::DataWriter* writer = get_capsule(*pydatawriter); + if (!writer) { + PyErr_SetString(Errors::PyOpenDDS_Error(), "Failed to retrieve DataWriter Capsule"); + return nullptr; + } + + // Wait + DDS::StatusCondition_var condition = writer->get_statuscondition(); + condition->set_enabled_statuses(status); + +#ifndef __APPLE__ + DDS::WaitSet_var waitset = new DDS::WaitSet; + if (!waitset) return PyErr_NoMemory(); + waitset->attach_condition(condition); + DDS::ConditionSeq active; + DDS::Duration_t max_duration = {seconds, nanoseconds}; + if (Errors::check_rc(waitset->wait(active, max_duration))) return nullptr; +#else + // TODO: wait() causes segmentation fault + // TODO: fallback to naive implementation + auto t_now = std::chrono::steady_clock::now(); + auto t_secs = std::chrono::seconds(seconds); + auto t_nanosecs = std::chrono::nanoseconds(nanoseconds); + auto t_timeout = t_now + t_secs + t_nanosecs; + + while (t_now < t_timeout) { + DDS::PublicationMatchedStatus matches; + if (writer->get_publication_matched_status(matches) != DDS::RETCODE_OK) { + PyErr_SetString(Errors::PyOpenDDS_Error(), "get_publication_matched_status failed"); + return nullptr; + } + if (matches.current_count >= 1) { + break; + } + // wait for 1 second anyway, and update clock + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + t_now = std::chrono::steady_clock::now(); + } +#endif + Py_RETURN_NONE; } + + + + + /// Documentation for Internal Python Objects const char* internal_docstr = "Internal to PyOpenDDS, not for use directly!"; PyMethodDef pyopendds_Methods[] = { - { - "init_opendds_impl", reinterpret_cast(init_opendds_impl), - METH_VARARGS | METH_KEYWORDS, internal_docstr - }, - {"create_participant", create_participant, METH_VARARGS, internal_docstr}, - {"participant_cleanup", participant_cleanup, METH_VARARGS, internal_docstr}, - {"create_subscriber", create_subscriber, METH_VARARGS, internal_docstr}, - {"create_publisher", create_publisher, METH_VARARGS, internal_docstr}, - {"create_topic", create_topic, METH_VARARGS, internal_docstr}, - {"create_datareader", create_datareader, METH_VARARGS, internal_docstr}, - {"datareader_wait_for", datareader_wait_for, METH_VARARGS, internal_docstr}, - {nullptr, nullptr, 0, nullptr} + { "init_opendds_impl", reinterpret_cast(init_opendds_impl), + METH_VARARGS | METH_KEYWORDS, internal_docstr + }, + { "create_participant", create_participant, METH_VARARGS, internal_docstr }, + { "participant_cleanup", participant_cleanup, METH_VARARGS, internal_docstr }, + { "create_subscriber", create_subscriber, METH_VARARGS, internal_docstr }, + { "create_publisher", create_publisher, METH_VARARGS, internal_docstr }, + { "create_topic", create_topic, METH_VARARGS, internal_docstr }, + { "create_datareader", create_datareader, METH_VARARGS, internal_docstr }, + { "create_datawriter", create_datawriter, METH_VARARGS, internal_docstr }, + { "datareader_wait_for", datareader_wait_for, METH_VARARGS, internal_docstr }, + { "datawriter_wait_for", datawriter_wait_for, METH_VARARGS, internal_docstr }, + { nullptr, nullptr, 0, nullptr } }; PyModuleDef pyopendds_Module = { - PyModuleDef_HEAD_INIT, - "_pyopendds", "Internal Python Bindings for OpenDDS", - -1, // Global State Module, because OpenDDS uses Singletons - pyopendds_Methods + PyModuleDef_HEAD_INIT, + "_pyopendds", "Internal Python Bindings for OpenDDS", + -1, // Global State Module, because OpenDDS uses Singletons + pyopendds_Methods }; } // Anonymous Namespace + PyMODINIT_FUNC PyInit__pyopendds() { - // Create _pyopendds - PyObject* native_module = PyModule_Create(&pyopendds_Module); - if (!native_module || Errors::cache()) return nullptr; + // Create _pyopendds + PyObject* native_module = PyModule_Create(&pyopendds_Module); + + if (!native_module || Errors::cache()) + return nullptr; - return native_module; + return native_module; } diff --git a/pyopendds/init_opendds.py b/pyopendds/init_opendds.py index 8c537ad..b6db09d 100644 --- a/pyopendds/init_opendds.py +++ b/pyopendds/init_opendds.py @@ -1,11 +1,11 @@ -'''Manage the initialization of OpenDDS and related functionality. -''' +""" Manage the initialization of OpenDDS and related functionality. +""" +import sys -def init_opendds(*args, - default_rtps=True, - opendds_debug_level=0): - '''Initialize OpenDDS using the TheParticipantFactoryWithArgs macro while + +def init_opendds(*args, default_rtps=True, opendds_debug_level=0): + """ Initialize OpenDDS using the TheParticipantFactoryWithArgs macro while passing the positional arguments in. default_rtps @@ -17,14 +17,14 @@ def init_opendds(*args, opendds_debug_level Debug logging level in OpenDDS which goes from 0 (off) to 10 (most verbose). It is printed to stdout. - ''' + """ - args = list(args) + args = list(sys.argv[1:]) if opendds_debug_level > 0: if not (1 <= opendds_debug_level <= 10): raise ValueError('OpenDDS debug level must be between 0 and 10!') args.extend(['-DCPSDebugLevel', str(opendds_debug_level)]) - from _pyopendds import init_opendds_impl + from _pyopendds import init_opendds_impl # noqa init_opendds_impl(*args, default_rtps=default_rtps) diff --git a/pyopendds/util.py b/pyopendds/util.py index d06eded..4d93276 100644 --- a/pyopendds/util.py +++ b/pyopendds/util.py @@ -1,5 +1,6 @@ -from typing import Union, Tuple +from typing import Union, Tuple, List from datetime import timedelta +from ctypes import c_ubyte, c_byte DDS_Duration_t = Tuple[int, int] TimeDurationType = Union[timedelta, DDS_Duration_t, int] @@ -20,4 +21,81 @@ def normalize_time_duration(duration: TimeDurationType): except Exception: raise TypeError('Could not extract time from value') - return (seconds, nanoseconds) + return seconds, nanoseconds + + +class _BitwiseImpl: + + value: ... + + def __init__(self, x): ... + + def __int__(self) -> int: ... + + def __or__(self, other): + self.value = self.value | other.value + return self + + def __and__(self, other): + self.value = self.value & other.value + return self + + def __xor__(self, other): + self.value = self.value ^ other.value + return self + + def __ior__(self, other): + self.value |= other.value + return self + + def __iand__(self, other): + self.value &= other.value + return self + + def __ixor__(self, other): + self.value ^= other.value + return self + + def __invert__(self): + self.value = ~self.value + return self + + +class UByte(c_ubyte, _BitwiseImpl): + + def __init__(self, x): + super().__init__(x) + + def __repr__(self): + return 'ub\\' + bin(self.value) + + def __int__(self) -> int: + return super().value + + @staticmethod + def bytes_to_list(data: bytes) -> List['UByte']: + return list(map(UByte, data)) + + @staticmethod + def list_to_bytes(data: List['UByte']) -> bytes: + return bytes(map(int, data)) + + +class Byte(c_byte, _BitwiseImpl): + + def __init__(self, x): + super().__init__(x) + + def __repr__(self): + return 'sb\\' + bin(self.value) + + def __int__(self) -> int: + return super().value + + @staticmethod + def bytes_to_list(data: bytes) -> List['Byte']: + return list(map(Byte, data)) + + @staticmethod + def list_to_bytes(data: List['Byte']) -> bytes: + return bytes(map(int, data)) diff --git a/setup.cfg b/setup.cfg index 1e6bb3a..59ea2a9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,13 +2,13 @@ name = pyopendds version = 0.1.0 author = Fred Hornsey -author-email = hornseyf@objectcomputing.com -home-page = https://github.com/oci-labs/pyopendds +author_email = hornseyf@objectcomputing.com +home_page = https://github.com/oci-labs/pyopendds description = Python Bindings for OpenDDS -long-description = file: README.md -long-description-content-type = text/markdown +long_description = file: README.md +long_description_content_type = text/markdown license = MIT -license-file = LICENSE +license_file = LICENSE keywords = opendds dds classifiers = License :: OSI Approved :: MIT License @@ -24,7 +24,7 @@ classifiers = Topic :: Software Development :: Libraries [flake8] -max-line-length = 100 +max_line_length = 100 ignore = E128, E131, # Tries to Enforce an Arbitrary Indentation Pattern diff --git a/setup.py b/setup.py index 5ed1314..22c8fea 100644 --- a/setup.py +++ b/setup.py @@ -17,6 +17,7 @@ entry_points={ 'console_scripts': [ 'itl2py=pyopendds.dev.itl2py.__main__:main', + 'pyidl=pyopendds.dev.pyidl.__main__:run', ], }, package_data={ @@ -28,6 +29,6 @@ ], }, install_requires=[ - 'jinja2', + 'Jinja2' ], ) diff --git a/tests/basic_test/CMakeLists.txt b/tests/basic_test/CMakeLists.txt index 4b8b942..213f35b 100644 --- a/tests/basic_test/CMakeLists.txt +++ b/tests/basic_test/CMakeLists.txt @@ -17,9 +17,28 @@ export( FILE "${CMAKE_CURRENT_BINARY_DIR}/basic_idlConfig.cmake" ) +# add_library(airbus_idl SHARED) +# if(${CPP11_IDL}) +# set(opendds_idl_mapping_option "-Lc++11") +# endif() +# OPENDDS_TARGET_SOURCES(airbus_idl "airbusdds.idl" +# OPENDDS_IDL_OPTIONS "-Gitl" "${opendds_idl_mapping_option}") +# target_link_libraries(airbus_idl PUBLIC OpenDDS::Dcps) +# export( +# TARGETS airbus_idl +# FILE "${CMAKE_CURRENT_BINARY_DIR}/airbus_idlConfig.cmake" +# ) + add_executable(publisher publisher.cpp) target_link_libraries(publisher OpenDDS::OpenDDS basic_idl) if(${CPP11_IDL}) set_target_properties(publisher PROPERTIES COMPILE_DEFINITIONS "CPP11_IDL") endif() + +add_executable(subscriber subscriber.cpp DataReaderListenerImpl.cpp) +target_link_libraries(subscriber OpenDDS::OpenDDS basic_idl) +if(${CPP11_IDL}) + set_target_properties(subscriber PROPERTIES + COMPILE_DEFINITIONS "CPP11_IDL") +endif() diff --git a/tests/basic_test/DataReaderListenerImpl.cpp b/tests/basic_test/DataReaderListenerImpl.cpp new file mode 100644 index 0000000..a164871 --- /dev/null +++ b/tests/basic_test/DataReaderListenerImpl.cpp @@ -0,0 +1,93 @@ +/* + * + * + * Distributed under the OpenDDS License. + * See: http://www.opendds.org/license.html + */ + +#include +#include + +#include "DataReaderListenerImpl.h" +#include "basicTypeSupportC.h" +#include "basicTypeSupportImpl.h" + +#include + +void +DataReaderListenerImpl::on_requested_deadline_missed( + DDS::DataReader_ptr /*reader*/, + const DDS::RequestedDeadlineMissedStatus& /*status*/) +{ +} + +void +DataReaderListenerImpl::on_requested_incompatible_qos( + DDS::DataReader_ptr /*reader*/, + const DDS::RequestedIncompatibleQosStatus& /*status*/) +{ +} + +void +DataReaderListenerImpl::on_sample_rejected( + DDS::DataReader_ptr /*reader*/, + const DDS::SampleRejectedStatus& /*status*/) +{ +} + +void +DataReaderListenerImpl::on_liveliness_changed( + DDS::DataReader_ptr /*reader*/, + const DDS::LivelinessChangedStatus& /*status*/) +{ +} + +void +DataReaderListenerImpl::on_data_available(DDS::DataReader_ptr reader) +{ + basic::ReadingDataReader_var reader_i = + basic::ReadingDataReader::_narrow(reader); + + if (!reader_i) { + ACE_ERROR((LM_ERROR, + ACE_TEXT("ERROR: %N:%l: on_data_available() -") + ACE_TEXT(" _narrow failed!\n"))); + ACE_OS::exit(1); + } + + basic::Reading sample; + DDS::SampleInfo info; + + DDS::ReturnCode_t error = reader_i->take_next_sample(sample, info); + + if (error == DDS::RETCODE_OK) { + std::cout << "SampleInfo.sample_rank = " << info.sample_rank << std::endl; + std::cout << "SampleInfo.instance_state = " << info.instance_state << std::endl; + + if (info.valid_data) { + std::cout << "Message: kind = " << sample.kind << std::endl + << " value = " << sample.value << std::endl + << " where = " << sample.where << std::endl; + + } + + } else { + ACE_ERROR((LM_ERROR, + ACE_TEXT("ERROR: %N:%l: on_data_available() -") + ACE_TEXT(" take_next_sample failed!\n"))); + } +} + +void +DataReaderListenerImpl::on_subscription_matched( + DDS::DataReader_ptr /*reader*/, + const DDS::SubscriptionMatchedStatus& /*status*/) +{ +} + +void +DataReaderListenerImpl::on_sample_lost( + DDS::DataReader_ptr /*reader*/, + const DDS::SampleLostStatus& /*status*/) +{ +} \ No newline at end of file diff --git a/tests/basic_test/DataReaderListenerImpl.h b/tests/basic_test/DataReaderListenerImpl.h new file mode 100644 index 0000000..79955a5 --- /dev/null +++ b/tests/basic_test/DataReaderListenerImpl.h @@ -0,0 +1,48 @@ +/* + * + * + * Distributed under the OpenDDS License. + * See: http://www.opendds.org/license.html + */ + +#ifndef DATAREADER_LISTENER_IMPL_H +#define DATAREADER_LISTENER_IMPL_H + +#include + +#include +#include +#include + +class DataReaderListenerImpl + : public virtual OpenDDS::DCPS::LocalObject { +public: + virtual void on_requested_deadline_missed( + DDS::DataReader_ptr reader, + const DDS::RequestedDeadlineMissedStatus& status); + + virtual void on_requested_incompatible_qos( + DDS::DataReader_ptr reader, + const DDS::RequestedIncompatibleQosStatus& status); + + virtual void on_sample_rejected( + DDS::DataReader_ptr reader, + const DDS::SampleRejectedStatus& status); + + virtual void on_liveliness_changed( + DDS::DataReader_ptr reader, + const DDS::LivelinessChangedStatus& status); + + virtual void on_data_available( + DDS::DataReader_ptr reader); + + virtual void on_subscription_matched( + DDS::DataReader_ptr reader, + const DDS::SubscriptionMatchedStatus& status); + + virtual void on_sample_lost( + DDS::DataReader_ptr reader, + const DDS::SampleLostStatus& status); +}; + +#endif /* DATAREADER_LISTENER_IMPL_H */ \ No newline at end of file diff --git a/tests/basic_test/basic.idl b/tests/basic_test/basic.idl index 67dbc63..82a7b1c 100644 --- a/tests/basic_test/basic.idl +++ b/tests/basic_test/basic.idl @@ -5,10 +5,18 @@ module basic { acceleration }; + struct Sample { + long value; + string where; + }; + + typedef sequence seqSample; + @topic struct Reading { ReadingKind kind; long value; string where; + seqSample sampleSeq; }; }; diff --git a/tests/basic_test/publisher.py b/tests/basic_test/publisher.py new file mode 100644 index 0000000..7afbd78 --- /dev/null +++ b/tests/basic_test/publisher.py @@ -0,0 +1,41 @@ +import sys +import time +from datetime import timedelta +from pyopendds.Qos import DataWriterQos + +from pyopendds import \ + init_opendds, DomainParticipant, StatusKind, PyOpenDDS_Error + +from pybasic.basic import Reading, ReadingKind + +if __name__ == "__main__": + try: + # Initialize OpenDDS and Create DDS Entities + init_opendds(opendds_debug_level=1) + + domain = DomainParticipant(34) + topic = domain.create_topic('Readings', Reading) + publisher = domain.create_publisher() + datawriterqos = DataWriterQos() + datawriterqos.history.depth = 2 + + writer = publisher.create_datawriter(topic,qos = datawriterqos) + + + # Wait for Subscriber to Connect + print('Waiting for Subscriber...') + writer.wait_for(StatusKind.PUBLICATION_MATCHED, timedelta(seconds=60)) + print('Found subscriber!') + + sample = Reading() + sample.kind = ReadingKind.acceleration + sample.value = 123 + sample.where = "somewhere" + + time.sleep(1) + # Read and Print Sample + writer.write(sample) + print('Done!') + + except PyOpenDDS_Error as e: + sys.exit(e) diff --git a/tests/basic_test/run_test.sh b/tests/basic_test/run_test.sh index 1591bda..a527fe2 100644 --- a/tests/basic_test/run_test.sh +++ b/tests/basic_test/run_test.sh @@ -5,20 +5,45 @@ sub=$! cd $dir ./publisher -DCPSConfigFile ../rtps.ini & pub=$! +cd - exit_status=0 wait $pub pub_status=$? if [ $pub_status -ne 0 ] then - echo "Publisher exited with status $pub_status" 1>&2 + echo "Cpp publisher exited with status $pub_status" 1>&2 exit_status=1 fi wait $sub sub_status=$? if [ $sub_status -ne 0 ] then - echo "Subscriber exited with status $sub_status" 1>&2 + echo "Python subscriber exited with status $sub_status" 1>&2 + exit_status=1 +fi + +cd $dir +./subscriber -DCPSConfigFile ../rtps.ini & +sub=$! +cd - + +LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$dir" python3 publisher.py & +pub=$! + +exit_status=0 +wait $pub +pub_status=$? +if [ $pub_status -ne 0 ] +then + echo "Python publisher exited with status $pub_status" 1>&2 + exit_status=1 +fi +wait $sub +sub_status=$? +if [ $sub_status -ne 0 ] +then + echo "Cpp subscriber exited with status $sub_status" 1>&2 exit_status=1 fi exit $exit_status diff --git a/tests/basic_test/subscriber.cpp b/tests/basic_test/subscriber.cpp new file mode 100644 index 0000000..36789d4 --- /dev/null +++ b/tests/basic_test/subscriber.cpp @@ -0,0 +1,130 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include "DataReaderListenerImpl.h" +#include "basicTypeSupportImpl.h" + +#include + +using OpenDDS::DCPS::retcode_to_string; + +int main(int argc, char* argv[]) { + + try { + // Init OpenDDS + TheServiceParticipant->default_configuration_file("rtps.ini"); + DDS::DomainParticipantFactory_var opendds = + TheParticipantFactoryWithArgs(argc, argv); + + DDS::DomainParticipantQos part_qos; + opendds->get_default_participant_qos(part_qos); + DDS::DomainParticipant_var participant = opendds->create_participant( + 34, part_qos, 0, OpenDDS::DCPS::DEFAULT_STATUS_MASK); + if (!participant) { + std::cerr << "Error: Failed to create participant" << std::endl; + return 1; + } + + basic::ReadingTypeSupport_var ts = new basic::ReadingTypeSupportImpl(); + DDS::ReturnCode_t rc = ts->register_type(participant.in(), ""); + if (rc != DDS::RETCODE_OK) { + std::cerr + << "Error: Failed to register type: " + << retcode_to_string(rc) << std::endl; + return 1; + } + + CORBA::String_var type_name = ts->get_type_name(); + DDS::Topic_var topic = participant->create_topic( + "Readings", type_name.in(), TOPIC_QOS_DEFAULT, 0, + OpenDDS::DCPS::DEFAULT_STATUS_MASK); + if (!topic) { + std::cerr << "Error: Failed to create topic" << std::endl; + return 1; + } + + DDS::Subscriber_var subscriber = participant->create_subscriber( + SUBSCRIBER_QOS_DEFAULT, 0, + OpenDDS::DCPS::DEFAULT_STATUS_MASK); + if (!subscriber) { + std::cerr << "Error: Failed to create subscriber" << std::endl; + return 1; + } + + DDS::DataReaderListener_var listener(new DataReaderListenerImpl); + DDS::DataReaderQos qos; + subscriber->get_default_datareader_qos(qos); + DDS::DataReader_var reader = subscriber->create_datareader( + topic.in(), qos, listener, + OpenDDS::DCPS::DEFAULT_STATUS_MASK); + if (!reader) { + std::cerr << "Error: Failed to create reader" << std::endl; + return 1; + } + basic::ReadingDataReader_var reader_i = + basic::ReadingDataReader::_narrow(reader); + + if (!reader_i) { + ACE_ERROR_RETURN((LM_ERROR, + ACE_TEXT("ERROR: %N:%l: main() -") + ACE_TEXT(" _narrow failed!\n")), + 1); + } + + // Wait for Subscriber + std::cout << "Wating for Subscriber..." << std::endl; + DDS::StatusCondition_var sc = reader->get_statuscondition(); + sc->set_enabled_statuses(DDS::SUBSCRIPTION_MATCHED_STATUS); + DDS::WaitSet_var ws = new DDS::WaitSet; + ws->attach_condition(sc); + const DDS::Duration_t max_wait = {10, 0}; + DDS::SubscriptionMatchedStatus status = {0, 0, 0, 0, 0}; + while (status.current_count < 1) { + DDS::ConditionSeq active; + if (ws->wait(active, max_wait) != DDS::RETCODE_OK) { + std::cerr << "Error: Timedout waiting for subscriber" << std::endl; + return 1; + } + if (reader->get_subscription_matched_status(status) != DDS::RETCODE_OK) { + std::cerr << "Error: Failed to get pub matched status" << std::endl; + return 1; + } + } + ws->detach_condition(sc); + std::cout << "Found Publisher..." << std::endl; + + DDS::SubscriptionMatchedStatus matches; + if (reader->get_subscription_matched_status(matches) != DDS::RETCODE_OK) { + ACE_ERROR_RETURN((LM_ERROR, + ACE_TEXT("ERROR: %N:%l: main() -") + ACE_TEXT(" get_subscription_matched_status failed!\n")), + 1); + } + + DDS::ConditionSeq conditions; + DDS::Duration_t timeout = { 60, 0 }; + if (ws->wait(conditions, timeout) != DDS::RETCODE_OK) { + ACE_ERROR_RETURN((LM_ERROR, + ACE_TEXT("ERROR: %N:%l: main() -") + ACE_TEXT(" wait failed!\n")), + 1); + } + + // Cleanup + participant->delete_contained_entities(); + opendds->delete_participant(participant.in()); + TheServiceParticipant->shutdown(); + + } catch (const CORBA::Exception& e) { + e._tao_print_exception("Exception caught in main():"); + return 1; + } + + return 0; +} diff --git a/tests/basic_test/subscriber.py b/tests/basic_test/subscriber.py index f5bb33b..c143284 100644 --- a/tests/basic_test/subscriber.py +++ b/tests/basic_test/subscriber.py @@ -1,28 +1,47 @@ import sys +import time from datetime import timedelta +from pyopendds.Qos import DataReaderQos from pyopendds import \ init_opendds, DomainParticipant, StatusKind, PyOpenDDS_Error -from pybasic.basic import Reading +from pybasic.basic import * + + +class TestClass: + def listener_func(self, sample: Reading): + print("main callback !", file=sys.stderr) + print(sample) + # todo: investigate the need of this sleep + time.sleep(1) + if __name__ == "__main__": try: + listener = TestClass() # Initialize OpenDDS and Create DDS Entities - init_opendds(opendds_debug_level=1) + init_opendds(opendds_debug_level=10) domain = DomainParticipant(34) topic = domain.create_topic('Readings', Reading) subscriber = domain.create_subscriber() - reader = subscriber.create_datareader(topic) + # Change qos testing + datareaderqos = DataReaderQos() + datareaderqos.history.depth = 2 + print("test subscriber") + print(datareaderqos) + print(datareaderqos.durability.kind) + reader = subscriber.create_datareader(topic=topic, qos =datareaderqos, listener=listener.listener_func) # Wait for Publisher to Connect print('Waiting for Publisher...') - reader.wait_for(StatusKind.SUBSCRIPTION_MATCHED, timedelta(seconds=5)) + reader.wait_for(StatusKind.SUBSCRIPTION_MATCHED, timedelta(seconds=30)) print('Found Publisher!') # Read and Print Sample - print(reader.take_next_sample()) + # print(reader.take_next_sample()) + time.sleep(60) print('Done!') - except PyOpenDDS_Error as e: + except Exception as e: sys.exit(e) diff --git a/tests/typeSupport_test/TypeSupport_test.idl b/tests/typeSupport_test/TypeSupport_test.idl new file mode 100644 index 0000000..dfb6c29 --- /dev/null +++ b/tests/typeSupport_test/TypeSupport_test.idl @@ -0,0 +1,23 @@ + +#include "TypeSupport_to_include.idl" + +module TypeSupportTest { + + @topic + struct MySample1 { + @key string a_string; + boolean a_bool; + float a_float; + double a_double; + short a_short; // 16 + long a_long; // 32 + long long a_longlong; // 64 + unsigned short a_ushort; + unsigned long a_ulong; + unsigned long long a_ulonglong; + char a_char; + wchar a_wchar; + octet an_octet; + MySample2 a_tata; + }; +}; diff --git a/tests/typeSupport_test/TypeSupport_to_include.idl b/tests/typeSupport_test/TypeSupport_to_include.idl new file mode 100644 index 0000000..aaf9377 --- /dev/null +++ b/tests/typeSupport_test/TypeSupport_to_include.idl @@ -0,0 +1,6 @@ + +@topic +struct MySample2 { + @key string a_string; + double a_double; +}; \ No newline at end of file diff --git a/tests/typeSupport_test/main.py b/tests/typeSupport_test/main.py new file mode 100644 index 0000000..c24259d --- /dev/null +++ b/tests/typeSupport_test/main.py @@ -0,0 +1,9 @@ +from pyTypeSupportTest.TypeSupportTest import MySample1 +from pyTypeSupportTest import MySample2 + + +if __name__ == "__main__": + sample_1 = MySample1() + sample_2 = MySample2() + print(sample_1) + print(sample_2) diff --git a/tests/typeSupport_test/run_test.sh b/tests/typeSupport_test/run_test.sh new file mode 100755 index 0000000..d07dcc0 --- /dev/null +++ b/tests/typeSupport_test/run_test.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +printf 'Compiling TypesSupport .idl files...\n' +pyidl TypeSupport_test.idl TypeSupport_to_include.idl -p TypeSupportTest + +printf '\nRunning test:\n' +python main.py