From 6060c84e71977556a98e8eddbfddb62cf100c792 Mon Sep 17 00:00:00 2001 From: David Pierret Date: Tue, 4 May 2021 13:06:56 +0200 Subject: [PATCH 01/55] Add publisher capability to pyopendds Add basic support for publisher know issue : Segfault after write() call Signed-off-by: David Pierret --- pyopendds/DataWriter.py | 31 ++++- pyopendds/DomainParticipant.py | 2 +- pyopendds/Publisher.py | 7 +- pyopendds/dev/include/pyopendds/user.hpp | 57 ++++++--- pyopendds/dev/itl2py/CppOutput.py | 30 ++++- pyopendds/dev/itl2py/PythonOutput.py | 2 +- pyopendds/dev/itl2py/templates/user.cpp | 37 +++++- pyopendds/ext/_pyopendds.cpp | 89 ++++++++++++++ pyopendds/init_opendds.py | 4 +- tests/basic_test/CMakeLists.txt | 7 ++ tests/basic_test/DataReaderListenerImpl.cpp | 93 ++++++++++++++ tests/basic_test/DataReaderListenerImpl.h | 48 ++++++++ tests/basic_test/publisher.py | 34 +++++ tests/basic_test/run_test.sh | 29 ++++- tests/basic_test/subscriber.cpp | 130 ++++++++++++++++++++ 15 files changed, 569 insertions(+), 31 deletions(-) create mode 100644 tests/basic_test/DataReaderListenerImpl.cpp create mode 100644 tests/basic_test/DataReaderListenerImpl.h create mode 100644 tests/basic_test/publisher.py create mode 100644 tests/basic_test/subscriber.cpp diff --git a/pyopendds/DataWriter.py b/pyopendds/DataWriter.py index a6beee8..c418265 100644 --- a/pyopendds/DataWriter.py +++ b/pyopendds/DataWriter.py @@ -1,2 +1,29 @@ -class DataWriter: - pass +from __future__ import annotations + +from .Topic import Topic +from .constants import StatusKind +from .util import TimeDurationType, normalize_time_duration + +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from .Publisher import Publisher + + +class DataWriter(object): + + def __init__(self, publisher: Publisher, topic: Topic, qos=None, listener=None): + self.topic = topic + self.qos = qos + self.listener = listener + self.publisher = publisher + publisher.writers.append(self) + + from _pyopendds import create_datawriter + create_datawriter(self, publisher, 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 write(self, sample): + return self.topic._ts_package.write(self, sample) diff --git a/pyopendds/DomainParticipant.py b/pyopendds/DomainParticipant.py index 862b423..64d9bd8 100644 --- a/pyopendds/DomainParticipant.py +++ b/pyopendds/DomainParticipant.py @@ -3,7 +3,7 @@ from .Publisher import Publisher -class DomainParticipant: +class DomainParticipant(object): def __init__(self, domain: int, qos=None, listener=None): self.domain = int(domain) diff --git a/pyopendds/Publisher.py b/pyopendds/Publisher.py index a292b85..e3a65e1 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,7 +8,7 @@ from .DomainParticipant import DomainParticipant -class Publisher: +class Publisher(object): def __init__(self, participant: DomainParticipant, qos=None, listener=None): participant.publishers.append(self) @@ -18,5 +19,5 @@ def __init__(self, participant: DomainParticipant, qos=None, listener=None): from _pyopendds import create_publisher create_publisher(self, participant) - def create_datawriter(self, topic: Topic, qos=None, listener=None): - pass + def create_datawriter(self, topic: Topic, qos=None, listener=None) -> DataWriter: + return DataWriter(self, topic, qos, listener) diff --git a/pyopendds/dev/include/pyopendds/user.hpp b/pyopendds/dev/include/pyopendds/user.hpp index aa7c7fd..387b457 100644 --- a/pyopendds/dev/include/pyopendds/user.hpp +++ b/pyopendds/dev/include/pyopendds/user.hpp @@ -30,7 +30,7 @@ class IntegerType { static PyObject* get_python_class() { - return PyLong_Type; + return PyLong_FromLong(0); } static void cpp_to_python(const T& cpp, PyObject*& py) @@ -45,24 +45,23 @@ class IntegerType { static void python_to_cpp(PyObject* py, T& cpp) { - LongType value; + long value; if (limits::is_signed) { - value = PyLong_AsLong(py); + value = PyLong_AsLong(py); } else { - value = PyLong_AsUnsignedLong(py); + 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; + cpp = T(value); } + }; typedef ::CORBA::Long i32; template<> class Type: public IntegerType {}; +typedef ::CORBA::Short i16; +template<> class Type: public IntegerType {}; + // TODO: Put Other Integer Types Here const char* string_data(const std::string& cpp) @@ -90,7 +89,7 @@ class StringType { public: static PyObject* get_python_class() { - return PyUnicode_Type; + return PyUnicode_FromString(""); } static void cpp_to_python(const T& cpp, PyObject*& py, const char* encoding) @@ -101,9 +100,14 @@ class StringType { py = o; } - static void python_to_cpp(PyObject* py, T& cpp) + static void python_to_cpp(PyObject* py, T& cpp, const char* encoding) { - // TODO: Encode or Throw Unicode Error + PyObject* repr = PyObject_Repr(py); + PyObject* str = PyUnicode_AsEncodedString(repr, "utf-8", "~E~"); + const char *bytes = PyBytes_AS_STRING(str); + cpp = T(bytes); + Py_XDECREF(repr); + Py_XDECREF(str); } }; @@ -127,6 +131,7 @@ class TopicTypeBase { 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; typedef std::shared_ptr Ptr; typedef std::map TopicTypes; @@ -215,7 +220,7 @@ class TopicType : public TopicTypeBase { DDS::WaitSet_var ws = new DDS::WaitSet; ws->attach_condition(read_condition); DDS::ConditionSeq active; - const DDS::Duration_t max_wait_time = {10, 0}; + const DDS::Duration_t max_wait_time = {60, 0}; if (Errors::check_rc(ws->wait(active, max_wait_time))) { throw Exception(); } @@ -234,6 +239,30 @@ class TopicType : public TopicTypeBase { return rv; } + PyObject* write(PyObject* pywriter, PyObject* pysample) + { + DDS::DataWriter* writer = get_capsule(pywriter); + if (!writer) PyErr_SetString(PyExc_Exception, "writer is a NULL pointer"); + + 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); + throw Exception( + "ERROR", Errors::PyOpenDDS_Error()); + if (Errors::check_rc(rc)) { + throw Exception(); + } + + return pysample; + } + PyObject* get_python_class() { return Type::get_python_class(); diff --git a/pyopendds/dev/itl2py/CppOutput.py b/pyopendds/dev/itl2py/CppOutput.py index b922eb0..0c7589e 100644 --- a/pyopendds/dev/itl2py/CppOutput.py +++ b/pyopendds/dev/itl2py/CppOutput.py @@ -44,7 +44,9 @@ 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 = [] @@ -61,16 +63,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 +128,6 @@ 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, }) diff --git a/pyopendds/dev/itl2py/PythonOutput.py b/pyopendds/dev/itl2py/PythonOutput.py index 42920d3..9efaf6a 100644 --- a/pyopendds/dev/itl2py/PythonOutput.py +++ b/pyopendds/dev/itl2py/PythonOutput.py @@ -98,4 +98,4 @@ def visit_enum(self, enum_type): dict(name=name, value=value) for name, value in enum_type.members.items() ], ), - )) + )) \ No newline at end of file diff --git a/pyopendds/dev/itl2py/templates/user.cpp b/pyopendds/dev/itl2py/templates/user.cpp index e57c9ca..fa5e882 100644 --- a/pyopendds/dev/itl2py/templates/user.cpp +++ b/pyopendds/dev/itl2py/templates/user.cpp @@ -59,7 +59,21 @@ class Type { static void python_to_cpp(PyObject* py, /*{{ type.cpp_name }}*/& cpp) { PyObject* cls = get_python_class(); - /*{{ type.from_lines | indent(4) }}*/ + /*{% if type.to_replace %}*/ + cpp = static_cast(PyLong_AsLong(py)); + /*{% else %}*/ + if (py) { + + if (PyObject_IsInstance(py, cls) != 1) { + throw Exception("Not a {{ type.py_name }}", PyExc_TypeError); + } + } else { + PyObject* args; + /*{{ type.new_lines | indent(6) }}*/ + py = PyObject_CallObject(cls, args); + } + /*{% if type.from_lines %}*//*{{ type.from_lines | indent(4) }}*//*{% endif %}*/ + /*{% endif %}*/ } }; @@ -119,9 +133,30 @@ 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++; + + // 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/ext/_pyopendds.cpp b/pyopendds/ext/_pyopendds.cpp index 0f16aaa..34b7785 100644 --- a/pyopendds/ext/_pyopendds.cpp +++ b/pyopendds/ext/_pyopendds.cpp @@ -366,6 +366,59 @@ PyObject* create_datareader(PyObject* self, PyObject* args) 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)); + writer = nullptr; + } +} + +/** + * create_datawriter(datawriter: DataWriter, publisher: Publisher, topic: Topic) -> None + */ +PyObject* create_datawriter(PyObject* self, PyObject* args) +{ + Ref pydatawriter; + Ref pypublisher; + Ref pytopic; + if (!PyArg_ParseTuple(args, "OOO", + &*pydatawriter, &*pypublisher, &*pytopic)) { + return nullptr; + } + pydatawriter++; + pypublisher++; + pytopic++; + + // 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 DataWriter + DDS::DataWriter* datawriter = publisher->create_datawriter( + topic, DATAWRITER_QOS_DEFAULT, 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, @@ -400,6 +453,40 @@ PyObject* datareader_wait_for(PyObject* self, PyObject* args) 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) return nullptr; + + // Wait + DDS::StatusCondition_var condition = writer->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; +} + /// Documentation for Internal Python Objects const char* internal_docstr = "Internal to PyOpenDDS, not for use directly!"; @@ -414,7 +501,9 @@ PyMethodDef pyopendds_Methods[] = { {"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} }; diff --git a/pyopendds/init_opendds.py b/pyopendds/init_opendds.py index 8c537ad..986d947 100644 --- a/pyopendds/init_opendds.py +++ b/pyopendds/init_opendds.py @@ -1,6 +1,7 @@ '''Manage the initialization of OpenDDS and related functionality. ''' +import sys def init_opendds(*args, default_rtps=True, @@ -19,12 +20,13 @@ def init_opendds(*args, 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)]) + print (f" arguments = {args}\n") from _pyopendds import init_opendds_impl init_opendds_impl(*args, default_rtps=default_rtps) diff --git a/tests/basic_test/CMakeLists.txt b/tests/basic_test/CMakeLists.txt index 4b8b942..a5a8bb9 100644 --- a/tests/basic_test/CMakeLists.txt +++ b/tests/basic_test/CMakeLists.txt @@ -23,3 +23,10 @@ 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/publisher.py b/tests/basic_test/publisher.py new file mode 100644 index 0000000..ab09dfb --- /dev/null +++ b/tests/basic_test/publisher.py @@ -0,0 +1,34 @@ +import sys +import time +from datetime import timedelta + +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() + writer = publisher.create_datawriter(topic) + + # Wait for Publisher 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; +} From 46c2f41cc4eba36bb5c584e76aa5e38ad423a307 Mon Sep 17 00:00:00 2001 From: David Pierret Date: Tue, 25 May 2021 23:30:28 +0200 Subject: [PATCH 02/55] add Sequences support manage sequences as list --- pyopendds/dev/include/pyopendds/user.hpp | 8 ++- pyopendds/dev/itl2py/CppOutput.py | 69 +++++++++++++++++------- pyopendds/dev/itl2py/PythonOutput.py | 4 +- pyopendds/dev/itl2py/ast.py | 3 ++ pyopendds/dev/itl2py/itl.py | 17 ++++-- pyopendds/dev/itl2py/templates/user.py | 1 + 6 files changed, 77 insertions(+), 25 deletions(-) diff --git a/pyopendds/dev/include/pyopendds/user.hpp b/pyopendds/dev/include/pyopendds/user.hpp index 387b457..5705b2b 100644 --- a/pyopendds/dev/include/pyopendds/user.hpp +++ b/pyopendds/dev/include/pyopendds/user.hpp @@ -254,11 +254,15 @@ class TopicType : public TopicTypeBase { Type::python_to_cpp(pysample, rv); DDS::ReturnCode_t rc = writer_impl->write(rv, DDS::HANDLE_NIL); - throw Exception( - "ERROR", Errors::PyOpenDDS_Error()); if (Errors::check_rc(rc)) { throw Exception(); } + // Wait for samples to be acknowledged + DDS::Duration_t timeout = { 30, 0 }; + if (writer_impl->wait_for_acknowledgments(timeout) != DDS::RETCODE_OK) { + throw Exception( + "wait_for_acknowledgments error : ", Errors::PyOpenDDS_Error()); + } return pysample; } diff --git a/pyopendds/dev/itl2py/CppOutput.py b/pyopendds/dev/itl2py/CppOutput.py index 0c7589e..5a71e25 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 from .Output import Output @@ -13,6 +13,8 @@ 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); else: raise NotImplementedError @@ -53,15 +55,32 @@ def visit_struct(self, struct_type): pyopendds_type = '' is_string = isinstance(field_node.type_node, PrimitiveType) and \ field_node.type_node.is_string() - - to_lines = [ - 'Type<{pyopendds_type}>::cpp_to_python(cpp.{field_name}', - '#ifdef CPP11_IDL', - ' ()', - '#endif', - ' , *field_value' - + (', "{default_encoding}"' if is_string else '') + ');', - ] + is_sequence = isinstance(field_node.type_node, SequenceType) + + if is_sequence: + to_lines = [ + 'Ref field_elem;', + 'field_value = PyList_New(0);', + 'for (int i = 0; i < cpp.{field_name}.length(); i++) {{', + ' {pyopendds_type} elem = cpp.{field_name}[i];', + ' field_elem = nullptr;', + ' Type<{pyopendds_type}>::cpp_to_python(elem', + ' #ifdef CPP11_IDL', + ' ()', + ' #endif', + ' , *field_elem' + (', "{default_encoding}"' if is_string else '') + ');', + ' PyList_Append(*field_value, *field_elem);', + '}}' + ] + else: + to_lines = [ + 'Type<{pyopendds_type}>::cpp_to_python(cpp.{field_name}', + '#ifdef CPP11_IDL', + ' ()', + '#endif', + ' , *field_value' + + (', "{default_encoding}"' if is_string else '') + ');', + ] from_lines = [ 'if (PyObject_HasAttrString(py, "{field_name}")) {{', @@ -83,14 +102,28 @@ def visit_struct(self, struct_type): ]) 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 '') + ');' - ]) + if is_sequence: + from_lines.extend([ + 'cpp.{field_name}.length(PyList_Size(*field_value));', + 'for (int i = 0; i < PyList_Size(*field_value); i++) {{', + ' ::ContTrajSegment elem = cpp.{field_name}[i];', + ' Type<{pyopendds_type}>::python_to_cpp(PyList_GetItem(*field_value, i), elem', + '#ifdef CPP11_IDL', + ' ()', + '#endif', + ' ' + (', "{default_encoding}"' if is_string else '') + ');', + ' cpp.{field_name}[i] = elem;', + '}}' + ]) + else: + 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 [''] + [ diff --git a/pyopendds/dev/itl2py/PythonOutput.py b/pyopendds/dev/itl2py/PythonOutput.py index 9efaf6a..ae624a2 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 from .Output import Output @@ -72,6 +72,8 @@ 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)' else: raise NotImplementedError(repr(field_type) + " is not supported") diff --git a/pyopendds/dev/itl2py/ast.py b/pyopendds/dev/itl2py/ast.py index 28a59a4..1e201cc 100644 --- a/pyopendds/dev/itl2py/ast.py +++ b/pyopendds/dev/itl2py/ast.py @@ -215,6 +215,9 @@ 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_' + self.base_type + '_' class NodeVisitor: diff --git a/pyopendds/dev/itl2py/itl.py b/pyopendds/dev/itl2py/itl.py index d447ee3..f248211 100644 --- a/pyopendds/dev/itl2py/itl.py +++ b/pyopendds/dev/itl2py/itl.py @@ -74,7 +74,7 @@ def parse_string(details): def parse_sequence(types, details): - base_type = parse_type(types, details["type"]) + base_type = parse_type(types, list(types)[0]) sequence_max_count = details.get("capacity", None) array_dimensions = details.get("size", None) if array_dimensions is not None: @@ -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 @@ -132,6 +139,8 @@ def parse_type(types, details): if details_type is str: if details in types: return types[details] + elif 'sequence' in details : + return parse_sequence(types, {'type':types, 'capacity': 1, 'size': None}) else: raise ValueError("Invalid Type: " + details) elif details_type is dict: diff --git a/pyopendds/dev/itl2py/templates/user.py b/pyopendds/dev/itl2py/templates/user.py index 63aaf37..0d4391d 100644 --- a/pyopendds/dev/itl2py/templates/user.py +++ b/pyopendds/dev/itl2py/templates/user.py @@ -1,5 +1,6 @@ {% if has_struct -%} from dataclasses import dataclass as _pyopendds_struct +from dataclasses import field {%- endif %} {% if has_enum -%} from enum import IntFlag as _pyopendds_enum From bb07ddcf31390c8e6fcbafc7e2ddc24dff7105a6 Mon Sep 17 00:00:00 2001 From: David Pierret Date: Mon, 31 May 2021 16:33:18 +0200 Subject: [PATCH 03/55] add Floating type support Signed-off-by: David Pierret --- pyopendds/dev/include/pyopendds/user.hpp | 33 ++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/pyopendds/dev/include/pyopendds/user.hpp b/pyopendds/dev/include/pyopendds/user.hpp index 387b457..528004c 100644 --- a/pyopendds/dev/include/pyopendds/user.hpp +++ b/pyopendds/dev/include/pyopendds/user.hpp @@ -123,6 +123,39 @@ typedef template<> class Type: public StringType {}; // TODO: Put Other String/Char Types Here +template +class FloatingType { +public: + typedef std::numeric_limits limits; + + static PyObject* get_python_class() + { + return PyFloat_FromDouble(0); + } + + static void cpp_to_python(const T& cpp, PyObject*& py) + { + py = PyFloat_FromDouble(cpp); + if (!py) throw Exception(); + } + + static void python_to_cpp(PyObject* py, T& cpp) + { + double value; + value = PyFloat_AsDouble(py); + if (value < limits::min() || 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; + } +}; + +typedef ::CORBA::Float f32; +typedef ::CORBA::Double f64; +template<> class Type: public FloatingType {}; +template<> class Type: public FloatingType {}; // TODO: FloatingType for floating point type class TopicTypeBase { From 5b999086e1431e02ad78efb633bf75b6503744cb Mon Sep 17 00:00:00 2001 From: David Pierret Date: Tue, 15 Jun 2021 15:43:32 +0200 Subject: [PATCH 04/55] fix after review Signed-off-by: David Pierret --- pyopendds/DataWriter.py | 2 +- pyopendds/DomainParticipant.py | 2 +- pyopendds/Publisher.py | 2 +- pyopendds/dev/include/pyopendds/user.hpp | 21 ++++++++++++++------- pyopendds/init_opendds.py | 1 - tests/basic_test/publisher.py | 2 +- 6 files changed, 18 insertions(+), 12 deletions(-) diff --git a/pyopendds/DataWriter.py b/pyopendds/DataWriter.py index c418265..ba8ff61 100644 --- a/pyopendds/DataWriter.py +++ b/pyopendds/DataWriter.py @@ -9,7 +9,7 @@ from .Publisher import Publisher -class DataWriter(object): +class DataWriter: def __init__(self, publisher: Publisher, topic: Topic, qos=None, listener=None): self.topic = topic diff --git a/pyopendds/DomainParticipant.py b/pyopendds/DomainParticipant.py index 64d9bd8..862b423 100644 --- a/pyopendds/DomainParticipant.py +++ b/pyopendds/DomainParticipant.py @@ -3,7 +3,7 @@ from .Publisher import Publisher -class DomainParticipant(object): +class DomainParticipant: def __init__(self, domain: int, qos=None, listener=None): self.domain = int(domain) diff --git a/pyopendds/Publisher.py b/pyopendds/Publisher.py index e3a65e1..8c19715 100644 --- a/pyopendds/Publisher.py +++ b/pyopendds/Publisher.py @@ -8,7 +8,7 @@ from .DomainParticipant import DomainParticipant -class Publisher(object): +class Publisher: def __init__(self, participant: DomainParticipant, qos=None, listener=None): participant.publishers.append(self) diff --git a/pyopendds/dev/include/pyopendds/user.hpp b/pyopendds/dev/include/pyopendds/user.hpp index 5705b2b..06b1a1a 100644 --- a/pyopendds/dev/include/pyopendds/user.hpp +++ b/pyopendds/dev/include/pyopendds/user.hpp @@ -45,12 +45,17 @@ class IntegerType { static void python_to_cpp(PyObject* py, T& cpp) { - long value; + LongType value; if (limits::is_signed) { - value = PyLong_AsLong(py); + value = PyLong_AsLong(py); } else { - value = PyLong_AsUnsignedLong(py); + 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 = T(value); } @@ -102,8 +107,10 @@ class StringType { static void python_to_cpp(PyObject* py, T& cpp, const char* encoding) { - PyObject* repr = PyObject_Repr(py); - PyObject* str = PyUnicode_AsEncodedString(repr, "utf-8", "~E~"); + 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); @@ -228,7 +235,7 @@ class TopicType : public TopicTypeBase { reader_impl->delete_readcondition(read_condition); IdlType sample; - DDS::SampleInfo info; + DDS::SampleInfo info; if (Errors::check_rc(reader_impl->take_next_sample(sample, info))) { throw Exception(); } @@ -242,7 +249,7 @@ class TopicType : public TopicTypeBase { PyObject* write(PyObject* pywriter, PyObject* pysample) { DDS::DataWriter* writer = get_capsule(pywriter); - if (!writer) PyErr_SetString(PyExc_Exception, "writer is a NULL pointer"); + if (!writer) throw Exception(); DataWriter* writer_impl = DataWriter::_narrow(writer); if (!writer_impl) { diff --git a/pyopendds/init_opendds.py b/pyopendds/init_opendds.py index 986d947..ecf1bec 100644 --- a/pyopendds/init_opendds.py +++ b/pyopendds/init_opendds.py @@ -27,6 +27,5 @@ def init_opendds(*args, raise ValueError('OpenDDS debug level must be between 0 and 10!') args.extend(['-DCPSDebugLevel', str(opendds_debug_level)]) - print (f" arguments = {args}\n") from _pyopendds import init_opendds_impl init_opendds_impl(*args, default_rtps=default_rtps) diff --git a/tests/basic_test/publisher.py b/tests/basic_test/publisher.py index ab09dfb..4bdb919 100644 --- a/tests/basic_test/publisher.py +++ b/tests/basic_test/publisher.py @@ -15,7 +15,7 @@ publisher = domain.create_publisher() writer = publisher.create_datawriter(topic) - # Wait for Publisher to Connect + # Wait for Subscriber to Connect print('Waiting for Subscriber...') writer.wait_for(StatusKind.PUBLICATION_MATCHED, timedelta(seconds=60)) print('Found subscriber!') From 900c777ec0a4afd6fc52292f2a67a5938d5818b3 Mon Sep 17 00:00:00 2001 From: David Pierret Date: Tue, 4 May 2021 13:06:56 +0200 Subject: [PATCH 05/55] Add publisher capability to pyopendds Add basic support for publisher know issue : Segfault after write() call Signed-off-by: David Pierret --- pyopendds/DataWriter.py | 31 ++++- pyopendds/DomainParticipant.py | 2 +- pyopendds/Publisher.py | 7 +- pyopendds/dev/include/pyopendds/user.hpp | 57 ++++++--- pyopendds/dev/itl2py/CppOutput.py | 30 ++++- pyopendds/dev/itl2py/PythonOutput.py | 2 +- pyopendds/dev/itl2py/templates/user.cpp | 37 +++++- pyopendds/ext/_pyopendds.cpp | 89 ++++++++++++++ pyopendds/init_opendds.py | 4 +- tests/basic_test/CMakeLists.txt | 7 ++ tests/basic_test/DataReaderListenerImpl.cpp | 93 ++++++++++++++ tests/basic_test/DataReaderListenerImpl.h | 48 ++++++++ tests/basic_test/publisher.py | 34 +++++ tests/basic_test/run_test.sh | 29 ++++- tests/basic_test/subscriber.cpp | 130 ++++++++++++++++++++ 15 files changed, 569 insertions(+), 31 deletions(-) create mode 100644 tests/basic_test/DataReaderListenerImpl.cpp create mode 100644 tests/basic_test/DataReaderListenerImpl.h create mode 100644 tests/basic_test/publisher.py create mode 100644 tests/basic_test/subscriber.cpp diff --git a/pyopendds/DataWriter.py b/pyopendds/DataWriter.py index a6beee8..c418265 100644 --- a/pyopendds/DataWriter.py +++ b/pyopendds/DataWriter.py @@ -1,2 +1,29 @@ -class DataWriter: - pass +from __future__ import annotations + +from .Topic import Topic +from .constants import StatusKind +from .util import TimeDurationType, normalize_time_duration + +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from .Publisher import Publisher + + +class DataWriter(object): + + def __init__(self, publisher: Publisher, topic: Topic, qos=None, listener=None): + self.topic = topic + self.qos = qos + self.listener = listener + self.publisher = publisher + publisher.writers.append(self) + + from _pyopendds import create_datawriter + create_datawriter(self, publisher, 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 write(self, sample): + return self.topic._ts_package.write(self, sample) diff --git a/pyopendds/DomainParticipant.py b/pyopendds/DomainParticipant.py index 862b423..64d9bd8 100644 --- a/pyopendds/DomainParticipant.py +++ b/pyopendds/DomainParticipant.py @@ -3,7 +3,7 @@ from .Publisher import Publisher -class DomainParticipant: +class DomainParticipant(object): def __init__(self, domain: int, qos=None, listener=None): self.domain = int(domain) diff --git a/pyopendds/Publisher.py b/pyopendds/Publisher.py index a292b85..e3a65e1 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,7 +8,7 @@ from .DomainParticipant import DomainParticipant -class Publisher: +class Publisher(object): def __init__(self, participant: DomainParticipant, qos=None, listener=None): participant.publishers.append(self) @@ -18,5 +19,5 @@ def __init__(self, participant: DomainParticipant, qos=None, listener=None): from _pyopendds import create_publisher create_publisher(self, participant) - def create_datawriter(self, topic: Topic, qos=None, listener=None): - pass + def create_datawriter(self, topic: Topic, qos=None, listener=None) -> DataWriter: + return DataWriter(self, topic, qos, listener) diff --git a/pyopendds/dev/include/pyopendds/user.hpp b/pyopendds/dev/include/pyopendds/user.hpp index aa7c7fd..387b457 100644 --- a/pyopendds/dev/include/pyopendds/user.hpp +++ b/pyopendds/dev/include/pyopendds/user.hpp @@ -30,7 +30,7 @@ class IntegerType { static PyObject* get_python_class() { - return PyLong_Type; + return PyLong_FromLong(0); } static void cpp_to_python(const T& cpp, PyObject*& py) @@ -45,24 +45,23 @@ class IntegerType { static void python_to_cpp(PyObject* py, T& cpp) { - LongType value; + long value; if (limits::is_signed) { - value = PyLong_AsLong(py); + value = PyLong_AsLong(py); } else { - value = PyLong_AsUnsignedLong(py); + 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; + cpp = T(value); } + }; typedef ::CORBA::Long i32; template<> class Type: public IntegerType {}; +typedef ::CORBA::Short i16; +template<> class Type: public IntegerType {}; + // TODO: Put Other Integer Types Here const char* string_data(const std::string& cpp) @@ -90,7 +89,7 @@ class StringType { public: static PyObject* get_python_class() { - return PyUnicode_Type; + return PyUnicode_FromString(""); } static void cpp_to_python(const T& cpp, PyObject*& py, const char* encoding) @@ -101,9 +100,14 @@ class StringType { py = o; } - static void python_to_cpp(PyObject* py, T& cpp) + static void python_to_cpp(PyObject* py, T& cpp, const char* encoding) { - // TODO: Encode or Throw Unicode Error + PyObject* repr = PyObject_Repr(py); + PyObject* str = PyUnicode_AsEncodedString(repr, "utf-8", "~E~"); + const char *bytes = PyBytes_AS_STRING(str); + cpp = T(bytes); + Py_XDECREF(repr); + Py_XDECREF(str); } }; @@ -127,6 +131,7 @@ class TopicTypeBase { 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; typedef std::shared_ptr Ptr; typedef std::map TopicTypes; @@ -215,7 +220,7 @@ class TopicType : public TopicTypeBase { DDS::WaitSet_var ws = new DDS::WaitSet; ws->attach_condition(read_condition); DDS::ConditionSeq active; - const DDS::Duration_t max_wait_time = {10, 0}; + const DDS::Duration_t max_wait_time = {60, 0}; if (Errors::check_rc(ws->wait(active, max_wait_time))) { throw Exception(); } @@ -234,6 +239,30 @@ class TopicType : public TopicTypeBase { return rv; } + PyObject* write(PyObject* pywriter, PyObject* pysample) + { + DDS::DataWriter* writer = get_capsule(pywriter); + if (!writer) PyErr_SetString(PyExc_Exception, "writer is a NULL pointer"); + + 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); + throw Exception( + "ERROR", Errors::PyOpenDDS_Error()); + if (Errors::check_rc(rc)) { + throw Exception(); + } + + return pysample; + } + PyObject* get_python_class() { return Type::get_python_class(); diff --git a/pyopendds/dev/itl2py/CppOutput.py b/pyopendds/dev/itl2py/CppOutput.py index b922eb0..0c7589e 100644 --- a/pyopendds/dev/itl2py/CppOutput.py +++ b/pyopendds/dev/itl2py/CppOutput.py @@ -44,7 +44,9 @@ 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 = [] @@ -61,16 +63,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 +128,6 @@ 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, }) diff --git a/pyopendds/dev/itl2py/PythonOutput.py b/pyopendds/dev/itl2py/PythonOutput.py index 42920d3..9efaf6a 100644 --- a/pyopendds/dev/itl2py/PythonOutput.py +++ b/pyopendds/dev/itl2py/PythonOutput.py @@ -98,4 +98,4 @@ def visit_enum(self, enum_type): dict(name=name, value=value) for name, value in enum_type.members.items() ], ), - )) + )) \ No newline at end of file diff --git a/pyopendds/dev/itl2py/templates/user.cpp b/pyopendds/dev/itl2py/templates/user.cpp index e57c9ca..fa5e882 100644 --- a/pyopendds/dev/itl2py/templates/user.cpp +++ b/pyopendds/dev/itl2py/templates/user.cpp @@ -59,7 +59,21 @@ class Type { static void python_to_cpp(PyObject* py, /*{{ type.cpp_name }}*/& cpp) { PyObject* cls = get_python_class(); - /*{{ type.from_lines | indent(4) }}*/ + /*{% if type.to_replace %}*/ + cpp = static_cast(PyLong_AsLong(py)); + /*{% else %}*/ + if (py) { + + if (PyObject_IsInstance(py, cls) != 1) { + throw Exception("Not a {{ type.py_name }}", PyExc_TypeError); + } + } else { + PyObject* args; + /*{{ type.new_lines | indent(6) }}*/ + py = PyObject_CallObject(cls, args); + } + /*{% if type.from_lines %}*//*{{ type.from_lines | indent(4) }}*//*{% endif %}*/ + /*{% endif %}*/ } }; @@ -119,9 +133,30 @@ 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++; + + // 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/ext/_pyopendds.cpp b/pyopendds/ext/_pyopendds.cpp index 0f16aaa..34b7785 100644 --- a/pyopendds/ext/_pyopendds.cpp +++ b/pyopendds/ext/_pyopendds.cpp @@ -366,6 +366,59 @@ PyObject* create_datareader(PyObject* self, PyObject* args) 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)); + writer = nullptr; + } +} + +/** + * create_datawriter(datawriter: DataWriter, publisher: Publisher, topic: Topic) -> None + */ +PyObject* create_datawriter(PyObject* self, PyObject* args) +{ + Ref pydatawriter; + Ref pypublisher; + Ref pytopic; + if (!PyArg_ParseTuple(args, "OOO", + &*pydatawriter, &*pypublisher, &*pytopic)) { + return nullptr; + } + pydatawriter++; + pypublisher++; + pytopic++; + + // 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 DataWriter + DDS::DataWriter* datawriter = publisher->create_datawriter( + topic, DATAWRITER_QOS_DEFAULT, 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, @@ -400,6 +453,40 @@ PyObject* datareader_wait_for(PyObject* self, PyObject* args) 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) return nullptr; + + // Wait + DDS::StatusCondition_var condition = writer->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; +} + /// Documentation for Internal Python Objects const char* internal_docstr = "Internal to PyOpenDDS, not for use directly!"; @@ -414,7 +501,9 @@ PyMethodDef pyopendds_Methods[] = { {"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} }; diff --git a/pyopendds/init_opendds.py b/pyopendds/init_opendds.py index 8c537ad..986d947 100644 --- a/pyopendds/init_opendds.py +++ b/pyopendds/init_opendds.py @@ -1,6 +1,7 @@ '''Manage the initialization of OpenDDS and related functionality. ''' +import sys def init_opendds(*args, default_rtps=True, @@ -19,12 +20,13 @@ def init_opendds(*args, 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)]) + print (f" arguments = {args}\n") from _pyopendds import init_opendds_impl init_opendds_impl(*args, default_rtps=default_rtps) diff --git a/tests/basic_test/CMakeLists.txt b/tests/basic_test/CMakeLists.txt index 4b8b942..a5a8bb9 100644 --- a/tests/basic_test/CMakeLists.txt +++ b/tests/basic_test/CMakeLists.txt @@ -23,3 +23,10 @@ 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/publisher.py b/tests/basic_test/publisher.py new file mode 100644 index 0000000..ab09dfb --- /dev/null +++ b/tests/basic_test/publisher.py @@ -0,0 +1,34 @@ +import sys +import time +from datetime import timedelta + +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() + writer = publisher.create_datawriter(topic) + + # Wait for Publisher 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; +} From da5df2ef0b6f32810602242f52f4e513fe1a3ff6 Mon Sep 17 00:00:00 2001 From: David Pierret Date: Tue, 25 May 2021 23:30:28 +0200 Subject: [PATCH 06/55] add Sequences support manage sequences as list --- pyopendds/dev/include/pyopendds/user.hpp | 8 ++- pyopendds/dev/itl2py/CppOutput.py | 69 +++++++++++++++++------- pyopendds/dev/itl2py/PythonOutput.py | 4 +- pyopendds/dev/itl2py/ast.py | 3 ++ pyopendds/dev/itl2py/itl.py | 17 ++++-- pyopendds/dev/itl2py/templates/user.py | 1 + 6 files changed, 77 insertions(+), 25 deletions(-) diff --git a/pyopendds/dev/include/pyopendds/user.hpp b/pyopendds/dev/include/pyopendds/user.hpp index 387b457..5705b2b 100644 --- a/pyopendds/dev/include/pyopendds/user.hpp +++ b/pyopendds/dev/include/pyopendds/user.hpp @@ -254,11 +254,15 @@ class TopicType : public TopicTypeBase { Type::python_to_cpp(pysample, rv); DDS::ReturnCode_t rc = writer_impl->write(rv, DDS::HANDLE_NIL); - throw Exception( - "ERROR", Errors::PyOpenDDS_Error()); if (Errors::check_rc(rc)) { throw Exception(); } + // Wait for samples to be acknowledged + DDS::Duration_t timeout = { 30, 0 }; + if (writer_impl->wait_for_acknowledgments(timeout) != DDS::RETCODE_OK) { + throw Exception( + "wait_for_acknowledgments error : ", Errors::PyOpenDDS_Error()); + } return pysample; } diff --git a/pyopendds/dev/itl2py/CppOutput.py b/pyopendds/dev/itl2py/CppOutput.py index 0c7589e..5a71e25 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 from .Output import Output @@ -13,6 +13,8 @@ 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); else: raise NotImplementedError @@ -53,15 +55,32 @@ def visit_struct(self, struct_type): pyopendds_type = '' is_string = isinstance(field_node.type_node, PrimitiveType) and \ field_node.type_node.is_string() - - to_lines = [ - 'Type<{pyopendds_type}>::cpp_to_python(cpp.{field_name}', - '#ifdef CPP11_IDL', - ' ()', - '#endif', - ' , *field_value' - + (', "{default_encoding}"' if is_string else '') + ');', - ] + is_sequence = isinstance(field_node.type_node, SequenceType) + + if is_sequence: + to_lines = [ + 'Ref field_elem;', + 'field_value = PyList_New(0);', + 'for (int i = 0; i < cpp.{field_name}.length(); i++) {{', + ' {pyopendds_type} elem = cpp.{field_name}[i];', + ' field_elem = nullptr;', + ' Type<{pyopendds_type}>::cpp_to_python(elem', + ' #ifdef CPP11_IDL', + ' ()', + ' #endif', + ' , *field_elem' + (', "{default_encoding}"' if is_string else '') + ');', + ' PyList_Append(*field_value, *field_elem);', + '}}' + ] + else: + to_lines = [ + 'Type<{pyopendds_type}>::cpp_to_python(cpp.{field_name}', + '#ifdef CPP11_IDL', + ' ()', + '#endif', + ' , *field_value' + + (', "{default_encoding}"' if is_string else '') + ');', + ] from_lines = [ 'if (PyObject_HasAttrString(py, "{field_name}")) {{', @@ -83,14 +102,28 @@ def visit_struct(self, struct_type): ]) 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 '') + ');' - ]) + if is_sequence: + from_lines.extend([ + 'cpp.{field_name}.length(PyList_Size(*field_value));', + 'for (int i = 0; i < PyList_Size(*field_value); i++) {{', + ' ::ContTrajSegment elem = cpp.{field_name}[i];', + ' Type<{pyopendds_type}>::python_to_cpp(PyList_GetItem(*field_value, i), elem', + '#ifdef CPP11_IDL', + ' ()', + '#endif', + ' ' + (', "{default_encoding}"' if is_string else '') + ');', + ' cpp.{field_name}[i] = elem;', + '}}' + ]) + else: + 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 [''] + [ diff --git a/pyopendds/dev/itl2py/PythonOutput.py b/pyopendds/dev/itl2py/PythonOutput.py index 9efaf6a..ae624a2 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 from .Output import Output @@ -72,6 +72,8 @@ 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)' else: raise NotImplementedError(repr(field_type) + " is not supported") diff --git a/pyopendds/dev/itl2py/ast.py b/pyopendds/dev/itl2py/ast.py index 28a59a4..1e201cc 100644 --- a/pyopendds/dev/itl2py/ast.py +++ b/pyopendds/dev/itl2py/ast.py @@ -215,6 +215,9 @@ 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_' + self.base_type + '_' class NodeVisitor: diff --git a/pyopendds/dev/itl2py/itl.py b/pyopendds/dev/itl2py/itl.py index d447ee3..f248211 100644 --- a/pyopendds/dev/itl2py/itl.py +++ b/pyopendds/dev/itl2py/itl.py @@ -74,7 +74,7 @@ def parse_string(details): def parse_sequence(types, details): - base_type = parse_type(types, details["type"]) + base_type = parse_type(types, list(types)[0]) sequence_max_count = details.get("capacity", None) array_dimensions = details.get("size", None) if array_dimensions is not None: @@ -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 @@ -132,6 +139,8 @@ def parse_type(types, details): if details_type is str: if details in types: return types[details] + elif 'sequence' in details : + return parse_sequence(types, {'type':types, 'capacity': 1, 'size': None}) else: raise ValueError("Invalid Type: " + details) elif details_type is dict: diff --git a/pyopendds/dev/itl2py/templates/user.py b/pyopendds/dev/itl2py/templates/user.py index 63aaf37..0d4391d 100644 --- a/pyopendds/dev/itl2py/templates/user.py +++ b/pyopendds/dev/itl2py/templates/user.py @@ -1,5 +1,6 @@ {% if has_struct -%} from dataclasses import dataclass as _pyopendds_struct +from dataclasses import field {%- endif %} {% if has_enum -%} from enum import IntFlag as _pyopendds_enum From 5773636577e8e652bad0d257c05e91968314ed62 Mon Sep 17 00:00:00 2001 From: David Pierret Date: Tue, 15 Jun 2021 15:43:32 +0200 Subject: [PATCH 07/55] fix after review Signed-off-by: David Pierret --- pyopendds/DataWriter.py | 2 +- pyopendds/DomainParticipant.py | 2 +- pyopendds/Publisher.py | 2 +- pyopendds/dev/include/pyopendds/user.hpp | 21 ++++++++++++++------- pyopendds/init_opendds.py | 1 - tests/basic_test/publisher.py | 2 +- 6 files changed, 18 insertions(+), 12 deletions(-) diff --git a/pyopendds/DataWriter.py b/pyopendds/DataWriter.py index c418265..ba8ff61 100644 --- a/pyopendds/DataWriter.py +++ b/pyopendds/DataWriter.py @@ -9,7 +9,7 @@ from .Publisher import Publisher -class DataWriter(object): +class DataWriter: def __init__(self, publisher: Publisher, topic: Topic, qos=None, listener=None): self.topic = topic diff --git a/pyopendds/DomainParticipant.py b/pyopendds/DomainParticipant.py index 64d9bd8..862b423 100644 --- a/pyopendds/DomainParticipant.py +++ b/pyopendds/DomainParticipant.py @@ -3,7 +3,7 @@ from .Publisher import Publisher -class DomainParticipant(object): +class DomainParticipant: def __init__(self, domain: int, qos=None, listener=None): self.domain = int(domain) diff --git a/pyopendds/Publisher.py b/pyopendds/Publisher.py index e3a65e1..8c19715 100644 --- a/pyopendds/Publisher.py +++ b/pyopendds/Publisher.py @@ -8,7 +8,7 @@ from .DomainParticipant import DomainParticipant -class Publisher(object): +class Publisher: def __init__(self, participant: DomainParticipant, qos=None, listener=None): participant.publishers.append(self) diff --git a/pyopendds/dev/include/pyopendds/user.hpp b/pyopendds/dev/include/pyopendds/user.hpp index 5705b2b..06b1a1a 100644 --- a/pyopendds/dev/include/pyopendds/user.hpp +++ b/pyopendds/dev/include/pyopendds/user.hpp @@ -45,12 +45,17 @@ class IntegerType { static void python_to_cpp(PyObject* py, T& cpp) { - long value; + LongType value; if (limits::is_signed) { - value = PyLong_AsLong(py); + value = PyLong_AsLong(py); } else { - value = PyLong_AsUnsignedLong(py); + 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 = T(value); } @@ -102,8 +107,10 @@ class StringType { static void python_to_cpp(PyObject* py, T& cpp, const char* encoding) { - PyObject* repr = PyObject_Repr(py); - PyObject* str = PyUnicode_AsEncodedString(repr, "utf-8", "~E~"); + 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); @@ -228,7 +235,7 @@ class TopicType : public TopicTypeBase { reader_impl->delete_readcondition(read_condition); IdlType sample; - DDS::SampleInfo info; + DDS::SampleInfo info; if (Errors::check_rc(reader_impl->take_next_sample(sample, info))) { throw Exception(); } @@ -242,7 +249,7 @@ class TopicType : public TopicTypeBase { PyObject* write(PyObject* pywriter, PyObject* pysample) { DDS::DataWriter* writer = get_capsule(pywriter); - if (!writer) PyErr_SetString(PyExc_Exception, "writer is a NULL pointer"); + if (!writer) throw Exception(); DataWriter* writer_impl = DataWriter::_narrow(writer); if (!writer_impl) { diff --git a/pyopendds/init_opendds.py b/pyopendds/init_opendds.py index 986d947..ecf1bec 100644 --- a/pyopendds/init_opendds.py +++ b/pyopendds/init_opendds.py @@ -27,6 +27,5 @@ def init_opendds(*args, raise ValueError('OpenDDS debug level must be between 0 and 10!') args.extend(['-DCPSDebugLevel', str(opendds_debug_level)]) - print (f" arguments = {args}\n") from _pyopendds import init_opendds_impl init_opendds_impl(*args, default_rtps=default_rtps) diff --git a/tests/basic_test/publisher.py b/tests/basic_test/publisher.py index ab09dfb..4bdb919 100644 --- a/tests/basic_test/publisher.py +++ b/tests/basic_test/publisher.py @@ -15,7 +15,7 @@ publisher = domain.create_publisher() writer = publisher.create_datawriter(topic) - # Wait for Publisher to Connect + # Wait for Subscriber to Connect print('Waiting for Subscriber...') writer.wait_for(StatusKind.PUBLICATION_MATCHED, timedelta(seconds=60)) print('Found subscriber!') From 7feb5b2006a48b2ecb89a4fd41f09147fe3377cb Mon Sep 17 00:00:00 2001 From: David Pierret Date: Mon, 31 May 2021 16:33:18 +0200 Subject: [PATCH 08/55] add Floating type support Signed-off-by: David Pierret --- pyopendds/dev/include/pyopendds/user.hpp | 33 ++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/pyopendds/dev/include/pyopendds/user.hpp b/pyopendds/dev/include/pyopendds/user.hpp index 06b1a1a..393b4d5 100644 --- a/pyopendds/dev/include/pyopendds/user.hpp +++ b/pyopendds/dev/include/pyopendds/user.hpp @@ -130,6 +130,39 @@ typedef template<> class Type: public StringType {}; // TODO: Put Other String/Char Types Here +template +class FloatingType { +public: + typedef std::numeric_limits limits; + + static PyObject* get_python_class() + { + return PyFloat_FromDouble(0); + } + + static void cpp_to_python(const T& cpp, PyObject*& py) + { + py = PyFloat_FromDouble(cpp); + if (!py) throw Exception(); + } + + static void python_to_cpp(PyObject* py, T& cpp) + { + double value; + value = PyFloat_AsDouble(py); + if (value < limits::min() || 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; + } +}; + +typedef ::CORBA::Float f32; +typedef ::CORBA::Double f64; +template<> class Type: public FloatingType {}; +template<> class Type: public FloatingType {}; // TODO: FloatingType for floating point type class TopicTypeBase { From 510b9a4200ebce1e6ec7b4b0dcd5edd554a0a8cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre-Etienne=20Sall=C3=A9?= Date: Thu, 17 Jun 2021 11:36:42 +0200 Subject: [PATCH 09/55] Add DataReaderListener for callback. --- pyopendds/DataReader.py | 9 +++- pyopendds/ext/_pyopendds.cpp | 97 ++++++++++++++++++++++++++++++++-- tests/basic_test/subscriber.py | 16 ++++-- 3 files changed, 113 insertions(+), 9 deletions(-) diff --git a/pyopendds/DataReader.py b/pyopendds/DataReader.py index f411ce7..502dc7f 100644 --- a/pyopendds/DataReader.py +++ b/pyopendds/DataReader.py @@ -18,8 +18,9 @@ def __init__(self, subscriber: Subscriber, topic: Topic, qos=None, listener=None self.subscriber = subscriber subscriber.readers.append(self) + print(str(listener)) from _pyopendds import create_datareader - create_datareader(self, subscriber, topic) + create_datareader(self, subscriber, topic, self.onDataAvailCallback) def wait_for(self, status: StatusKind, timeout: TimeDurationType): from _pyopendds import datareader_wait_for @@ -27,3 +28,9 @@ def wait_for(self, status: StatusKind, timeout: TimeDurationType): def take_next_sample(self): return self.topic._ts_package.take_next_sample(self) + + def onDataAvailCallback(self): + print("Python Callback") + sample = self.topic._ts_package.take_next_sample(self) + self.listener(sample) + diff --git a/pyopendds/ext/_pyopendds.cpp b/pyopendds/ext/_pyopendds.cpp index 34b7785..9b29ef9 100644 --- a/pyopendds/ext/_pyopendds.cpp +++ b/pyopendds/ext/_pyopendds.cpp @@ -17,6 +17,78 @@ PyObject* Errors::PyOpenDDS_Error_ = nullptr; 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 *arglist = NULL; + PyObject *result = NULL; + + PyGILState_STATE gstate; + gstate = PyGILState_Ensure(); + + if(PyCallable_Check(callable)) { + std::cerr << "function is callback" << std::endl; + //arglist = Py_BuildValue("()", null); + //result = PyEval_CallObject(callable, nullptr); + result = PyObject_CallFunctionObjArgs(callable,nullptr); + //Py_DECREF(arglist); + + if(result == NULL) + PyErr_Print(); + + Py_XDECREF(result); + } else { + std::cerr << "function is not a callback" << std::endl; + } + + Py_XDECREF(callable); + PyGILState_Release(gstate); +} /// Global Participant Factory DDS::DomainParticipantFactory_var participant_factory; @@ -326,15 +398,19 @@ void delete_datareader_var(PyObject* reader_capsule) } /** - * create_datareader(datareader: DataReader, subscriber: Subscriber, topic: Topic) -> None + * create_datareader(datareader: DataReader, subscriber: Subscriber, topic: Topic, listener: pyObject) -> None */ PyObject* create_datareader(PyObject* self, PyObject* args) { Ref pydatareader; Ref pysubscriber; Ref pytopic; - if (!PyArg_ParseTuple(args, "OOO", - &*pydatareader, &*pysubscriber, &*pytopic)) { + PyObject *pycallback; + + std::cout<<"create_datareader function" << std::endl; + + if (!PyArg_ParseTuple(args, "OOOO", + &*pydatareader, &*pysubscriber, &*pytopic, &pycallback)) { return nullptr; } pydatareader++; @@ -349,9 +425,22 @@ PyObject* create_datareader(PyObject* self, PyObject* args) 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 DataReader DDS::DataReader* datareader = subscriber->create_datareader( - topic, DATAREADER_QOS_DEFAULT, nullptr, + topic, DATAREADER_QOS_DEFAULT, listener, OpenDDS::DCPS::DEFAULT_STATUS_MASK); if (!datareader) { PyErr_SetString(Errors::PyOpenDDS_Error(), "Failed to Create DataReader"); diff --git a/tests/basic_test/subscriber.py b/tests/basic_test/subscriber.py index f5bb33b..0e22dd8 100644 --- a/tests/basic_test/subscriber.py +++ b/tests/basic_test/subscriber.py @@ -1,26 +1,34 @@ import sys +import time from datetime import timedelta from pyopendds import \ init_opendds, DomainParticipant, StatusKind, PyOpenDDS_Error from pybasic.basic import Reading + +def listener_func(sample: Reading): + print("main callback !") + print(sample) + + if __name__ == "__main__": try: # 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) + reader = subscriber.create_datareader(topic=topic, 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!') From 6e5c98af865861acb601dded68e3a4eae9b9d46b Mon Sep 17 00:00:00 2001 From: David Pierret Date: Thu, 24 Jun 2021 14:31:43 +0200 Subject: [PATCH 10/55] cleanup and more tests Signed-off-by: David Pierret --- pyopendds/DataReader.py | 15 +++- pyopendds/DomainParticipant.py | 2 +- pyopendds/dev/include/pyopendds/user.hpp | 9 +- pyopendds/ext/_pyopendds.cpp | 31 ++++--- tests/basic_test/DataReaderListenerImpl.cpp | 93 --------------------- tests/basic_test/DataReaderListenerImpl.h | 48 ----------- tests/basic_test/subscriber.py | 14 ++-- 7 files changed, 43 insertions(+), 169 deletions(-) delete mode 100644 tests/basic_test/DataReaderListenerImpl.cpp delete mode 100644 tests/basic_test/DataReaderListenerImpl.h diff --git a/pyopendds/DataReader.py b/pyopendds/DataReader.py index 502dc7f..18a1b24 100644 --- a/pyopendds/DataReader.py +++ b/pyopendds/DataReader.py @@ -1,5 +1,7 @@ from __future__ import annotations +import sys + from .Topic import Topic from .constants import StatusKind from .util import TimeDurationType, normalize_time_duration @@ -18,7 +20,6 @@ def __init__(self, subscriber: Subscriber, topic: Topic, qos=None, listener=None self.subscriber = subscriber subscriber.readers.append(self) - print(str(listener)) from _pyopendds import create_datareader create_datareader(self, subscriber, topic, self.onDataAvailCallback) @@ -30,7 +31,13 @@ def take_next_sample(self): return self.topic._ts_package.take_next_sample(self) def onDataAvailCallback(self): - print("Python Callback") - sample = self.topic._ts_package.take_next_sample(self) - self.listener(sample) + sample = None + if hasattr(self, 'topic'): + sample = self.take_next_sample() + else: + print("Error, no topic in self => " + self.__qualname__, file=sys.stderr) + if sample is not None: + self.listener(sample) + else: + print("Error, data not valid", file=sys.stderr) diff --git a/pyopendds/DomainParticipant.py b/pyopendds/DomainParticipant.py index 862b423..935f57c 100644 --- a/pyopendds/DomainParticipant.py +++ b/pyopendds/DomainParticipant.py @@ -22,7 +22,7 @@ def __del__(self): participant_cleanup(self) def create_topic(self, - name: str, topic_type: type, qos=None, listener=None) -> Topic: + 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: diff --git a/pyopendds/dev/include/pyopendds/user.hpp b/pyopendds/dev/include/pyopendds/user.hpp index 393b4d5..e97e3d2 100644 --- a/pyopendds/dev/include/pyopendds/user.hpp +++ b/pyopendds/dev/include/pyopendds/user.hpp @@ -274,7 +274,10 @@ class TopicType : public TopicTypeBase { } PyObject* rv = nullptr; - Type::cpp_to_python(sample, rv); + if (info.valid_data) + Type::cpp_to_python(sample, rv); + else + rv = Py_None; return rv; } @@ -295,7 +298,9 @@ class TopicType : public TopicTypeBase { DDS::ReturnCode_t rc = writer_impl->write(rv, DDS::HANDLE_NIL); if (Errors::check_rc(rc)) { - throw Exception(); + std::cerr << "write return error " << rc << std::endl; + throw Exception( + "WRITE ERROR", Errors::PyOpenDDS_Error()); } // Wait for samples to be acknowledged DDS::Duration_t timeout = { 30, 0 }; diff --git a/pyopendds/ext/_pyopendds.cpp b/pyopendds/ext/_pyopendds.cpp index 9b29ef9..b75f34a 100644 --- a/pyopendds/ext/_pyopendds.cpp +++ b/pyopendds/ext/_pyopendds.cpp @@ -65,28 +65,25 @@ void DataReaderListenerImpl::on_data_available(DDS::DataReader_ptr reader) { PyObject *callable = _callback; - //PyObject *arglist = NULL; PyObject *result = NULL; PyGILState_STATE gstate; gstate = PyGILState_Ensure(); - + try{ if(PyCallable_Check(callable)) { - std::cerr << "function is callback" << std::endl; - //arglist = Py_BuildValue("()", null); - //result = PyEval_CallObject(callable, nullptr); result = PyObject_CallFunctionObjArgs(callable,nullptr); - //Py_DECREF(arglist); - if(result == NULL) PyErr_Print(); - Py_XDECREF(result); } else { - std::cerr << "function is not a callback" << std::endl; + throw Exception("function is not a callback", PyExc_TypeError); } Py_XDECREF(callable); + } catch (Exception& e ) { + PyGILState_Release(gstate); + throw e; + } PyGILState_Release(gstate); } @@ -393,6 +390,9 @@ void delete_datareader_var(PyObject* reader_capsule) if (PyCapsule_CheckExact(reader_capsule)) { DDS::DataReader_var reader = static_cast( PyCapsule_GetPointer(reader_capsule, nullptr)); + DDS::DataReaderListener_ptr listener = DDS::DataReader::_narrow(reader)->get_listener(); + free(listener); + listener = nullptr; reader = nullptr; } } @@ -405,17 +405,18 @@ PyObject* create_datareader(PyObject* self, PyObject* args) Ref pydatareader; Ref pysubscriber; Ref pytopic; - PyObject *pycallback; + Ref pycallback; std::cout<<"create_datareader function" << std::endl; if (!PyArg_ParseTuple(args, "OOOO", - &*pydatareader, &*pysubscriber, &*pytopic, &pycallback)) { + &*pydatareader, &*pysubscriber, &*pytopic, &*pycallback)) { return nullptr; } pydatareader++; pysubscriber++; pytopic++; + pycallback++; // Get Subscriber DDS::Subscriber* subscriber = get_capsule(*pysubscriber); @@ -425,12 +426,10 @@ PyObject* create_datareader(PyObject* self, PyObject* args) 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); + 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); diff --git a/tests/basic_test/DataReaderListenerImpl.cpp b/tests/basic_test/DataReaderListenerImpl.cpp deleted file mode 100644 index a164871..0000000 --- a/tests/basic_test/DataReaderListenerImpl.cpp +++ /dev/null @@ -1,93 +0,0 @@ -/* - * - * - * 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 deleted file mode 100644 index 79955a5..0000000 --- a/tests/basic_test/DataReaderListenerImpl.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * - * - * 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/subscriber.py b/tests/basic_test/subscriber.py index 0e22dd8..f32c9ec 100644 --- a/tests/basic_test/subscriber.py +++ b/tests/basic_test/subscriber.py @@ -7,19 +7,23 @@ from pybasic.basic import Reading -def listener_func(sample: Reading): - print("main callback !") - print(sample) +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=10) domain = DomainParticipant(34) topic = domain.create_topic('Readings', Reading) subscriber = domain.create_subscriber() - reader = subscriber.create_datareader(topic=topic, listener=listener_func) + reader = subscriber.create_datareader(topic=topic, listener=listener.listener_func) # Wait for Publisher to Connect print('Waiting for Publisher...') @@ -32,5 +36,5 @@ def listener_func(sample: Reading): print('Done!') - except PyOpenDDS_Error as e: + except Exception as e: sys.exit(e) From 8a50cad6bb634e141940970bffee116cab2c9c66 Mon Sep 17 00:00:00 2001 From: David Pierret Date: Tue, 29 Jun 2021 13:12:10 +0200 Subject: [PATCH 11/55] add listener and fix publisher Signed-off-by: David Pierret --- pyopendds/dev/include/pyopendds/user.hpp | 15 +++------ pyopendds/dev/itl2py/templates/user.cpp | 1 + pyopendds/ext/_pyopendds.cpp | 22 ++++++------- tests/basic_test/CMakeLists.txt | 7 ----- tests/basic_test/publisher.py | 40 +++++++++++++++++++----- 5 files changed, 47 insertions(+), 38 deletions(-) diff --git a/pyopendds/dev/include/pyopendds/user.hpp b/pyopendds/dev/include/pyopendds/user.hpp index e97e3d2..8b0453f 100644 --- a/pyopendds/dev/include/pyopendds/user.hpp +++ b/pyopendds/dev/include/pyopendds/user.hpp @@ -289,27 +289,20 @@ class TopicType : public TopicTypeBase { DataWriter* writer_impl = DataWriter::_narrow(writer); if (!writer_impl) { - throw Exception( - "Could not narrow writer implementation", Errors::PyOpenDDS_Error()); + 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 (Errors::check_rc(rc)) { - std::cerr << "write return error " << rc << std::endl; + if (rc != DDS::RETCODE_OK) { throw Exception( "WRITE ERROR", Errors::PyOpenDDS_Error()); } - // Wait for samples to be acknowledged - DDS::Duration_t timeout = { 30, 0 }; - if (writer_impl->wait_for_acknowledgments(timeout) != DDS::RETCODE_OK) { - throw Exception( - "wait_for_acknowledgments error : ", Errors::PyOpenDDS_Error()); - } + if (Errors::check_rc(rc)) return nullptr; - return pysample; + return PyLong_FromLong(rc); } PyObject* get_python_class() diff --git a/pyopendds/dev/itl2py/templates/user.cpp b/pyopendds/dev/itl2py/templates/user.cpp index fa5e882..4a93fa0 100644 --- a/pyopendds/dev/itl2py/templates/user.cpp +++ b/pyopendds/dev/itl2py/templates/user.cpp @@ -139,6 +139,7 @@ PyObject* pywrite(PyObject* self, PyObject* args) 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"); diff --git a/pyopendds/ext/_pyopendds.cpp b/pyopendds/ext/_pyopendds.cpp index b75f34a..034076c 100644 --- a/pyopendds/ext/_pyopendds.cpp +++ b/pyopendds/ext/_pyopendds.cpp @@ -70,20 +70,20 @@ DataReaderListenerImpl::on_data_available(DDS::DataReader_ptr reader) 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("function is not a callback", PyExc_TypeError); - } - - Py_XDECREF(callable); + if(PyCallable_Check(callable)) { + result = PyObject_CallFunctionObjArgs(callable,nullptr); + if(result == NULL) + PyErr_Print(); + Py_XDECREF(result); + } else { + throw Exception("function is not a callback", PyExc_TypeError); + } } catch (Exception& e ) { + // Py_XDECREF(callable); PyGILState_Release(gstate); throw e; } + //Py_XDECREF(callable); PyGILState_Release(gstate); } @@ -407,8 +407,6 @@ PyObject* create_datareader(PyObject* self, PyObject* args) Ref pytopic; Ref pycallback; - std::cout<<"create_datareader function" << std::endl; - if (!PyArg_ParseTuple(args, "OOOO", &*pydatareader, &*pysubscriber, &*pytopic, &*pycallback)) { return nullptr; diff --git a/tests/basic_test/CMakeLists.txt b/tests/basic_test/CMakeLists.txt index a5a8bb9..4b8b942 100644 --- a/tests/basic_test/CMakeLists.txt +++ b/tests/basic_test/CMakeLists.txt @@ -23,10 +23,3 @@ 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/publisher.py b/tests/basic_test/publisher.py index 4bdb919..dc18922 100644 --- a/tests/basic_test/publisher.py +++ b/tests/basic_test/publisher.py @@ -10,25 +10,49 @@ try: # Initialize OpenDDS and Create DDS Entities init_opendds(opendds_debug_level=1) + time.sleep(1) domain = DomainParticipant(34) + time.sleep(1) topic = domain.create_topic('Readings', Reading) + time.sleep(1) publisher = domain.create_publisher() + time.sleep(1) writer = publisher.create_datawriter(topic) + time.sleep(1) # 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" + write_sample_speed = Reading() + write_sample_accel = Reading() + write_sample_dist = Reading() + while True: + time.sleep(1) + write_sample_speed.kind = ReadingKind.speed + write_sample_speed.value = 123 + write_sample_speed.where = "somewhere" + # Read and Print Sample + rc = writer.write(write_sample_speed) + print(rc) - time.sleep(1) - # Read and Print Sample - writer.write(sample) - print('Done!') + time.sleep(1) + write_sample_accel.kind = ReadingKind.acceleration + write_sample_accel.value = 2 + write_sample_accel.where = "everywhere" + # Read and Print Sample + rc = writer.write(write_sample_accel) + print(rc) + + time.sleep(1) + write_sample_dist.kind = ReadingKind.distance + write_sample_dist.value = 543 + write_sample_dist.where = "anywhere" + # Read and Print Sample + rc = writer.write(write_sample_dist) + print(rc) + print('Done!') except PyOpenDDS_Error as e: sys.exit(e) From 6c67cb7c583841359d9d206a13b59522e6bb4b08 Mon Sep 17 00:00:00 2001 From: David Pierret Date: Fri, 2 Jul 2021 08:53:33 +0200 Subject: [PATCH 12/55] add boolean type support Signed-off-by: David Pierret --- pyopendds/dev/include/pyopendds/user.hpp | 34 ++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/pyopendds/dev/include/pyopendds/user.hpp b/pyopendds/dev/include/pyopendds/user.hpp index 8b0453f..861a40f 100644 --- a/pyopendds/dev/include/pyopendds/user.hpp +++ b/pyopendds/dev/include/pyopendds/user.hpp @@ -22,6 +22,40 @@ class Type /*{ static void python_to_cpp(PyObject* py, T& cpp); }*/; + +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) + { + if (PyBool_Check(py)) { + throw Exception("Not a boolean", PyExc_ValueError); + } + if(py) { + cpp = true; + } else { + cpp = false; + } + } +}; + +//typedef ::CORBA::Boolean bool; +template<> class Type: public BooleanType {}; + template class IntegerType { public: From cdffefa72d64e6d7aef3d2825b802fa5b7cbc94d Mon Sep 17 00:00:00 2001 From: David Pierret Date: Fri, 2 Jul 2021 10:09:27 +0200 Subject: [PATCH 13/55] fix wrong type in itl2py Signed-off-by: David Pierret --- pyopendds/dev/include/pyopendds/user.hpp | 2 +- pyopendds/dev/itl2py/CppOutput.py | 125 ++++++++++++++--------- pyopendds/dev/itl2py/PythonOutput.py | 17 ++- pyopendds/dev/itl2py/ast.py | 6 +- pyopendds/dev/itl2py/itl.py | 5 +- pyopendds/dev/itl2py/templates/user.cpp | 5 +- pyopendds/dev/itl2py/templates/user.py | 7 ++ tests/basic_test/CMakeLists.txt | 12 +++ tests/basic_test/airbusdds.idl | 14 +++ tests/basic_test/basic.idl | 8 ++ tests/basic_test/publisher.py | 14 ++- tests/basic_test/subscriber.py | 2 +- 12 files changed, 160 insertions(+), 57 deletions(-) create mode 100644 tests/basic_test/airbusdds.idl diff --git a/pyopendds/dev/include/pyopendds/user.hpp b/pyopendds/dev/include/pyopendds/user.hpp index 861a40f..314c56f 100644 --- a/pyopendds/dev/include/pyopendds/user.hpp +++ b/pyopendds/dev/include/pyopendds/user.hpp @@ -79,7 +79,7 @@ class IntegerType { static void python_to_cpp(PyObject* py, T& cpp) { - LongType value; + int value; //todo: change to LongType if (limits::is_signed) { value = PyLong_AsLong(py); } else { diff --git a/pyopendds/dev/itl2py/CppOutput.py b/pyopendds/dev/itl2py/CppOutput.py index 5a71e25..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, SequenceType +from .ast import PrimitiveType, StructType, EnumType, SequenceType, ArrayType from .Output import Output @@ -15,6 +15,8 @@ def cpp_type_name(type_node): 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 @@ -57,30 +59,14 @@ def visit_struct(self, struct_type): field_node.type_node.is_string() is_sequence = isinstance(field_node.type_node, SequenceType) - if is_sequence: - to_lines = [ - 'Ref field_elem;', - 'field_value = PyList_New(0);', - 'for (int i = 0; i < cpp.{field_name}.length(); i++) {{', - ' {pyopendds_type} elem = cpp.{field_name}[i];', - ' field_elem = nullptr;', - ' Type<{pyopendds_type}>::cpp_to_python(elem', - ' #ifdef CPP11_IDL', - ' ()', - ' #endif', - ' , *field_elem' + (', "{default_encoding}"' if is_string else '') + ');', - ' PyList_Append(*field_value, *field_elem);', - '}}' - ] - else: - to_lines = [ - 'Type<{pyopendds_type}>::cpp_to_python(cpp.{field_name}', - '#ifdef CPP11_IDL', - ' ()', - '#endif', - ' , *field_value' - + (', "{default_encoding}"' if is_string else '') + ');', - ] + to_lines = [ + 'Type<{pyopendds_type}>::cpp_to_python(cpp.{field_name}', + '#ifdef CPP11_IDL', + ' ()', + '#endif', + ' , *field_value' + + (', "{default_encoding}"' if is_string else '') + ');', + ] from_lines = [ 'if (PyObject_HasAttrString(py, "{field_name}")) {{', @@ -102,28 +88,14 @@ def visit_struct(self, struct_type): ]) if from_lines: - if is_sequence: - from_lines.extend([ - 'cpp.{field_name}.length(PyList_Size(*field_value));', - 'for (int i = 0; i < PyList_Size(*field_value); i++) {{', - ' ::ContTrajSegment elem = cpp.{field_name}[i];', - ' Type<{pyopendds_type}>::python_to_cpp(PyList_GetItem(*field_value, i), elem', - '#ifdef CPP11_IDL', - ' ()', - '#endif', - ' ' + (', "{default_encoding}"' if is_string else '') + ');', - ' cpp.{field_name}[i] = elem;', - '}}' - ]) - else: - from_lines.extend([ - 'Type<{pyopendds_type}>::python_to_cpp(*field_value, cpp.{field_name}', - '#ifdef CPP11_IDL', - ' ()', - '#endif', - ' ' - + (', "{default_encoding}"' if is_string else '') + ');' - ]) + 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 [''] + [ @@ -164,3 +136,62 @@ def visit_enum(self, enum_type): '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 ae624a2..b57ca86 100644 --- a/pyopendds/dev/itl2py/PythonOutput.py +++ b/pyopendds/dev/itl2py/PythonOutput.py @@ -1,4 +1,4 @@ -from .ast import PrimitiveType, StructType, EnumType, SequenceType +from .ast import PrimitiveType, StructType, EnumType, SequenceType, ArrayType from .Output import Output @@ -74,6 +74,8 @@ def get_python_default_value_string(self, field_type): 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") @@ -100,4 +102,15 @@ def visit_enum(self, enum_type): dict(name=name, value=value) for name, value in enum_type.members.items() ], ), - )) \ No newline at end of file + )) + + 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 1e201cc..76d5d5b 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,6 +211,7 @@ 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) @@ -234,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 f248211..1ffb186 100644 --- a/pyopendds/dev/itl2py/itl.py +++ b/pyopendds/dev/itl2py/itl.py @@ -74,7 +74,7 @@ def parse_string(details): def parse_sequence(types, details): - base_type = parse_type(types, list(types)[0]) + base_type = parse_type(types, details["type"]) sequence_max_count = details.get("capacity", None) array_dimensions = details.get("size", None) if array_dimensions is not None: @@ -139,8 +139,6 @@ def parse_type(types, details): if details_type is str: if details in types: return types[details] - elif 'sequence' in details : - return parse_sequence(types, {'type':types, 'capacity': 1, 'size': None}) else: raise ValueError("Invalid Type: " + details) elif details_type is dict: @@ -157,3 +155,4 @@ def parse_itl(types, itl): # just use the first definition we found. if parsed_type.name.itl_name not in types: types[parsed_type.name.itl_name] = parsed_type + diff --git a/pyopendds/dev/itl2py/templates/user.cpp b/pyopendds/dev/itl2py/templates/user.cpp index 4a93fa0..9275491 100644 --- a/pyopendds/dev/itl2py/templates/user.cpp +++ b/pyopendds/dev/itl2py/templates/user.cpp @@ -43,6 +43,10 @@ class Type { /*{{ type.new_lines | indent(4) }}*/ py = PyObject_CallObject(cls, args); /*{% else %}*/ + /*{% if type.sequence %}*/ + if (py) Py_DECREF(py); + py = nullptr; + /*{% endif %}*/ if (py) { if (PyObject_IsInstance(cls, py) != 1) { throw Exception("Not a {{ type.py_name }}", PyExc_TypeError); @@ -63,7 +67,6 @@ class Type { cpp = static_cast(PyLong_AsLong(py)); /*{% else %}*/ if (py) { - if (PyObject_IsInstance(py, cls) != 1) { throw Exception("Not a {{ type.py_name }}", PyExc_TypeError); } diff --git a/pyopendds/dev/itl2py/templates/user.py b/pyopendds/dev/itl2py/templates/user.py index 0d4391d..6ca3313 100644 --- a/pyopendds/dev/itl2py/templates/user.py +++ b/pyopendds/dev/itl2py/templates/user.py @@ -5,7 +5,10 @@ {% if has_enum -%} from enum import IntFlag as _pyopendds_enum {%- endif %} +{% if has_sequence -%} +{%- endif %} {% for type in types -%} + {%- if type.struct %} @_pyopendds_struct @@ -21,6 +24,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/tests/basic_test/CMakeLists.txt b/tests/basic_test/CMakeLists.txt index 4b8b942..c370ca0 100644 --- a/tests/basic_test/CMakeLists.txt +++ b/tests/basic_test/CMakeLists.txt @@ -17,6 +17,18 @@ 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}) diff --git a/tests/basic_test/airbusdds.idl b/tests/basic_test/airbusdds.idl new file mode 100644 index 0000000..23ee831 --- /dev/null +++ b/tests/basic_test/airbusdds.idl @@ -0,0 +1,14 @@ +struct ContTrajSegment { + double altitude; + double CAStgt; + double centerLLat; + double centerLLong; +}; + + + +struct ContingencyTrajectory { + string dest; + sequence traj; + long trajIndex; +}; \ 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 index dc18922..9c8f51e 100644 --- a/tests/basic_test/publisher.py +++ b/tests/basic_test/publisher.py @@ -4,7 +4,7 @@ from pyopendds import \ init_opendds, DomainParticipant, StatusKind, PyOpenDDS_Error -from pybasic.basic import Reading, ReadingKind +from pybasic.basic import * if __name__ == "__main__": try: @@ -26,13 +26,23 @@ print('Found subscriber!') write_sample_speed = Reading() + s1 = Sample() + s1.value = 1 + s1.where = "toto1" + + s2 = Sample() + s2.value = 2 + s2.where = "toto2" + write_sample_accel = Reading() write_sample_dist = Reading() + while True: time.sleep(1) write_sample_speed.kind = ReadingKind.speed write_sample_speed.value = 123 write_sample_speed.where = "somewhere" + write_sample_speed.sampleSeq = seqSample([s1, s2]) # Read and Print Sample rc = writer.write(write_sample_speed) print(rc) @@ -41,6 +51,7 @@ write_sample_accel.kind = ReadingKind.acceleration write_sample_accel.value = 2 write_sample_accel.where = "everywhere" + write_sample_accel.sampleSeq = seqSample([s1, s2]) # Read and Print Sample rc = writer.write(write_sample_accel) print(rc) @@ -49,6 +60,7 @@ write_sample_dist.kind = ReadingKind.distance write_sample_dist.value = 543 write_sample_dist.where = "anywhere" + write_sample_dist.sampleSeq = seqSample([s1, s2]) # Read and Print Sample rc = writer.write(write_sample_dist) print(rc) diff --git a/tests/basic_test/subscriber.py b/tests/basic_test/subscriber.py index f32c9ec..6a3c49e 100644 --- a/tests/basic_test/subscriber.py +++ b/tests/basic_test/subscriber.py @@ -4,7 +4,7 @@ from pyopendds import \ init_opendds, DomainParticipant, StatusKind, PyOpenDDS_Error -from pybasic.basic import Reading +from pybasic.basic import * class TestClass: From 27cf7223043f93b99f63b8b80589c30387b2f629 Mon Sep 17 00:00:00 2001 From: David Pierret Date: Mon, 5 Jul 2021 17:16:12 +0200 Subject: [PATCH 14/55] Add Char type to IntegerType --- pyopendds/dev/include/pyopendds/user.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyopendds/dev/include/pyopendds/user.hpp b/pyopendds/dev/include/pyopendds/user.hpp index 314c56f..5e92bfc 100644 --- a/pyopendds/dev/include/pyopendds/user.hpp +++ b/pyopendds/dev/include/pyopendds/user.hpp @@ -101,6 +101,8 @@ template<> class Type: public IntegerType {}; typedef ::CORBA::Short i16; template<> class Type: public IntegerType {}; +typedef ::CORBA::Char c8; +template<> class Type: public IntegerType {}; // TODO: Put Other Integer Types Here const char* string_data(const std::string& cpp) From c9593f70bd7fc796d71d47fc31df808c0334f508 Mon Sep 17 00:00:00 2001 From: David Pierret Date: Fri, 9 Jul 2021 15:53:28 +0200 Subject: [PATCH 15/55] Add more supported types --- pyopendds/dev/include/pyopendds/user.hpp | 31 +++++++++++++++++++----- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/pyopendds/dev/include/pyopendds/user.hpp b/pyopendds/dev/include/pyopendds/user.hpp index 5e92bfc..d5d7348 100644 --- a/pyopendds/dev/include/pyopendds/user.hpp +++ b/pyopendds/dev/include/pyopendds/user.hpp @@ -70,20 +70,36 @@ class IntegerType { static void cpp_to_python(const T& cpp, PyObject*& py) { if (limits::is_signed) { - py = PyLong_FromLong(cpp); + if (sizeof(cpp) > sizeof(long)) { + py = PyLong_FromLongLong(cpp); + } else { + py = PyLong_FromLong(cpp); + } } else { - py = PyLong_FromUnsignedLong(cpp); + if (sizeof(cpp) > sizeof(long)) { + py = PyLong_FromUnsignedLongLong(cpp); + } else { + py = PyLong_FromUnsignedLong(cpp); + } } if (!py) throw Exception(); } static void python_to_cpp(PyObject* py, T& cpp) { - int value; //todo: change to LongType + T value; //todo: change to LongType if (limits::is_signed) { - value = PyLong_AsLong(py); + if (sizeof(cpp) > sizeof(long)) { + value = PyLong_AsLongLong(py); + } else { + value = PyLong_AsLong(py); + } } else { - value = PyLong_AsUnsignedLong(py); + if (sizeof(cpp) > sizeof(long)) { + value = PyLong_AsUnsignedLongLong(py); + } else { + value = PyLong_AsUnsignedLong(py); + } } if (value < limits::min() || value > limits::max()) { throw Exception( @@ -95,6 +111,9 @@ class IntegerType { }; +typedef ::CORBA::LongLong i64; +template<> class Type: public IntegerType {}; + typedef ::CORBA::Long i32; template<> class Type: public IntegerType {}; @@ -178,7 +197,7 @@ class FloatingType { static void cpp_to_python(const T& cpp, PyObject*& py) { - py = PyFloat_FromDouble(cpp); + py = PyFloat_FromDouble((double)cpp); if (!py) throw Exception(); } From 7046ac457d3cec45d7768b6172d9ed6ff2da65a6 Mon Sep 17 00:00:00 2001 From: David Pierret Date: Mon, 26 Jul 2021 14:32:57 +0200 Subject: [PATCH 16/55] partial Qos addition to pyOpenDDS Signed-off-by: David Pierret --- pyopendds/DataReader.py | 10 ++- pyopendds/DataWriter.py | 5 +- pyopendds/Publisher.py | 4 +- pyopendds/Qos.py | 49 +++++++++++ pyopendds/ext/_pyopendds.cpp | 149 +++++++++++++++++++++++++++++++++ tests/basic_test/airbusdds.idl | 14 ---- 6 files changed, 211 insertions(+), 20 deletions(-) create mode 100644 pyopendds/Qos.py delete mode 100644 tests/basic_test/airbusdds.idl diff --git a/pyopendds/DataReader.py b/pyopendds/DataReader.py index 18a1b24..2db34c2 100644 --- a/pyopendds/DataReader.py +++ b/pyopendds/DataReader.py @@ -15,7 +15,6 @@ class DataReader: def __init__(self, subscriber: Subscriber, topic: Topic, qos=None, listener=None): self.topic = topic - self.qos = qos self.listener = listener self.subscriber = subscriber subscriber.readers.append(self) @@ -32,12 +31,17 @@ def take_next_sample(self): def onDataAvailCallback(self): sample = None + #print(f"------ onDataAvailCallback") if hasattr(self, 'topic'): sample = self.take_next_sample() + #print(f"---------- Sample {sample}") else: - print("Error, no topic in self => " + self.__qualname__, file=sys.stderr) + print("------ Error, no topic in self => " + self.__qualname__) if sample is not None: self.listener(sample) else: - print("Error, data not valid", file=sys.stderr) + print("------ Error, data not valid") + def update_reader_qos(self, qos: DataReaderQos): + from _pyopendds import update_reader_qos + return update_reader_qos(self, qos) \ No newline at end of file diff --git a/pyopendds/DataWriter.py b/pyopendds/DataWriter.py index ba8ff61..3527112 100644 --- a/pyopendds/DataWriter.py +++ b/pyopendds/DataWriter.py @@ -13,7 +13,6 @@ class DataWriter: def __init__(self, publisher: Publisher, topic: Topic, qos=None, listener=None): self.topic = topic - self.qos = qos self.listener = listener self.publisher = publisher publisher.writers.append(self) @@ -27,3 +26,7 @@ def wait_for(self, status: StatusKind, timeout: TimeDurationType): def write(self, sample): return self.topic._ts_package.write(self, sample) + + def update_writer_qos(self, qos: DataWriterQos): + from _pyopendds import update_writer_qos + return update_writer_qos(self, qos) \ No newline at end of file diff --git a/pyopendds/Publisher.py b/pyopendds/Publisher.py index 8c19715..d5bc2d4 100644 --- a/pyopendds/Publisher.py +++ b/pyopendds/Publisher.py @@ -19,5 +19,5 @@ def __init__(self, participant: DomainParticipant, qos=None, listener=None): from _pyopendds import create_publisher create_publisher(self, participant) - def create_datawriter(self, topic: Topic, qos=None, listener=None) -> DataWriter: - return DataWriter(self, topic, qos, listener) + def create_datawriter(self, topic: Topic, listener=None) -> DataWriter: + return DataWriter(self, topic, listener) diff --git a/pyopendds/Qos.py b/pyopendds/Qos.py new file mode 100644 index 0000000..92f50fa --- /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.PERSISTENT_DURABILITY_QOS + + +class ReliabilityQosPolicy: + def __init__(self): + self.kind = ReliabilityQosPolicyKind.RELIABLE_RELIABILITY_QOS + self.max_blocking_time = 0 + + +class HistoryQosPolicy: + def __init__(self): + self.kind = HistoryQosPolicyKind.KEEP_LAST_HISTORY_QOS + self.depth = 0 + + +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/ext/_pyopendds.cpp b/pyopendds/ext/_pyopendds.cpp index 034076c..273ab86 100644 --- a/pyopendds/ext/_pyopendds.cpp +++ b/pyopendds/ext/_pyopendds.cpp @@ -573,6 +573,153 @@ PyObject* datawriter_wait_for(PyObject* self, PyObject* args) Py_RETURN_NONE; } +PyObject* update_writer_qos(PyObject* self, PyObject* args) +{ + Ref pydatawriter; + Ref pyQos; + + Ref pydurability; + Ref pyreliability; + Ref pyhistory; + Ref pydurabilityKind; + Ref pyreliabilityKind; + Ref pyhistoryKind; + + if (!PyArg_ParseTuple(args, "OO", + &*pydatawriter, &*pyQos)) { + return nullptr; + } + pydatawriter++; + pyQos++; + + // Get DataWriter + DDS::DataWriter* writer = get_capsule(*pydatawriter); + if (!writer) return nullptr; + + std::cerr << "get default qos" << std::endl; + // Create Qos for the data writer according to the spec + DDS::DataWriterQos qos; + writer->get_publisher()->get_default_datawriter_qos(qos); + + + std::cerr << "get durability" << std::endl; + pydurability = PyObject_GetAttrString(*pyQos, "durability"); + if (!pydurability) return nullptr; + pydurability ++; + + std::cerr << "get reliability" << std::endl; + pyreliability = PyObject_GetAttrString(*pyQos, "reliability"); + if (!pyreliability) return nullptr; + pyreliability ++; + + std::cerr << "get history" << std::endl; + pyhistory = PyObject_GetAttrString(*pyQos, "history"); + if (!pyhistory) return nullptr; + pyhistory ++; + + + std::cerr << "get dura kind" << std::endl; + pydurabilityKind = PyObject_GetAttrString(*pydurability, "kind"); + if (!pydurabilityKind) return nullptr; + 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 nullptr; + 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 nullptr; + pyhistoryKind ++; + + std::cerr << "AsLong" << std::endl; + qos.history.kind = (DDS::HistoryQosPolicyKind) PyLong_AsLong(*pyhistoryKind); + + std::cerr << "set QOS" << std::endl; + writer->set_qos (qos); + + std::cerr << "return" << std::endl; + Py_RETURN_NONE; +} + +PyObject* update_reader_qos(PyObject* self, PyObject* args) +{ + Ref pydatareader; + Ref pyQos; + + Ref pydurability; + Ref pyreliability; + Ref pyhistory; + Ref pydurabilityKind; + Ref pyreliabilityKind; + Ref pyhistoryKind; + Ref pyhistorydepth; + Ref pyreliabilitymax; + + if (!PyArg_ParseTuple(args, "OO", + &*pydatareader, &*pyQos)) { + return nullptr; + } + pydatareader++; + pyQos++; + + // Get DataReader + DDS::DataReader* reader = get_capsule(*pydatareader); + if (!reader) return nullptr; + + // Create Qos for the data writer according to the spec + DDS::DataReaderQos qos; + reader->get_subscriber()->get_default_datareader_qos(qos); + + pydurability = PyObject_GetAttrString(*pyQos, "durability"); + if (!pydurability) return nullptr; + pydurability ++; + + pyreliability = PyObject_GetAttrString(*pyQos, "reliability"); + if (!pyreliability) return nullptr; + pyreliability ++; + + pyhistory = PyObject_GetAttrString(*pyQos, "history"); + if (!pyhistory) return nullptr; + pyhistory ++; + + + pydurabilityKind = PyObject_GetAttrString(*pydurability, "kind"); + if (!pydurabilityKind) return nullptr; + pydurabilityKind ++; + qos.durability.kind = (DDS::DurabilityQosPolicyKind) PyLong_AsLong(*pydurabilityKind); + + pyreliabilityKind = PyObject_GetAttrString(*pyreliability, "kind"); + if (!pyreliabilityKind) return nullptr; + pyreliabilityKind ++; + qos.reliability.kind = (DDS::ReliabilityQosPolicyKind) PyLong_AsLong(*pyreliabilityKind); + + pyreliabilitymax = PyObject_GetAttrString(*pyreliability, "max_blocking_time"); + if (!pyreliabilitymax) return nullptr; + pyreliabilitymax ++; + qos.history.depth = PyLong_AsLong(*pyreliabilitymax); + + + pyhistoryKind = PyObject_GetAttrString(*pyhistory, "kind"); + if (!pyhistoryKind) return nullptr; + pyhistoryKind ++; + + qos.history.kind = (DDS::HistoryQosPolicyKind) PyLong_AsLong(*pyhistoryKind); + + pyhistorydepth = PyObject_GetAttrString(*pyhistory, "depth"); + if (!pyhistorydepth) return nullptr; + pyhistorydepth ++; + qos.history.depth = PyLong_AsLong(*pyhistorydepth); + + reader->set_qos (qos); + Py_RETURN_NONE; +} + /// Documentation for Internal Python Objects const char* internal_docstr = "Internal to PyOpenDDS, not for use directly!"; @@ -590,6 +737,8 @@ PyMethodDef pyopendds_Methods[] = { {"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}, + {"update_writer_qos", update_writer_qos, METH_VARARGS, internal_docstr}, + {"update_reader_qos", update_reader_qos, METH_VARARGS, internal_docstr}, {nullptr, nullptr, 0, nullptr} }; diff --git a/tests/basic_test/airbusdds.idl b/tests/basic_test/airbusdds.idl deleted file mode 100644 index 23ee831..0000000 --- a/tests/basic_test/airbusdds.idl +++ /dev/null @@ -1,14 +0,0 @@ -struct ContTrajSegment { - double altitude; - double CAStgt; - double centerLLat; - double centerLLong; -}; - - - -struct ContingencyTrajectory { - string dest; - sequence traj; - long trajIndex; -}; \ No newline at end of file From b5f84f34949395a2a541ad48a210d1ea9b915a9b Mon Sep 17 00:00:00 2001 From: Andrea Ruffino Date: Mon, 27 Sep 2021 10:35:05 +0200 Subject: [PATCH 17/55] Minor bug fix: - Fix cpp issue where topic_types_ private member was accessed by derived class. - Fix DataWriter python class issue where the datawriter used datareader_wait_for cpp binding. --- pyopendds/DataWriter.py | 4 ++-- pyopendds/dev/include/pyopendds/user.hpp | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyopendds/DataWriter.py b/pyopendds/DataWriter.py index 3527112..6b9b133 100644 --- a/pyopendds/DataWriter.py +++ b/pyopendds/DataWriter.py @@ -22,11 +22,11 @@ def __init__(self, publisher: Publisher, topic: Topic, qos=None, listener=None): def wait_for(self, status: StatusKind, timeout: TimeDurationType): from _pyopendds import datareader_wait_for - return datareader_wait_for(self, status, *normalize_time_duration(timeout)) + datawriter_wait_for(self, status, *normalize_time_duration(timeout)) def write(self, sample): return self.topic._ts_package.write(self, sample) def update_writer_qos(self, qos: DataWriterQos): from _pyopendds import update_writer_qos - return update_writer_qos(self, qos) \ No newline at end of file + return update_writer_qos(self, qos) diff --git a/pyopendds/dev/include/pyopendds/user.hpp b/pyopendds/dev/include/pyopendds/user.hpp index d5d7348..6568845 100644 --- a/pyopendds/dev/include/pyopendds/user.hpp +++ b/pyopendds/dev/include/pyopendds/user.hpp @@ -53,8 +53,8 @@ class BooleanType { } }; -//typedef ::CORBA::Boolean bool; -template<> class Type: public BooleanType {}; +typedef ::CORBA::Boolean b; +template<> class Type: public BooleanType {}; template class IntegerType { @@ -240,7 +240,7 @@ class TopicTypeBase { return i->second.get(); } -private: +protected: static TopicTypes topic_types_; }; From 1244d6a585242bf68c3d47c7b542a7625f096a5f Mon Sep 17 00:00:00 2001 From: Andrea Ruffino Date: Mon, 27 Sep 2021 10:44:41 +0200 Subject: [PATCH 18/55] Implementation improvements: - DataWriter and Publisher classes should not have a listener member. - QoS still can be passed, but QOS_DEFAULTs are always used in cpp binding methods. --- pyopendds/DataReader.py | 39 +++++++++++++++------------------- pyopendds/DataWriter.py | 18 +++++++++------- pyopendds/DomainParticipant.py | 4 ++-- pyopendds/Publisher.py | 9 ++++---- pyopendds/Subscriber.py | 6 ++++-- 5 files changed, 38 insertions(+), 38 deletions(-) diff --git a/pyopendds/DataReader.py b/pyopendds/DataReader.py index 2db34c2..0be916d 100644 --- a/pyopendds/DataReader.py +++ b/pyopendds/DataReader.py @@ -1,47 +1,42 @@ from __future__ import annotations -import sys - from .Topic import Topic from .constants import StatusKind from .util import TimeDurationType, normalize_time_duration -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable, Optional 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=None, listener: Optional[Callable[..., None]] = None): self.topic = topic self.listener = listener self.subscriber = subscriber subscriber.readers.append(self) from _pyopendds import create_datareader - create_datareader(self, subscriber, topic, self.onDataAvailCallback) + create_datareader(self, subscriber, topic, self.on_data_available_callback) + self.update_qos(qos) + + def update_qos(self, qos: DataReaderQos): + # from _pyopendds import update_reader_qos + # return update_reader_qos(self, qos) + print("DataReader.update_qos() not implemented") + pass - def wait_for(self, status: StatusKind, timeout: TimeDurationType): + def wait_for(self, timeout: TimeDurationType, status: StatusKind = StatusKind.SUBSCRIPTION_MATCHED): from _pyopendds import datareader_wait_for - return datareader_wait_for(self, status, *normalize_time_duration(timeout)) + datareader_wait_for(self, status, *normalize_time_duration(timeout)) def take_next_sample(self): return self.topic._ts_package.take_next_sample(self) - def onDataAvailCallback(self): - sample = None - #print(f"------ onDataAvailCallback") - if hasattr(self, 'topic'): - sample = self.take_next_sample() - #print(f"---------- Sample {sample}") - else: - print("------ Error, no topic in self => " + self.__qualname__) - if sample is not None: + def on_data_available_callback(self): + sample = self.take_next_sample() + if sample is None: + print("on_data_available_callback error: sample is None") + elif self.listener is not None: self.listener(sample) - else: - print("------ Error, data not valid") - - def update_reader_qos(self, qos: DataReaderQos): - from _pyopendds import update_reader_qos - return update_reader_qos(self, qos) \ No newline at end of file diff --git a/pyopendds/DataWriter.py b/pyopendds/DataWriter.py index 6b9b133..e71646b 100644 --- a/pyopendds/DataWriter.py +++ b/pyopendds/DataWriter.py @@ -11,22 +11,24 @@ class DataWriter: - def __init__(self, publisher: Publisher, topic: Topic, qos=None, listener=None): + def __init__(self, publisher: Publisher, topic: Topic, qos=None): self.topic = topic - self.listener = listener self.publisher = publisher publisher.writers.append(self) from _pyopendds import create_datawriter create_datawriter(self, publisher, topic) + self.update_qos(qos) - def wait_for(self, status: StatusKind, timeout: TimeDurationType): - from _pyopendds import datareader_wait_for + def update_qos(self, qos: DataWriterQos): + # from _pyopendds import update_writer_qos + # return update_writer_qos(self, qos) + print("DataWriterr.update_qos() not implemented") + pass + + def wait_for(self, timeout: TimeDurationType, status: StatusKind = StatusKind.PUBLICATION_MATCHED): + from _pyopendds import datawriter_wait_for datawriter_wait_for(self, status, *normalize_time_duration(timeout)) def write(self, sample): return self.topic._ts_package.write(self, sample) - - def update_writer_qos(self, qos: DataWriterQos): - from _pyopendds import update_writer_qos - return update_writer_qos(self, qos) diff --git a/pyopendds/DomainParticipant.py b/pyopendds/DomainParticipant.py index 935f57c..00cb19e 100644 --- a/pyopendds/DomainParticipant.py +++ b/pyopendds/DomainParticipant.py @@ -28,5 +28,5 @@ def create_topic(self, 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 d5bc2d4..f90643f 100644 --- a/pyopendds/Publisher.py +++ b/pyopendds/Publisher.py @@ -10,14 +10,15 @@ class Publisher: - 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 create_publisher(self, participant) - def create_datawriter(self, topic: Topic, listener=None) -> DataWriter: - return DataWriter(self, topic, listener) + def create_datawriter(self, topic: Topic, qos=None) -> DataWriter: + writer = DataWriter(self, topic, qos) + self.writers.append(writer) + return writer diff --git a/pyopendds/Subscriber.py b/pyopendds/Subscriber.py index 25ee2d3..c37f438 100644 --- a/pyopendds/Subscriber.py +++ b/pyopendds/Subscriber.py @@ -19,5 +19,7 @@ def __init__(self, participant: DomainParticipant, qos=None, listener=None): from _pyopendds import create_subscriber 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 From 39c7556f2e0a2e5320bdf4f0614fe2ecf741ede6 Mon Sep 17 00:00:00 2001 From: Andrea Ruffino Date: Mon, 27 Sep 2021 10:47:06 +0200 Subject: [PATCH 19/55] Fix setup.cfg warning: - Dash separation in label will be deprecated, use underscore instead --- setup.cfg | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) 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 From ff63d6d765db683816d33f0114a55b7fdfb77b08 Mon Sep 17 00:00:00 2001 From: Andrea Ruffino Date: Mon, 27 Sep 2021 10:58:34 +0200 Subject: [PATCH 20/55] Fix Segmentation fault runtime errors: - DataWriter and DataReader wait_for methods does not use WaitSet dispatch. A simple while loop is used to check status conditions, and the STL current thread sleep is used to wait. - DataRead take_next_sample does not perform his own wait but try immediately perform library call to take_next_sample. --- pyopendds/dev/include/pyopendds/user.hpp | 40 +++++++----- pyopendds/ext/_pyopendds.cpp | 81 +++++++++++++++++++----- 2 files changed, 89 insertions(+), 32 deletions(-) diff --git a/pyopendds/dev/include/pyopendds/user.hpp b/pyopendds/dev/include/pyopendds/user.hpp index 6568845..84a29cc 100644 --- a/pyopendds/dev/include/pyopendds/user.hpp +++ b/pyopendds/dev/include/pyopendds/user.hpp @@ -215,10 +215,10 @@ class FloatingType { }; typedef ::CORBA::Float f32; -typedef ::CORBA::Double f64; template<> class Type: public FloatingType {}; + +typedef ::CORBA::Double f64; template<> class Type: public FloatingType {}; -// TODO: FloatingType for floating point type class TopicTypeBase { public: @@ -310,22 +310,28 @@ class TopicType : public TopicTypeBase { "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 = {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); + // 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); + + // TODO: fallback to naive implementation IdlType sample; - DDS::SampleInfo info; - if (Errors::check_rc(reader_impl->take_next_sample(sample, info))) { - throw Exception(); + DDS::SampleInfo info; + DDS::ReturnCode_t rc = reader_impl->take_next_sample(sample, info); + if (rc != DDS::RETCODE_OK) { + PyErr_SetString(Errors::PyOpenDDS_Error(), "reader_impl->take_next_sample() failed"); + return nullptr; } PyObject* rv = nullptr; @@ -355,7 +361,7 @@ class TopicType : public TopicTypeBase { throw Exception( "WRITE ERROR", Errors::PyOpenDDS_Error()); } - if (Errors::check_rc(rc)) return nullptr; +// if (Errors::check_rc(rc)) return nullptr; return PyLong_FromLong(rc); } diff --git a/pyopendds/ext/_pyopendds.cpp b/pyopendds/ext/_pyopendds.cpp index 273ab86..cc141a5 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; @@ -432,7 +435,7 @@ PyObject* create_datareader(PyObject* self, PyObject* args) else { throw Exception("Callback provided is not a callable object", PyExc_TypeError); } - + } // Create DataReader @@ -524,18 +527,42 @@ PyObject* datareader_wait_for(PyObject* self, PyObject* args) // Get DataReader DDS::DataReader* reader = get_capsule(*pydatareader); - if (!reader) return nullptr; + 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); - 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; + // TODO: wait() causes segmentation fault +// 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; + + // 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::seconds(1)); + t_now = std::chrono::steady_clock::now(); + } Py_RETURN_NONE; } @@ -558,18 +585,42 @@ PyObject* datawriter_wait_for(PyObject* self, PyObject* args) // Get DataWriter DDS::DataWriter* writer = get_capsule(*pydatawriter); - if (!writer) return nullptr; + 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); - 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; + // TODO: wait() causes segmentation fault +// 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; + + // 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::seconds(1)); + t_now = std::chrono::steady_clock::now(); + } Py_RETURN_NONE; } From b712b3e67848fdb9a73ec5f859b4db165c13fc77 Mon Sep 17 00:00:00 2001 From: Andrea Ruffino Date: Mon, 27 Sep 2021 11:02:49 +0200 Subject: [PATCH 21/55] Fix an error where negative python floats in Samples causes crash: - When checking limits of floating value with cpp STL library, min() returns the minimum positive representable value, lowest() must be used instead. --- pyopendds/dev/include/pyopendds/user.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyopendds/dev/include/pyopendds/user.hpp b/pyopendds/dev/include/pyopendds/user.hpp index 84a29cc..234ee7d 100644 --- a/pyopendds/dev/include/pyopendds/user.hpp +++ b/pyopendds/dev/include/pyopendds/user.hpp @@ -101,7 +101,7 @@ class IntegerType { value = PyLong_AsUnsignedLong(py); } } - if (value < limits::min() || value > limits::max()) { + if (value < limits::lowest() || value > limits::max()) { throw Exception( "Integer Value is Out of Range for IDL Type", PyExc_ValueError); } @@ -192,7 +192,7 @@ class FloatingType { static PyObject* get_python_class() { - return PyFloat_FromDouble(0); + return PyFloat_FromDouble(0.0); } static void cpp_to_python(const T& cpp, PyObject*& py) @@ -205,7 +205,7 @@ class FloatingType { { double value; value = PyFloat_AsDouble(py); - if (value < limits::min() || value > limits::max()) { + if (value < limits::lowest() || value > limits::max()) { throw Exception( "Floating Value is Out of Range for IDL Type", PyExc_ValueError); } From 3de40850be439e70a3f707de8d830a2d4d74a1c4 Mon Sep 17 00:00:00 2001 From: Andrea Ruffino Date: Mon, 27 Sep 2021 16:03:33 +0200 Subject: [PATCH 22/55] Add support for nested IDL modules: - Each supported type defined in IDL imports now the whole module name at once (importing the root and browsing into submodules resulted into a NULL value and an exception). --- pyopendds/dev/itl2py/ast.py | 2 +- pyopendds/dev/itl2py/templates/user.cpp | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/pyopendds/dev/itl2py/ast.py b/pyopendds/dev/itl2py/ast.py index 76d5d5b..3ea9435 100644 --- a/pyopendds/dev/itl2py/ast.py +++ b/pyopendds/dev/itl2py/ast.py @@ -219,7 +219,7 @@ def __repr__(self): def repr_name(self): if self.name: - return '::' + self.name.join('::') + '::_tao_seq_' + self.base_type + '_' + return '::' + self.name.join('::') + '::_tao_seq_' + repr(self.base_type) + '_' class NodeVisitor: diff --git a/pyopendds/dev/itl2py/templates/user.cpp b/pyopendds/dev/itl2py/templates/user.cpp index 9275491..562bd76 100644 --- a/pyopendds/dev/itl2py/templates/user.cpp +++ b/pyopendds/dev/itl2py/templates/user.cpp @@ -20,14 +20,9 @@ class Type { { PyObject* python_class = nullptr; if (!python_class) { - Ref module = PyImport_ImportModule("/*{{ package_name }}*/"); + Ref module = PyImport_ImportModule("/*{{ package_name }}*//*{% for name in type.name_parts -%}*/./*{{name}}*//*{%- endfor %}*/"); if (!module) throw Exception(); - /*{% for name in type.name_parts -%}*/ - module = PyObject_GetAttrString(*module, "/*{{ name }}*/"); - if (!module) throw Exception(); - /*{%- endfor %}*/ - python_class = PyObject_GetAttrString(*module, "/*{{ type.local_name }}*/"); if (!python_class) throw Exception(); } From 573ffb99d18e374bc1355f08b357d3f07842ab05 Mon Sep 17 00:00:00 2001 From: Andrea Ruffino Date: Mon, 27 Sep 2021 16:07:08 +0200 Subject: [PATCH 23/55] Minor code refactoring --- pyopendds/DataReader.py | 7 +++++-- pyopendds/DataWriter.py | 4 +++- pyopendds/DomainParticipant.py | 3 +-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/pyopendds/DataReader.py b/pyopendds/DataReader.py index 0be916d..4f4bf22 100644 --- a/pyopendds/DataReader.py +++ b/pyopendds/DataReader.py @@ -15,6 +15,7 @@ def __init__(self, subscriber: Subscriber, topic: Topic, qos=None, listener: Opt self.topic = topic self.listener = listener self.subscriber = subscriber + self.qos = qos subscriber.readers.append(self) from _pyopendds import create_datareader @@ -24,7 +25,8 @@ def __init__(self, subscriber: Subscriber, topic: Topic, qos=None, listener: Opt def update_qos(self, qos: DataReaderQos): # from _pyopendds import update_reader_qos # return update_reader_qos(self, qos) - print("DataReader.update_qos() not implemented") + + # print("DataReader.update_qos() not implemented") pass def wait_for(self, timeout: TimeDurationType, status: StatusKind = StatusKind.SUBSCRIPTION_MATCHED): @@ -37,6 +39,7 @@ def take_next_sample(self): def on_data_available_callback(self): sample = self.take_next_sample() if sample is None: - print("on_data_available_callback error: sample is None") + # print("on_data_available_callback error: sample is None") + pass elif self.listener is not None: self.listener(sample) diff --git a/pyopendds/DataWriter.py b/pyopendds/DataWriter.py index e71646b..ed75d28 100644 --- a/pyopendds/DataWriter.py +++ b/pyopendds/DataWriter.py @@ -14,6 +14,7 @@ class DataWriter: def __init__(self, publisher: Publisher, topic: Topic, qos=None): self.topic = topic self.publisher = publisher + self.qos = qos publisher.writers.append(self) from _pyopendds import create_datawriter @@ -23,7 +24,8 @@ def __init__(self, publisher: Publisher, topic: Topic, qos=None): def update_qos(self, qos: DataWriterQos): # from _pyopendds import update_writer_qos # return update_writer_qos(self, qos) - print("DataWriterr.update_qos() not implemented") + + # print("DataWriterr.update_qos() not implemented") pass def wait_for(self, timeout: TimeDurationType, status: StatusKind = StatusKind.PUBLICATION_MATCHED): diff --git a/pyopendds/DomainParticipant.py b/pyopendds/DomainParticipant.py index 00cb19e..8b7c513 100644 --- a/pyopendds/DomainParticipant.py +++ b/pyopendds/DomainParticipant.py @@ -1,6 +1,7 @@ from .Topic import Topic from .Subscriber import Subscriber from .Publisher import Publisher +from _pyopendds import participant_cleanup, create_participant class DomainParticipant: @@ -14,11 +15,9 @@ def __init__(self, domain: int, qos=None, listener=None): self.publishers = [] self._registered_typesupport = [] - from _pyopendds import create_participant create_participant(self, domain) def __del__(self): - from _pyopendds import participant_cleanup participant_cleanup(self) def create_topic(self, From 1613d9f686b23b742f143e17f4fd688835142b15 Mon Sep 17 00:00:00 2001 From: Andrea Ruffino Date: Mon, 27 Sep 2021 16:13:22 +0200 Subject: [PATCH 24/55] Fix a bug for IDL nested modules: - When a module has a type which is defined into a parent module, the type is not found. Imports are now done properly. - To investigate: Two Exceptions are now disabled in cpp bindings because the conditions seems not be required. --- pyopendds/dev/itl2py/PythonOutput.py | 3 ++- pyopendds/dev/itl2py/templates/user.cpp | 11 +++++++---- pyopendds/dev/itl2py/templates/user.py | 1 + 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/pyopendds/dev/itl2py/PythonOutput.py b/pyopendds/dev/itl2py/PythonOutput.py index b57ca86..a3a2e1f 100644 --- a/pyopendds/dev/itl2py/PythonOutput.py +++ b/pyopendds/dev/itl2py/PythonOutput.py @@ -61,7 +61,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): diff --git a/pyopendds/dev/itl2py/templates/user.cpp b/pyopendds/dev/itl2py/templates/user.cpp index 562bd76..59512be 100644 --- a/pyopendds/dev/itl2py/templates/user.cpp +++ b/pyopendds/dev/itl2py/templates/user.cpp @@ -21,10 +21,13 @@ class Type { PyObject* python_class = nullptr; if (!python_class) { Ref module = PyImport_ImportModule("/*{{ package_name }}*//*{% for name in type.name_parts -%}*/./*{{name}}*//*{%- endfor %}*/"); - if (!module) throw Exception(); + if (!module) + throw Exception("Could not import module /*{{ package_name }}*//*{% for name in type.name_parts -%}*/./*{{name}}*//*{%- endfor %}*/", PyExc_ImportError); + python_class = PyObject_GetAttrString(*module, "/*{{ type.local_name }}*/"); - if (!python_class) throw Exception(); + if (!python_class) + throw Exception("It seems that /*{{ type.local_name }}*/ does not exist in /*{{ package_name }}*//*{% for name in type.name_parts -%}*/./*{{name}}*//*{%- endfor %}*/", PyExc_ImportError); } return python_class; } @@ -44,7 +47,7 @@ class Type { /*{% endif %}*/ if (py) { if (PyObject_IsInstance(cls, py) != 1) { - throw Exception("Not a {{ type.py_name }}", PyExc_TypeError); + //throw Exception("PyObject is not a /*{{ type.py_name }}*/", PyExc_TypeError); } } else { PyObject* args; @@ -63,7 +66,7 @@ class Type { /*{% else %}*/ if (py) { if (PyObject_IsInstance(py, cls) != 1) { - throw Exception("Not a {{ type.py_name }}", PyExc_TypeError); + //throw Exception("PyObject is not of type: /*{{ type.py_name }}*/", PyExc_TypeError); } } else { PyObject* args; diff --git a/pyopendds/dev/itl2py/templates/user.py b/pyopendds/dev/itl2py/templates/user.py index 6ca3313..ef6a505 100644 --- a/pyopendds/dev/itl2py/templates/user.py +++ b/pyopendds/dev/itl2py/templates/user.py @@ -1,6 +1,7 @@ {% if has_struct -%} from dataclasses import dataclass as _pyopendds_struct from dataclasses import field +import pyDragonfly.Dragonfly {%- endif %} {% if has_enum -%} from enum import IntFlag as _pyopendds_enum From dccdb0a18e53ac77762aa1e6b8754bd427abbf6a Mon Sep 17 00:00:00 2001 From: Andrea Ruffino Date: Mon, 27 Sep 2021 16:16:23 +0200 Subject: [PATCH 25/55] Fix segmentation fault at exit: - DomainParticipant pointers were created with a factory, and then deleted with the help of SharedPointer (aliased to DomainParticipant_var). Mixing rough ptrs and shared ptrs causes troubles and the factory should now be responsible for the deletion of the participants. --- pyopendds/ext/_pyopendds.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pyopendds/ext/_pyopendds.cpp b/pyopendds/ext/_pyopendds.cpp index cc141a5..833c3cb 100644 --- a/pyopendds/ext/_pyopendds.cpp +++ b/pyopendds/ext/_pyopendds.cpp @@ -181,9 +181,11 @@ PyObject* init_opendds_impl(PyObject* self, PyObject* args, PyObject* kw) void delete_participant_var(PyObject* part_capsule) { if (PyCapsule_CheckExact(part_capsule)) { - DDS::DomainParticipant_var participant = static_cast( + DDS::DomainParticipant* participant = static_cast( PyCapsule_GetPointer(part_capsule, nullptr)); - participant = nullptr; + if(participant) { + participant_factory->delete_participant(participant); + } } } From d7ec6e7b278e46e15af022e3fd7fce3dbd5f9ddc Mon Sep 17 00:00:00 2001 From: Andrea Ruffino Date: Mon, 27 Sep 2021 16:22:18 +0200 Subject: [PATCH 26/55] Fix exception conditions in cpp bindings. - The IDL generated classes use Polymorphism (e.g: sequence type that directly derived from list). A simple call to Py_IsInstance() was not suffiscient and the exception was thrown (and unhandled) even though the case of the derived class should work. --- pyopendds/dev/itl2py/templates/user.cpp | 28 ++++++++++++------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/pyopendds/dev/itl2py/templates/user.cpp b/pyopendds/dev/itl2py/templates/user.cpp index 59512be..a6437ed 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 { @@ -24,10 +26,9 @@ class Type { if (!module) throw Exception("Could not import module /*{{ package_name }}*//*{% for name in type.name_parts -%}*/./*{{name}}*//*{%- endfor %}*/", PyExc_ImportError); - python_class = PyObject_GetAttrString(*module, "/*{{ type.local_name }}*/"); if (!python_class) - throw Exception("It seems that /*{{ type.local_name }}*/ does not exist in /*{{ package_name }}*//*{% for name in type.name_parts -%}*/./*{{name}}*//*{%- endfor %}*/", PyExc_ImportError); + throw Exception("/*{{ type.local_name }}*/ does not exist in /*{{ package_name }}*//*{% for name in type.name_parts -%}*/./*{{name}}*//*{%- endfor %}*/", PyExc_ImportError); } return python_class; } @@ -35,7 +36,7 @@ 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) }}*/ @@ -45,17 +46,11 @@ class Type { if (py) Py_DECREF(py); py = nullptr; /*{% endif %}*/ - if (py) { - if (PyObject_IsInstance(cls, py) != 1) { - //throw Exception("PyObject is not a /*{{ type.py_name }}*/", PyExc_TypeError); - } - } else { - PyObject* args; - /*{{ type.new_lines | indent(6) }}*/ - py = PyObject_CallObject(cls, args); - } + PyObject* args; + /*{{ type.new_lines | indent(6) }}*/ + py = PyObject_CallObject(cls, args); /*{% if type.to_lines %}*//*{{ type.to_lines | indent(4) }}*//*{% endif %}*/ - /*{% endif %}*/ + /*{%- endif %}*/ } static void python_to_cpp(PyObject* py, /*{{ type.cpp_name }}*/& cpp) @@ -65,8 +60,11 @@ class Type { cpp = static_cast(PyLong_AsLong(py)); /*{% else %}*/ if (py) { - if (PyObject_IsInstance(py, cls) != 1) { - //throw Exception("PyObject is not of type: /*{{ 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(" << actual_type << ") is not of type /*{{ type.local_name }}*/ nor is not parent class."; + throw Exception(msg.str().c_str(), PyExc_TypeError); } } else { PyObject* args; From 4e3b5739501be58af5d032b772a4b090a574a6d2 Mon Sep 17 00:00:00 2001 From: Andrea Ruffino Date: Mon, 27 Sep 2021 16:39:21 +0200 Subject: [PATCH 27/55] Fix warning and good practises python pep8 linter. --- pyopendds/DataReader.py | 13 ++++++------- pyopendds/DataWriter.py | 13 ++++++------- pyopendds/DomainParticipant.py | 6 +++--- pyopendds/Publisher.py | 2 +- pyopendds/Subscriber.py | 2 +- pyopendds/Topic.py | 20 ++++++++++---------- pyopendds/exceptions.py | 11 ++++++----- pyopendds/init_opendds.py | 15 +++++++-------- pyopendds/util.py | 2 +- 9 files changed, 41 insertions(+), 43 deletions(-) diff --git a/pyopendds/DataReader.py b/pyopendds/DataReader.py index 4f4bf22..3fa109f 100644 --- a/pyopendds/DataReader.py +++ b/pyopendds/DataReader.py @@ -3,6 +3,7 @@ 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 if TYPE_CHECKING: @@ -16,25 +17,23 @@ def __init__(self, subscriber: Subscriber, topic: Topic, qos=None, listener: Opt self.listener = listener self.subscriber = subscriber self.qos = qos + self.update_qos(qos) subscriber.readers.append(self) - from _pyopendds import create_datareader + from _pyopendds import create_datareader # noqa create_datareader(self, subscriber, topic, self.on_data_available_callback) - self.update_qos(qos) def update_qos(self, qos: DataReaderQos): - # from _pyopendds import update_reader_qos + # TODO: Call cpp binding to implement QoS # return update_reader_qos(self, qos) - - # print("DataReader.update_qos() not implemented") pass def wait_for(self, timeout: TimeDurationType, status: StatusKind = StatusKind.SUBSCRIPTION_MATCHED): - from _pyopendds import datareader_wait_for + from _pyopendds import datareader_wait_for # noqa datareader_wait_for(self, status, *normalize_time_duration(timeout)) def take_next_sample(self): - return self.topic._ts_package.take_next_sample(self) + return self.topic.ts_package.take_next_sample(self) def on_data_available_callback(self): sample = self.take_next_sample() diff --git a/pyopendds/DataWriter.py b/pyopendds/DataWriter.py index ed75d28..a55f1ba 100644 --- a/pyopendds/DataWriter.py +++ b/pyopendds/DataWriter.py @@ -3,6 +3,7 @@ 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: @@ -15,22 +16,20 @@ def __init__(self, publisher: Publisher, topic: Topic, qos=None): self.topic = topic self.publisher = publisher self.qos = qos + self.update_qos(qos) publisher.writers.append(self) - from _pyopendds import create_datawriter + from _pyopendds import create_datawriter # noqa create_datawriter(self, publisher, topic) - self.update_qos(qos) def update_qos(self, qos: DataWriterQos): - # from _pyopendds import update_writer_qos + # TODO: Call cpp binding to implement QoS # return update_writer_qos(self, qos) - - # print("DataWriterr.update_qos() not implemented") pass def wait_for(self, timeout: TimeDurationType, status: StatusKind = StatusKind.PUBLICATION_MATCHED): - from _pyopendds import datawriter_wait_for + from _pyopendds import datawriter_wait_for # noqa datawriter_wait_for(self, status, *normalize_time_duration(timeout)) def write(self, sample): - return self.topic._ts_package.write(self, sample) + return self.topic.ts_package.write(self, sample) diff --git a/pyopendds/DomainParticipant.py b/pyopendds/DomainParticipant.py index 8b7c513..b152e1a 100644 --- a/pyopendds/DomainParticipant.py +++ b/pyopendds/DomainParticipant.py @@ -1,7 +1,6 @@ from .Topic import Topic from .Subscriber import Subscriber from .Publisher import Publisher -from _pyopendds import participant_cleanup, create_participant class DomainParticipant: @@ -15,13 +14,14 @@ def __init__(self, domain: int, qos=None, listener=None): self.publishers = [] self._registered_typesupport = [] + from _pyopendds import create_participant # noqa create_participant(self, domain) def __del__(self): + from _pyopendds import participant_cleanup # noqa 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: diff --git a/pyopendds/Publisher.py b/pyopendds/Publisher.py index f90643f..f469ac1 100644 --- a/pyopendds/Publisher.py +++ b/pyopendds/Publisher.py @@ -15,7 +15,7 @@ def __init__(self, participant: DomainParticipant, qos=None): self.qos = qos 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) -> DataWriter: diff --git a/pyopendds/Subscriber.py b/pyopendds/Subscriber.py index c37f438..a9de9b9 100644 --- a/pyopendds/Subscriber.py +++ b/pyopendds/Subscriber.py @@ -16,7 +16,7 @@ def __init__(self, participant: DomainParticipant, qos=None, listener=None): 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) -> DataReader: 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/exceptions.py b/pyopendds/exceptions.py index 1ac9a8f..3368ec8 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,8 @@ 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/init_opendds.py b/pyopendds/init_opendds.py index ecf1bec..50c5f7a 100644 --- a/pyopendds/init_opendds.py +++ b/pyopendds/init_opendds.py @@ -1,12 +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 @@ -18,7 +17,7 @@ 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(sys.argv[1:]) @@ -27,5 +26,5 @@ def init_opendds(*args, 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..d86e3ce 100644 --- a/pyopendds/util.py +++ b/pyopendds/util.py @@ -20,4 +20,4 @@ def normalize_time_duration(duration: TimeDurationType): except Exception: raise TypeError('Could not extract time from value') - return (seconds, nanoseconds) + return seconds, nanoseconds From a1dbfaebcf6b767a009367ec341ff4ec79a88a8f Mon Sep 17 00:00:00 2001 From: Andrea Ruffino Date: Mon, 27 Sep 2021 16:49:09 +0200 Subject: [PATCH 28/55] Refactoring: - separate user.hpp and common.hpp into subfiles. - cpp/hpp files use now four spaces indentation --- pyopendds/dev/include/pyopendds/basictype.hpp | 203 +++ pyopendds/dev/include/pyopendds/common.hpp | 142 +- pyopendds/dev/include/pyopendds/exception.hpp | 88 ++ pyopendds/dev/include/pyopendds/topictype.hpp | 178 +++ pyopendds/dev/include/pyopendds/user.hpp | 379 +----- pyopendds/dev/include/pyopendds/utils.hpp | 93 ++ pyopendds/ext/_pyopendds.cpp | 1140 +++++++++-------- 7 files changed, 1143 insertions(+), 1080 deletions(-) create mode 100644 pyopendds/dev/include/pyopendds/basictype.hpp create mode 100644 pyopendds/dev/include/pyopendds/exception.hpp create mode 100644 pyopendds/dev/include/pyopendds/topictype.hpp create mode 100644 pyopendds/dev/include/pyopendds/utils.hpp diff --git a/pyopendds/dev/include/pyopendds/basictype.hpp b/pyopendds/dev/include/pyopendds/basictype.hpp new file mode 100644 index 0000000..40cbf7c --- /dev/null +++ b/pyopendds/dev/include/pyopendds/basictype.hpp @@ -0,0 +1,203 @@ +#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) + { + if (PyBool_Check(py)) throw Exception("Not a boolean", PyExc_ValueError); + + if (py) { + cpp = true; + } else { + cpp = false; + } + } +}; + + +template +class IntegerType { +public: + typedef std::numeric_limits limits; + typedef std::conditional LongType; + + static PyObject* get_python_class() + { + return PyLong_FromLong(0); + } + + static void cpp_to_python(const T& cpp, PyObject*& py) + { + if (limits::is_signed) { + if (sizeof(cpp) > sizeof(long)) { + py = PyLong_FromLongLong(cpp); + } else { + py = PyLong_FromLong(cpp); + } + } else { + if (sizeof(cpp) > sizeof(long)) { + py = PyLong_FromUnsignedLongLong(cpp); + } else { + py = PyLong_FromUnsignedLong(cpp); + } + } + } + + static void python_to_cpp(PyObject* py, T& cpp) + { + T value; + if (limits::is_signed) { + if (sizeof(cpp) > sizeof(long)) { + value = PyLong_AsLongLong(py); + } else { + value = PyLong_AsLong(py); + } + } else { + if (sizeof(cpp) > sizeof(long)) { + value = PyLong_AsUnsignedLongLong(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); + } +}; + +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::Long i32; +template<> class Type: public IntegerType {}; + +typedef ::CORBA::Short i16; +template<> class Type: public IntegerType {}; + +typedef ::CORBA::Char c8; +template<> class Type: public IntegerType {}; +// TODO: Put Other Integer Types Here + +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..99a2e5d --- /dev/null +++ b/pyopendds/dev/include/pyopendds/topictype.hpp @@ -0,0 +1,178 @@ +#ifndef PYOPENDDS_TOPICTYPE_HEADER +#define PYOPENDDS_TOPICTYPE_HEADER + +#include + +#include +#include +#include + +#include +#include +#include + +namespace pyopendds { + +template +class Type; + +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) + { + 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(); + } + +protected: + 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; + + static void init() + { + Ptr type{ new TopicType }; + 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)) + 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()); + } + + // 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); + + // 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) { + PyErr_SetString(Errors::PyOpenDDS_Error(), "reader_impl->take_next_sample() failed"); + return nullptr; + } + + 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) { + 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 234ee7d..93a9bc8 100644 --- a/pyopendds/dev/include/pyopendds/user.hpp +++ b/pyopendds/dev/include/pyopendds/user.hpp @@ -2,382 +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 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) - { - if (PyBool_Check(py)) { - throw Exception("Not a boolean", PyExc_ValueError); - } - if(py) { - cpp = true; - } else { - cpp = false; - } - } -}; - -typedef ::CORBA::Boolean b; -template<> class Type: public BooleanType {}; - -template -class IntegerType { -public: - typedef std::numeric_limits limits; - typedef std::conditional LongType; - - static PyObject* get_python_class() - { - return PyLong_FromLong(0); - } - - static void cpp_to_python(const T& cpp, PyObject*& py) - { - if (limits::is_signed) { - if (sizeof(cpp) > sizeof(long)) { - py = PyLong_FromLongLong(cpp); - } else { - py = PyLong_FromLong(cpp); - } - } else { - if (sizeof(cpp) > sizeof(long)) { - py = PyLong_FromUnsignedLongLong(cpp); - } else { - py = PyLong_FromUnsignedLong(cpp); - } - } - if (!py) throw Exception(); - } - - static void python_to_cpp(PyObject* py, T& cpp) - { - T value; //todo: change to LongType - if (limits::is_signed) { - if (sizeof(cpp) > sizeof(long)) { - value = PyLong_AsLongLong(py); - } else { - value = PyLong_AsLong(py); - } - } else { - if (sizeof(cpp) > sizeof(long)) { - value = PyLong_AsUnsignedLongLong(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); - } - -}; - -typedef ::CORBA::LongLong i64; -template<> class Type: public IntegerType {}; - -typedef ::CORBA::Long i32; -template<> class Type: public IntegerType {}; - -typedef ::CORBA::Short i16; -template<> class Type: public IntegerType {}; - -typedef ::CORBA::Char c8; -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_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); - } -}; - -// 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 - -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); - if (!py) throw Exception(); - } - - 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; - } -}; - -typedef ::CORBA::Float f32; -template<> class Type: public FloatingType {}; - -typedef ::CORBA::Double f64; -template<> class Type: public FloatingType {}; - -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; - virtual PyObject* write(PyObject* pywriter, PyObject* pysample) = 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(); - } - -protected: - 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()); - } - - // 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); - - // 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) { - PyErr_SetString(Errors::PyOpenDDS_Error(), "reader_impl->take_next_sample() failed"); - return nullptr; - } - - 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) { - throw Exception( - "WRITE ERROR", Errors::PyOpenDDS_Error()); - } -// if (Errors::check_rc(rc)) return nullptr; - - return PyLong_FromLong(rc); - } - - 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..5ce0c8c --- /dev/null +++ b/pyopendds/dev/include/pyopendds/utils.hpp @@ -0,0 +1,93 @@ +#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); + Py_DECREF(capsule); + + return error; + } + +} // namesapce pyopendds + +#endif // PYOPENDDS_UTILS_HEADER diff --git a/pyopendds/ext/_pyopendds.cpp b/pyopendds/ext/_pyopendds.cpp index 833c3cb..59c8b61 100644 --- a/pyopendds/ext/_pyopendds.cpp +++ b/pyopendds/ext/_pyopendds.cpp @@ -20,69 +20,72 @@ PyObject* Errors::PyOpenDDS_Error_ = nullptr; PyObject* Errors::ReturnCodeError_ = nullptr; namespace { + class DataReaderListenerImpl : public virtual OpenDDS::DCPS::LocalObject { -public: - DataReaderListenerImpl(PyObject * self, PyObject *callback); +public: + DataReaderListenerImpl(PyObject * self, PyObject *callback); - virtual void on_requested_deadline_missed( - DDS::DataReader_ptr reader, - const DDS::RequestedDeadlineMissedStatus& status) {} + 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_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_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_liveliness_changed( + DDS::DataReader_ptr reader, + const DDS::LivelinessChangedStatus& status) { } - virtual void on_data_available( - DDS::DataReader_ptr reader); + 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_subscription_matched( + DDS::DataReader_ptr reader, + const DDS::SubscriptionMatchedStatus& status) { } - virtual void on_sample_lost( - DDS::DataReader_ptr reader, - const DDS::SampleLostStatus& status) {} + virtual void on_sample_lost( + DDS::DataReader_ptr reader, + const DDS::SampleLostStatus& status) { } - private: - PyObject *_callback; - PyObject * _self; +private: + PyObject *_callback; + PyObject * _self; }; -DataReaderListenerImpl::DataReaderListenerImpl(PyObject * self, PyObject *callback): OpenDDS::DCPS::LocalObject() { - _self = self; - Py_XINCREF(_self); - _callback = callback; - Py_XINCREF(_callback); +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) +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) + try { + if (PyCallable_Check(callable)) { + result = PyObject_CallFunctionObjArgs(callable, nullptr); + if (result == NULL) { PyErr_Print(); + } Py_XDECREF(result); } else { - throw Exception("function is not a callback", PyExc_TypeError); + throw Exception("Pyobject is not a callable", PyExc_TypeError); } - } catch (Exception& e ) { - // Py_XDECREF(callable); + } catch (Exception& e) { + // Py_XDECREF(callable); PyGILState_Release(gstate); throw e; } @@ -94,450 +97,440 @@ DataReaderListenerImpl::on_data_available(DDS::DataReader_ptr reader) 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); - } + /* + * 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); - } + /* + * 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); - // Initialize OpenDDS - participant_factory = TheParticipantFactoryWithArgs(argc, argv); - if (!participant_factory) { - PyErr_SetString(Errors::PyOpenDDS_Error(), "Failed to Initialize OpenDDS"); - return nullptr; - } + OpenDDS::DCPS::TransportConfig_rch transport_config = + TheTransportRegistry->create_config("default_rtps_transport_config"); - // Cleanup args - for (int i = 0; i < original_argc; i++) { - delete original_argv[i]; - } - delete [] original_argv; - delete [] argv; + 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; + } - Py_RETURN_NONE; + // 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* participant = static_cast( - PyCapsule_GetPointer(part_capsule, nullptr)); - if(participant) { participant_factory->delete_participant(participant); + if (PyCapsule_CheckExact(part_capsule)) { + DDS::DomainParticipant_var participant = static_cast( + PyCapsule_GetPointer(part_capsule, nullptr)); + + if (participant) { + } } - } } /** - * 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; - } + Ref pyparticipant; + unsigned domain; + if (!PyArg_ParseTuple(args, "OI", &*pyparticipant, &domain)) { + return nullptr; + } + pyparticipant++; - // Attach OpenDDS Participant to Participant Python Object - if (set_capsule(*pyparticipant, participant, delete_participant_var)) { - return nullptr; - } + // 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); - Py_RETURN_NONE; + 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++; + Ref pyparticipant; + if (!PyArg_ParseTuple(args, "O", &*pyparticipant)) { + return nullptr; + } + pyparticipant++; + + // Get DomainParticipant_var + DDS::DomainParticipant* participant = + get_capsule(*pyparticipant); - // Get DomainParticipant_var - DDS::DomainParticipant* participant = - get_capsule(*pyparticipant); - if (!participant) return nullptr; + if (!participant) return nullptr; participant->delete_contained_entities(); - Py_RETURN_NONE; + + 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; - } + 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; - } + // Attach OpenDDS Topic to Topic Python Object + if (set_capsule(*pytopic, topic, delete_topic_var)) { + return nullptr; + } - Py_RETURN_NONE; + 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; - } + Ref pyparticipant; + Ref pysubscriber; + if (!PyArg_ParseTuple(args, "OO", &*pysubscriber, &*pyparticipant)) { + return nullptr; + } + pyparticipant++; + pysubscriber++; - // 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; - } + // Get DomainParticipant_var + DDS::DomainParticipant* participant = + get_capsule(*pyparticipant); + if (!participant) return nullptr; - // Attach OpenDDS Subscriber to Subscriber Python Object - if (set_capsule(*pysubscriber, subscriber, delete_subscriber_var)) { - 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; + 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; - } + Ref pyparticipant; + Ref pypublisher; + if (!PyArg_ParseTuple(args, "OO", &*pypublisher, &*pyparticipant)) { + return nullptr; + } + pyparticipant++; + pypublisher++; - // Attach OpenDDS Publisher to Publisher Python Object - if (set_capsule(*pypublisher, publisher, delete_publisher_var)) { - return nullptr; - } + // 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; + 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)); - DDS::DataReaderListener_ptr listener = DDS::DataReader::_narrow(reader)->get_listener(); - free(listener); - listener = 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; + } + } } /** - * create_datareader(datareader: DataReader, subscriber: Subscriber, topic: Topic, listener: pyObject) -> None - */ +* create_datareader(datareader: DataReader, subscriber: Subscriber, topic: Topic, listener: pyObject) -> None +*/ PyObject* create_datareader(PyObject* self, PyObject* args) { - Ref pydatareader; - Ref pysubscriber; - Ref pytopic; - Ref pycallback; - - if (!PyArg_ParseTuple(args, "OOOO", - &*pydatareader, &*pysubscriber, &*pytopic, &*pycallback)) { - return nullptr; - } - pydatareader++; - pysubscriber++; - pytopic++; - pycallback++; - - // 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); + Ref pydatareader; + Ref pysubscriber; + Ref pytopic; + Ref pycallback; + + if (!PyArg_ParseTuple(args, "OOOO", + &*pydatareader, &*pysubscriber, &*pytopic, &*pycallback)) { + return nullptr; } - else { - throw Exception("Callback provided is not a callable object", PyExc_TypeError); + pydatareader++; + pysubscriber++; + pytopic++; + pycallback++; + + // 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 DataReader + DDS::DataReader* datareader = subscriber->create_datareader( + topic, DATAREADER_QOS_DEFAULT, listener, + OpenDDS::DCPS::DEFAULT_STATUS_MASK); - // Create DataReader - DDS::DataReader* datareader = subscriber->create_datareader( - topic, DATAREADER_QOS_DEFAULT, listener, - OpenDDS::DCPS::DEFAULT_STATUS_MASK); - if (!datareader) { - PyErr_SetString(Errors::PyOpenDDS_Error(), "Failed to Create DataReader"); - return nullptr; - } + 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; - } + // Attach OpenDDS DataReader to DataReader Python Object + if (set_capsule(*pydatareader, datareader, delete_datareader_var)) { + return nullptr; + } - Py_RETURN_NONE; + Py_RETURN_NONE; } /** - * Callback for Python to Call when the DataWriter Capsule is Deleted - */ +* 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)); - writer = nullptr; - } + if (PyCapsule_CheckExact(writer_capsule)) { + DDS::DataWriter_var writer = static_cast( + PyCapsule_GetPointer(writer_capsule, nullptr)); + if (writer) writer = nullptr; + } } /** - * create_datawriter(datawriter: DataWriter, publisher: Publisher, topic: Topic) -> None - */ +* create_datawriter(datawriter: DataWriter, publisher: Publisher, topic: Topic) -> None +*/ PyObject* create_datawriter(PyObject* self, PyObject* args) { - Ref pydatawriter; - Ref pypublisher; - Ref pytopic; - if (!PyArg_ParseTuple(args, "OOO", - &*pydatawriter, &*pypublisher, &*pytopic)) { - return nullptr; - } - pydatawriter++; - pypublisher++; - pytopic++; - - // 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 DataWriter - DDS::DataWriter* datawriter = publisher->create_datawriter( - topic, DATAWRITER_QOS_DEFAULT, nullptr, - OpenDDS::DCPS::DEFAULT_STATUS_MASK); - if (!datawriter) { - PyErr_SetString(Errors::PyOpenDDS_Error(), "Failed to Create DataWriter"); - return nullptr; - } + Ref pydatawriter; + Ref pypublisher; + Ref pytopic; + if (!PyArg_ParseTuple(args, "OOO", + &*pydatawriter, &*pypublisher, &*pytopic)) { + return nullptr; + } + pydatawriter++; + pypublisher++; + pytopic++; + + // 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 DataWriter + DDS::DataWriter* datawriter = publisher->create_datawriter( + topic, DATAWRITER_QOS_DEFAULT, 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; - } + // Attach OpenDDS DataWriter to DataWriter Python Object + if (set_capsule(*pydatawriter, datawriter, delete_datawriter_var)) { + return nullptr; + } - Py_RETURN_NONE; + 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) { - 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); - // TODO: wait() causes segmentation fault // DDS::WaitSet_var waitset = new DDS::WaitSet; // if (!waitset) return PyErr_NoMemory(); @@ -565,37 +558,37 @@ PyObject* datareader_wait_for(PyObject* self, PyObject* args) std::this_thread::sleep_for(std::chrono::seconds(1)); t_now = std::chrono::steady_clock::now(); } - 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); + + Py_RETURN_NONE; } /** - * datawriter_wait_for( - * datawriter: DataWriter, status: StatusKind, - * seconds: int, nanoseconds: int) -> 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); - // TODO: wait() causes segmentation fault // DDS::WaitSet_var waitset = new DDS::WaitSet; // if (!waitset) return PyErr_NoMemory(); @@ -623,192 +616,211 @@ PyObject* datawriter_wait_for(PyObject* self, PyObject* args) std::this_thread::sleep_for(std::chrono::seconds(1)); t_now = std::chrono::steady_clock::now(); } - Py_RETURN_NONE; + 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); + + Py_RETURN_NONE; } PyObject* update_writer_qos(PyObject* self, PyObject* args) { - Ref pydatawriter; - Ref pyQos; - - Ref pydurability; - Ref pyreliability; - Ref pyhistory; - Ref pydurabilityKind; - Ref pyreliabilityKind; - Ref pyhistoryKind; - - if (!PyArg_ParseTuple(args, "OO", - &*pydatawriter, &*pyQos)) { - return nullptr; - } - pydatawriter++; - pyQos++; - - // Get DataWriter - DDS::DataWriter* writer = get_capsule(*pydatawriter); - if (!writer) return nullptr; - - std::cerr << "get default qos" << std::endl; - // Create Qos for the data writer according to the spec - DDS::DataWriterQos qos; - writer->get_publisher()->get_default_datawriter_qos(qos); - - - std::cerr << "get durability" << std::endl; - pydurability = PyObject_GetAttrString(*pyQos, "durability"); - if (!pydurability) return nullptr; - pydurability ++; - - std::cerr << "get reliability" << std::endl; - pyreliability = PyObject_GetAttrString(*pyQos, "reliability"); - if (!pyreliability) return nullptr; - pyreliability ++; - - std::cerr << "get history" << std::endl; - pyhistory = PyObject_GetAttrString(*pyQos, "history"); - if (!pyhistory) return nullptr; - pyhistory ++; - - - std::cerr << "get dura kind" << std::endl; - pydurabilityKind = PyObject_GetAttrString(*pydurability, "kind"); - if (!pydurabilityKind) return nullptr; - 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 nullptr; - 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 nullptr; - pyhistoryKind ++; - - std::cerr << "AsLong" << std::endl; - qos.history.kind = (DDS::HistoryQosPolicyKind) PyLong_AsLong(*pyhistoryKind); - - std::cerr << "set QOS" << std::endl; - writer->set_qos (qos); - - std::cerr << "return" << std::endl; - Py_RETURN_NONE; + Ref pydatawriter; + Ref pyQos; + + Ref pydurability; + Ref pyreliability; + Ref pyhistory; + Ref pydurabilityKind; + Ref pyreliabilityKind; + Ref pyhistoryKind; + + if (!PyArg_ParseTuple(args, "OO", + &*pydatawriter, &*pyQos)) { + return nullptr; + } + pydatawriter++; + pyQos++; + + // Get DataWriter + DDS::DataWriter* writer = get_capsule(*pydatawriter); + if (!writer) return nullptr; + + std::cerr << "get default qos" << std::endl; + // Create Qos for the data writer according to the spec + DDS::DataWriterQos qos; + writer->get_publisher()->get_default_datawriter_qos(qos); + + std::cerr << "get durability" << std::endl; + pydurability = PyObject_GetAttrString(*pyQos, "durability"); + if (!pydurability) return nullptr; + pydurability ++; + + std::cerr << "get reliability" << std::endl; + pyreliability = PyObject_GetAttrString(*pyQos, "reliability"); + if (!pyreliability) return nullptr; + pyreliability ++; + + std::cerr << "get history" << std::endl; + pyhistory = PyObject_GetAttrString(*pyQos, "history"); + if (!pyhistory) return nullptr; + pyhistory ++; + + std::cerr << "get dura kind" << std::endl; + pydurabilityKind = PyObject_GetAttrString(*pydurability, "kind"); + if (!pydurabilityKind) return nullptr; + 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 nullptr; + 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 nullptr; + pyhistoryKind ++; + + std::cerr << "AsLong" << std::endl; + qos.history.kind = (DDS::HistoryQosPolicyKind) PyLong_AsLong(*pyhistoryKind); + + std::cerr << "set QOS" << std::endl; + writer->set_qos (qos); + + std::cerr << "return" << std::endl; + Py_RETURN_NONE; } PyObject* update_reader_qos(PyObject* self, PyObject* args) { - Ref pydatareader; - Ref pyQos; - - Ref pydurability; - Ref pyreliability; - Ref pyhistory; - Ref pydurabilityKind; - Ref pyreliabilityKind; - Ref pyhistoryKind; - Ref pyhistorydepth; - Ref pyreliabilitymax; - - if (!PyArg_ParseTuple(args, "OO", - &*pydatareader, &*pyQos)) { - return nullptr; - } - pydatareader++; - pyQos++; - - // Get DataReader - DDS::DataReader* reader = get_capsule(*pydatareader); - if (!reader) return nullptr; - - // Create Qos for the data writer according to the spec - DDS::DataReaderQos qos; - reader->get_subscriber()->get_default_datareader_qos(qos); + Ref pydatareader; + Ref pyQos; + + Ref pydurability; + Ref pyreliability; + Ref pyhistory; + Ref pydurabilityKind; + Ref pyreliabilityKind; + Ref pyhistoryKind; + Ref pyhistorydepth; + Ref pyreliabilitymax; + + if (!PyArg_ParseTuple(args, "OO", + &*pydatareader, &*pyQos)) { + return nullptr; + } + pydatareader++; + pyQos++; - pydurability = PyObject_GetAttrString(*pyQos, "durability"); - if (!pydurability) return nullptr; - pydurability ++; + // Get DataReader + DDS::DataReader* reader = get_capsule(*pydatareader); + if (!reader) return nullptr; - pyreliability = PyObject_GetAttrString(*pyQos, "reliability"); - if (!pyreliability) return nullptr; - pyreliability ++; + // Create Qos for the data writer according to the spec + DDS::DataReaderQos qos; + reader->get_subscriber()->get_default_datareader_qos(qos); - pyhistory = PyObject_GetAttrString(*pyQos, "history"); - if (!pyhistory) return nullptr; - pyhistory ++; + pydurability = PyObject_GetAttrString(*pyQos, "durability"); + if (!pydurability) return nullptr; + pydurability ++; + pyreliability = PyObject_GetAttrString(*pyQos, "reliability"); + if (!pyreliability) return nullptr; + pyreliability ++; - pydurabilityKind = PyObject_GetAttrString(*pydurability, "kind"); - if (!pydurabilityKind) return nullptr; - pydurabilityKind ++; - qos.durability.kind = (DDS::DurabilityQosPolicyKind) PyLong_AsLong(*pydurabilityKind); + pyhistory = PyObject_GetAttrString(*pyQos, "history"); + if (!pyhistory) return nullptr; + pyhistory ++; - pyreliabilityKind = PyObject_GetAttrString(*pyreliability, "kind"); - if (!pyreliabilityKind) return nullptr; - pyreliabilityKind ++; - qos.reliability.kind = (DDS::ReliabilityQosPolicyKind) PyLong_AsLong(*pyreliabilityKind); + pydurabilityKind = PyObject_GetAttrString(*pydurability, "kind"); + if (!pydurabilityKind) return nullptr; + pydurabilityKind ++; + qos.durability.kind = (DDS::DurabilityQosPolicyKind) PyLong_AsLong(*pydurabilityKind); - pyreliabilitymax = PyObject_GetAttrString(*pyreliability, "max_blocking_time"); - if (!pyreliabilitymax) return nullptr; - pyreliabilitymax ++; - qos.history.depth = PyLong_AsLong(*pyreliabilitymax); + pyreliabilityKind = PyObject_GetAttrString(*pyreliability, "kind"); + if (!pyreliabilityKind) return nullptr; + pyreliabilityKind ++; + qos.reliability.kind = (DDS::ReliabilityQosPolicyKind) PyLong_AsLong(*pyreliabilityKind); + pyreliabilitymax = PyObject_GetAttrString(*pyreliability, "max_blocking_time"); + if (!pyreliabilitymax) return nullptr; + pyreliabilitymax ++; + qos.history.depth = PyLong_AsLong(*pyreliabilitymax); - pyhistoryKind = PyObject_GetAttrString(*pyhistory, "kind"); - if (!pyhistoryKind) return nullptr; - pyhistoryKind ++; + pyhistoryKind = PyObject_GetAttrString(*pyhistory, "kind"); + if (!pyhistoryKind) return nullptr; + pyhistoryKind ++; - qos.history.kind = (DDS::HistoryQosPolicyKind) PyLong_AsLong(*pyhistoryKind); + qos.history.kind = (DDS::HistoryQosPolicyKind) PyLong_AsLong(*pyhistoryKind); - pyhistorydepth = PyObject_GetAttrString(*pyhistory, "depth"); - if (!pyhistorydepth) return nullptr; - pyhistorydepth ++; - qos.history.depth = PyLong_AsLong(*pyhistorydepth); + pyhistorydepth = PyObject_GetAttrString(*pyhistory, "depth"); + if (!pyhistorydepth) return nullptr; + pyhistorydepth ++; + qos.history.depth = PyLong_AsLong(*pyhistorydepth); - reader->set_qos (qos); - Py_RETURN_NONE; + reader->set_qos (qos); + 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}, - {"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}, - {"update_writer_qos", update_writer_qos, METH_VARARGS, internal_docstr}, - {"update_reader_qos", update_reader_qos, 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 }, + { "update_writer_qos", update_writer_qos, METH_VARARGS, internal_docstr }, + { "update_reader_qos", update_reader_qos, 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; } From b0561650ed1f0fc5c9be88f551d7dce080c77d57 Mon Sep 17 00:00:00 2001 From: Andrea Ruffino Date: Mon, 27 Sep 2021 16:59:46 +0200 Subject: [PATCH 29/55] Fix a few segmentation faults on app terminate: - DecRef the TypeSupport_var object instead of calling direct delete. - Import _pyopendds c bindings (and try-catch ImportError) in top level instead of __del__, because dynamically loaded modules are likely to be unavailable at terminate. --- pyopendds/DomainParticipant.py | 8 +- pyopendds/dev/include/pyopendds/topictype.hpp | 13 +- pyopendds/ext/_pyopendds.cpp | 118 +++++++++--------- 3 files changed, 77 insertions(+), 62 deletions(-) diff --git a/pyopendds/DomainParticipant.py b/pyopendds/DomainParticipant.py index b152e1a..f12097c 100644 --- a/pyopendds/DomainParticipant.py +++ b/pyopendds/DomainParticipant.py @@ -2,6 +2,13 @@ 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: @@ -18,7 +25,6 @@ def __init__(self, domain: int, qos=None, listener=None): create_participant(self, domain) def __del__(self): - from _pyopendds import participant_cleanup # noqa participant_cleanup(self) def create_topic(self, name: str, topic_type: type, qos=None, listener=None) -> Topic: diff --git a/pyopendds/dev/include/pyopendds/topictype.hpp b/pyopendds/dev/include/pyopendds/topictype.hpp index 99a2e5d..e55fc4d 100644 --- a/pyopendds/dev/include/pyopendds/topictype.hpp +++ b/pyopendds/dev/include/pyopendds/topictype.hpp @@ -78,8 +78,10 @@ class TopicType: public TopicTypeBase { */ static void delete_typesupport(PyObject* capsule) { - if (PyCapsule_CheckExact(capsule)) - delete static_cast( PyCapsule_GetPointer(capsule, nullptr) ); + if (PyCapsule_CheckExact(capsule)) { + DDS::TypeSupport_var ts = static_cast(PyCapsule_GetPointer(capsule, nullptr)); + if (ts) ts = nullptr; + } } void register_type(PyObject* pyparticipant) @@ -95,7 +97,7 @@ class TopicType: public TopicTypeBase { TypeSupportImpl* type_support = new TypeSupportImpl; if (type_support->register_type(participant, "") != DDS::RETCODE_OK) { delete type_support; - type_support = 0; + type_support = nullptr; throw Exception("Could not create register type", Errors::PyOpenDDS_Error()); } @@ -103,8 +105,9 @@ class TopicType: public TopicTypeBase { 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(); + Ref list { PyObject_GetAttrString(pyparticipant, "_registered_typesupport") }; + if (!list || PyList_Append(*list, *capsule)) + throw Exception(); } PyObject* take_next_sample(PyObject* pyreader) diff --git a/pyopendds/ext/_pyopendds.cpp b/pyopendds/ext/_pyopendds.cpp index 59c8b61..d58d90f 100644 --- a/pyopendds/ext/_pyopendds.cpp +++ b/pyopendds/ext/_pyopendds.cpp @@ -187,12 +187,13 @@ PyObject* init_opendds_impl(PyObject* self, PyObject* args, PyObject* kw) */ void delete_participant_var(PyObject* part_capsule) { - participant_factory->delete_participant(participant); if (PyCapsule_CheckExact(part_capsule)) { DDS::DomainParticipant_var participant = static_cast( PyCapsule_GetPointer(part_capsule, nullptr)); if (participant) { + //participant->delete_contained_entities(); + participant = nullptr; } } } @@ -244,7 +245,8 @@ PyObject* participant_cleanup(PyObject* self, PyObject* args) if (!participant) return nullptr; - participant->delete_contained_entities(); + participant->delete_contained_entities(); + participant_factory->delete_participant(participant); Py_RETURN_NONE; } @@ -531,33 +533,6 @@ PyObject* create_datawriter(PyObject* self, PyObject* args) */ PyObject* datareader_wait_for(PyObject* self, PyObject* args) { - // TODO: wait() causes segmentation fault -// 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; - - // 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::seconds(1)); - t_now = std::chrono::steady_clock::now(); - } Ref pydatareader; unsigned status; int seconds; @@ -579,6 +554,35 @@ PyObject* datareader_wait_for(PyObject* self, PyObject* args) 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; } @@ -589,33 +593,6 @@ PyObject* datareader_wait_for(PyObject* self, PyObject* args) */ PyObject* datawriter_wait_for(PyObject* self, PyObject* args) { - // TODO: wait() causes segmentation fault -// 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; - - // 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::seconds(1)); - t_now = std::chrono::steady_clock::now(); - } Ref pydatawriter; unsigned status; int seconds; @@ -637,6 +614,35 @@ PyObject* datawriter_wait_for(PyObject* self, PyObject* args) 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; } From 8146316b44771cbacf9377b2154491084e83a691 Mon Sep 17 00:00:00 2001 From: Andrea Ruffino Date: Mon, 27 Sep 2021 17:02:20 +0200 Subject: [PATCH 30/55] Fix a bug where python template uses hardcoded module name. --- pyopendds/dev/itl2py/templates/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyopendds/dev/itl2py/templates/user.py b/pyopendds/dev/itl2py/templates/user.py index ef6a505..d40da41 100644 --- a/pyopendds/dev/itl2py/templates/user.py +++ b/pyopendds/dev/itl2py/templates/user.py @@ -1,7 +1,7 @@ {% if has_struct -%} from dataclasses import dataclass as _pyopendds_struct from dataclasses import field -import pyDragonfly.Dragonfly +import {{ package_name }} {%- endif %} {% if has_enum -%} from enum import IntFlag as _pyopendds_enum From 5480c0f883928bcac811dda7cfd356b05bd9fde1 Mon Sep 17 00:00:00 2001 From: Andrea Ruffino Date: Mon, 27 Sep 2021 17:07:53 +0200 Subject: [PATCH 31/55] Fix a bug where static member value was reset, opting for a singleton implementation --- pyopendds/dev/include/pyopendds/topictype.hpp | 27 ++++++++++++++----- pyopendds/dev/include/pyopendds/utils.hpp | 24 +++++++++++++++++ pyopendds/dev/itl2py/templates/user.cpp | 2 +- 3 files changed, 46 insertions(+), 7 deletions(-) diff --git a/pyopendds/dev/include/pyopendds/topictype.hpp b/pyopendds/dev/include/pyopendds/topictype.hpp index e55fc4d..c6f9647 100644 --- a/pyopendds/dev/include/pyopendds/topictype.hpp +++ b/pyopendds/dev/include/pyopendds/topictype.hpp @@ -3,6 +3,8 @@ #include +#include "utils.hpp" + #include #include #include @@ -15,6 +17,18 @@ 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: @@ -33,15 +47,14 @@ class TopicTypeBase { static TopicTypeBase* find(PyObject* pytype) { - TopicTypes::iterator i = topic_types_.find(pytype); - if (i == topic_types_.end()) { + 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(); } - -protected: - static TopicTypes topic_types_; }; template @@ -59,8 +72,10 @@ class TopicType: public TopicTypeBase { static void init() { + Global* global = &Singleton::getInstance(); + Ptr type{ new TopicType }; - topic_types_.insert(TopicTypes::value_type(Type::get_python_class(), type)); + global->topic_types_().insert(TopicTypes::value_type(Type::get_python_class(), type)); } PyObject* get_python_class() diff --git a/pyopendds/dev/include/pyopendds/utils.hpp b/pyopendds/dev/include/pyopendds/utils.hpp index 5ce0c8c..64e9841 100644 --- a/pyopendds/dev/include/pyopendds/utils.hpp +++ b/pyopendds/dev/include/pyopendds/utils.hpp @@ -88,6 +88,30 @@ namespace pyopendds { 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/templates/user.cpp b/pyopendds/dev/itl2py/templates/user.cpp index a6437ed..2c2cd9f 100644 --- a/pyopendds/dev/itl2py/templates/user.cpp +++ b/pyopendds/dev/itl2py/templates/user.cpp @@ -11,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 %}*/ From 88219c5dc344a15caa42bd5978e13d967214be58 Mon Sep 17 00:00:00 2001 From: Andrea Ruffino Date: Mon, 27 Sep 2021 17:11:50 +0200 Subject: [PATCH 32/55] Add preprocessor checking if Apple. --- pyopendds/DataReader.py | 4 ++-- pyopendds/DataWriter.py | 2 +- pyopendds/dev/include/pyopendds/topictype.hpp | 14 ++++++++++++-- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/pyopendds/DataReader.py b/pyopendds/DataReader.py index 3fa109f..862cf5b 100644 --- a/pyopendds/DataReader.py +++ b/pyopendds/DataReader.py @@ -5,7 +5,7 @@ from .util import TimeDurationType, normalize_time_duration from .Qos import DataReaderQos -from typing import TYPE_CHECKING, Callable, Optional +from typing import TYPE_CHECKING, Callable, Optional, Any if TYPE_CHECKING: from .Subscriber import Subscriber @@ -32,7 +32,7 @@ def wait_for(self, timeout: TimeDurationType, status: StatusKind = StatusKind.SU from _pyopendds import datareader_wait_for # noqa datareader_wait_for(self, status, *normalize_time_duration(timeout)) - def take_next_sample(self): + def take_next_sample(self) -> Any: return self.topic.ts_package.take_next_sample(self) def on_data_available_callback(self): diff --git a/pyopendds/DataWriter.py b/pyopendds/DataWriter.py index a55f1ba..88284c3 100644 --- a/pyopendds/DataWriter.py +++ b/pyopendds/DataWriter.py @@ -31,5 +31,5 @@ def wait_for(self, timeout: TimeDurationType, status: StatusKind = StatusKind.PU from _pyopendds import datawriter_wait_for # noqa datawriter_wait_for(self, status, *normalize_time_duration(timeout)) - def write(self, sample): + def write(self, sample) -> int: return self.topic.ts_package.write(self, sample) diff --git a/pyopendds/dev/include/pyopendds/topictype.hpp b/pyopendds/dev/include/pyopendds/topictype.hpp index c6f9647..df65356 100644 --- a/pyopendds/dev/include/pyopendds/topictype.hpp +++ b/pyopendds/dev/include/pyopendds/topictype.hpp @@ -135,6 +135,7 @@ class TopicType: public TopicTypeBase { 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); @@ -150,15 +151,24 @@ class TopicType: public TopicTypeBase { // 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) { - PyErr_SetString(Errors::PyOpenDDS_Error(), "reader_impl->take_next_sample() failed"); - return nullptr; + // 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); From f0e622475d0a2385fb292b2fbde5df45caa0503d Mon Sep 17 00:00:00 2001 From: Andrea Ruffino Date: Mon, 27 Sep 2021 17:14:52 +0200 Subject: [PATCH 33/55] Fix PyBool_Check returning an exception --- pyopendds/dev/include/pyopendds/basictype.hpp | 5 ++- pyopendds/dev/include/pyopendds/topictype.hpp | 42 +++++++++---------- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/pyopendds/dev/include/pyopendds/basictype.hpp b/pyopendds/dev/include/pyopendds/basictype.hpp index 40cbf7c..ef8d95f 100644 --- a/pyopendds/dev/include/pyopendds/basictype.hpp +++ b/pyopendds/dev/include/pyopendds/basictype.hpp @@ -33,9 +33,10 @@ class BooleanType { static void python_to_cpp(PyObject* py, T& cpp) { - if (PyBool_Check(py)) throw Exception("Not a boolean", PyExc_ValueError); + // PyBool_Check always return true + //if (PyBool_Check(py)) throw Exception("Not a boolean", PyExc_ValueError); - if (py) { + if (py == Py_True) { cpp = true; } else { cpp = false; diff --git a/pyopendds/dev/include/pyopendds/topictype.hpp b/pyopendds/dev/include/pyopendds/topictype.hpp index df65356..09c178e 100644 --- a/pyopendds/dev/include/pyopendds/topictype.hpp +++ b/pyopendds/dev/include/pyopendds/topictype.hpp @@ -137,27 +137,26 @@ class TopicType: public TopicTypeBase { #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 + 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; @@ -193,7 +192,8 @@ class TopicType: public TopicTypeBase { DDS::ReturnCode_t rc = writer_impl->write(rv, DDS::HANDLE_NIL); if (rc != DDS::RETCODE_OK) { - throw Exception("Writer could not write sample", Errors::PyOpenDDS_Error()); + // 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; From 2f68a41abc3717fff74d3c1726b58f45553f8556 Mon Sep 17 00:00:00 2001 From: Andrea Ruffino Date: Mon, 27 Sep 2021 17:17:21 +0200 Subject: [PATCH 34/55] Forced QoS (for reader and writer) to RELIABLE_RELIABILITY --- pyopendds/ext/_pyopendds.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/pyopendds/ext/_pyopendds.cpp b/pyopendds/ext/_pyopendds.cpp index d58d90f..a55c882 100644 --- a/pyopendds/ext/_pyopendds.cpp +++ b/pyopendds/ext/_pyopendds.cpp @@ -454,9 +454,15 @@ PyObject* create_datareader(PyObject* self, PyObject* args) } } + // TODO : forced qos to RELIABLE_RELIABILITY_QOS + // Create QoS + DDS::DataReaderQos qos; + subscriber->get_default_datareader_qos(qos); + qos.reliability.kind = DDS::RELIABLE_RELIABILITY_QOS; + // Create DataReader DDS::DataReader* datareader = subscriber->create_datareader( - topic, DATAREADER_QOS_DEFAULT, listener, + topic, qos, listener, OpenDDS::DCPS::DEFAULT_STATUS_MASK); if (!datareader) { @@ -508,9 +514,15 @@ PyObject* create_datawriter(PyObject* self, PyObject* args) DDS::Topic* topic = get_capsule(*pytopic); if (!topic) return nullptr; + // TODO : force qos to DDS::RELIABLE_RELIABILITY_QOS + // Create QoS + DDS::DataWriterQos qos; + publisher->get_default_datawriter_qos(qos); + qos.reliability.kind = DDS::RELIABLE_RELIABILITY_QOS; + // Create DataWriter DDS::DataWriter* datawriter = publisher->create_datawriter( - topic, DATAWRITER_QOS_DEFAULT, nullptr, + topic, qos, nullptr, OpenDDS::DCPS::DEFAULT_STATUS_MASK); if (!datawriter) { From 68bf37beed71aeb14012d55513f7575aafe2f70b Mon Sep 17 00:00:00 2001 From: Andrea Ruffino Date: Mon, 27 Sep 2021 17:26:15 +0200 Subject: [PATCH 35/55] Add support to octet type into IDL. c_ubyte and c_byte are wrapped into UByte and Byte custom python classes. --- pyopendds/dev/include/pyopendds/basictype.hpp | 101 +++++++++++++++++- pyopendds/dev/itl2py/PythonOutput.py | 5 +- pyopendds/dev/itl2py/templates/user.py | 1 + pyopendds/util.py | 80 +++++++++++++- 4 files changed, 180 insertions(+), 7 deletions(-) diff --git a/pyopendds/dev/include/pyopendds/basictype.hpp b/pyopendds/dev/include/pyopendds/basictype.hpp index ef8d95f..3a00845 100644 --- a/pyopendds/dev/include/pyopendds/basictype.hpp +++ b/pyopendds/dev/include/pyopendds/basictype.hpp @@ -44,6 +44,10 @@ class BooleanType { } }; +PyObject* pyopendds_mod_str = NULL; +PyObject* pyopendds_mod = NULL; +PyObject* pyopendds_byte_func = NULL; +PyObject* pyopendds_ubyte_func = NULL; template class IntegerType { @@ -56,19 +60,63 @@ class IntegerType { 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 { - py = PyLong_FromLong(cpp); + 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 { - py = PyLong_FromUnsignedLong(cpp); + if (sizeof(cpp) <= sizeof(uint8_t)) { + py = PyUByte_FromUInt8(cpp); + } else { + py = PyLong_FromUnsignedLong(cpp); + } } } } @@ -80,13 +128,21 @@ class IntegerType { if (sizeof(cpp) > sizeof(long)) { value = PyLong_AsLongLong(py); } else { - value = PyLong_AsLong(py); + if (sizeof(cpp) <= sizeof(int8_t)) { + value = static_cast(PyByte_AsLong(py)); + } else { + value = PyLong_AsLong(py); + } } } else { if (sizeof(cpp) > sizeof(long)) { value = PyLong_AsUnsignedLongLong(py); } else { - value = PyLong_AsUnsignedLong(py); + if (sizeof(cpp) <= sizeof(uint8_t)) { + value = static_cast(PyUByte_AsUnsignedLong(py)); + } else { + value = PyLong_AsUnsignedLong(py); + } } } if (value < limits::lowest() || value > limits::max()) { @@ -97,6 +153,40 @@ class IntegerType { 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 @@ -181,6 +271,9 @@ template<> class Type: public IntegerType {}; typedef ::CORBA::Char c8; template<> class Type: public IntegerType {}; + +typedef ::CORBA::Octet u8; +template<> class Type: public IntegerType {}; // TODO: Put Other Integer Types Here typedef ::CORBA::Float f32; diff --git a/pyopendds/dev/itl2py/PythonOutput.py b/pyopendds/dev/itl2py/PythonOutput.py index a3a2e1f..e4f6617 100644 --- a/pyopendds/dev/itl2py/PythonOutput.py +++ b/pyopendds/dev/itl2py/PythonOutput.py @@ -10,8 +10,9 @@ class PythonOutput(Output): ''' primitive_types = { # (Python Type, Default Default Value) - PrimitiveType.Kind.bool: ('bool', 'False'), - PrimitiveType.Kind.u8: ('int', '0'), + PrimitiveType.Kind.byte: ('UByte', '0x00'), + PrimitiveType.Kind.u8: ('UByte', '0x00'), + PrimitiveType.Kind.i8: ('Byte', '0x00'), PrimitiveType.Kind.i8: ('int', '0'), PrimitiveType.Kind.u16: ('int', '0'), PrimitiveType.Kind.i16: ('int', '0'), diff --git a/pyopendds/dev/itl2py/templates/user.py b/pyopendds/dev/itl2py/templates/user.py index d40da41..f662d0e 100644 --- a/pyopendds/dev/itl2py/templates/user.py +++ b/pyopendds/dev/itl2py/templates/user.py @@ -1,6 +1,7 @@ {% 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 -%} diff --git a/pyopendds/util.py b/pyopendds/util.py index d86e3ce..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] @@ -21,3 +22,80 @@ def normalize_time_duration(duration: TimeDurationType): raise TypeError('Could not extract time from value') 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)) From 407d5c328e57e9a7e32279001f70d3b55049b6e8 Mon Sep 17 00:00:00 2001 From: Andrea Ruffino Date: Tue, 28 Sep 2021 10:49:41 +0200 Subject: [PATCH 36/55] Reformat whitespaces and spacings --- pyopendds/DataReader.py | 5 +++-- pyopendds/DataWriter.py | 5 +++-- pyopendds/Publisher.py | 3 +-- pyopendds/Subscriber.py | 3 +-- pyopendds/dev/include/pyopendds/basictype.hpp | 1 + pyopendds/dev/include/pyopendds/topictype.hpp | 1 - pyopendds/dev/itl2py/itl.py | 1 - pyopendds/exceptions.py | 1 - 8 files changed, 9 insertions(+), 11 deletions(-) diff --git a/pyopendds/DataReader.py b/pyopendds/DataReader.py index 862cf5b..1465ee6 100644 --- a/pyopendds/DataReader.py +++ b/pyopendds/DataReader.py @@ -6,6 +6,7 @@ from .Qos import DataReaderQos from typing import TYPE_CHECKING, Callable, Optional, Any + if TYPE_CHECKING: from .Subscriber import Subscriber @@ -20,7 +21,7 @@ def __init__(self, subscriber: Subscriber, topic: Topic, qos=None, listener: Opt self.update_qos(qos) subscriber.readers.append(self) - from _pyopendds import create_datareader # noqa + from _pyopendds import create_datareader # noqa create_datareader(self, subscriber, topic, self.on_data_available_callback) def update_qos(self, qos: DataReaderQos): @@ -29,7 +30,7 @@ def update_qos(self, qos: DataReaderQos): pass def wait_for(self, timeout: TimeDurationType, status: StatusKind = StatusKind.SUBSCRIPTION_MATCHED): - from _pyopendds import datareader_wait_for # noqa + from _pyopendds import datareader_wait_for # noqa datareader_wait_for(self, status, *normalize_time_duration(timeout)) def take_next_sample(self) -> Any: diff --git a/pyopendds/DataWriter.py b/pyopendds/DataWriter.py index 88284c3..12289d8 100644 --- a/pyopendds/DataWriter.py +++ b/pyopendds/DataWriter.py @@ -6,6 +6,7 @@ from .Qos import DataWriterQos from typing import TYPE_CHECKING + if TYPE_CHECKING: from .Publisher import Publisher @@ -19,7 +20,7 @@ def __init__(self, publisher: Publisher, topic: Topic, qos=None): self.update_qos(qos) publisher.writers.append(self) - from _pyopendds import create_datawriter # noqa + from _pyopendds import create_datawriter # noqa create_datawriter(self, publisher, topic) def update_qos(self, qos: DataWriterQos): @@ -28,7 +29,7 @@ def update_qos(self, qos: DataWriterQos): pass def wait_for(self, timeout: TimeDurationType, status: StatusKind = StatusKind.PUBLICATION_MATCHED): - from _pyopendds import datawriter_wait_for # noqa + from _pyopendds import datawriter_wait_for # noqa datawriter_wait_for(self, status, *normalize_time_duration(timeout)) def write(self, sample) -> int: diff --git a/pyopendds/Publisher.py b/pyopendds/Publisher.py index f469ac1..ba85a4d 100644 --- a/pyopendds/Publisher.py +++ b/pyopendds/Publisher.py @@ -14,8 +14,7 @@ def __init__(self, participant: DomainParticipant, qos=None): participant.publishers.append(self) self.qos = qos self.writers = [] - - from _pyopendds import create_publisher # noqa + from _pyopendds import create_publisher # noqa create_publisher(self, participant) def create_datawriter(self, topic: Topic, qos=None) -> DataWriter: diff --git a/pyopendds/Subscriber.py b/pyopendds/Subscriber.py index a9de9b9..9efc004 100644 --- a/pyopendds/Subscriber.py +++ b/pyopendds/Subscriber.py @@ -15,8 +15,7 @@ def __init__(self, participant: DomainParticipant, qos=None, listener=None): self.qos = qos self.listener = listener self.readers = [] - - from _pyopendds import create_subscriber # noqa + from _pyopendds import create_subscriber # noqa create_subscriber(self, participant) def create_datareader(self, topic: Topic, qos=None, listener=None) -> DataReader: diff --git a/pyopendds/dev/include/pyopendds/basictype.hpp b/pyopendds/dev/include/pyopendds/basictype.hpp index 3a00845..48c19a6 100644 --- a/pyopendds/dev/include/pyopendds/basictype.hpp +++ b/pyopendds/dev/include/pyopendds/basictype.hpp @@ -44,6 +44,7 @@ class BooleanType { } }; + PyObject* pyopendds_mod_str = NULL; PyObject* pyopendds_mod = NULL; PyObject* pyopendds_byte_func = NULL; diff --git a/pyopendds/dev/include/pyopendds/topictype.hpp b/pyopendds/dev/include/pyopendds/topictype.hpp index 09c178e..7152370 100644 --- a/pyopendds/dev/include/pyopendds/topictype.hpp +++ b/pyopendds/dev/include/pyopendds/topictype.hpp @@ -166,7 +166,6 @@ class TopicType: public TopicTypeBase { // PyErr_SetString(Errors::PyOpenDDS_Error(), "reader_impl->take_next_sample() failed"); return Py_None; } - #endif PyObject* rv = nullptr; if (info.valid_data) { diff --git a/pyopendds/dev/itl2py/itl.py b/pyopendds/dev/itl2py/itl.py index 1ffb186..efb9ca0 100644 --- a/pyopendds/dev/itl2py/itl.py +++ b/pyopendds/dev/itl2py/itl.py @@ -155,4 +155,3 @@ def parse_itl(types, itl): # just use the first definition we found. if parsed_type.name.itl_name not in types: types[parsed_type.name.itl_name] = parsed_type - diff --git a/pyopendds/exceptions.py b/pyopendds/exceptions.py index 3368ec8..341788f 100644 --- a/pyopendds/exceptions.py +++ b/pyopendds/exceptions.py @@ -47,5 +47,4 @@ def __str__(self): repr(self.unknown_code) - ReturnCodeError.generate_subclasses() From 799393fcbe601ea7653b4623d8f339364a802119 Mon Sep 17 00:00:00 2001 From: Andrea Ruffino Date: Tue, 28 Sep 2021 10:53:27 +0200 Subject: [PATCH 37/55] Fix a bug where primitives_types contained 'i8' which was declared twice and bool was missing. --- pyopendds/dev/itl2py/PythonOutput.py | 2 +- pyopendds/init_opendds.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyopendds/dev/itl2py/PythonOutput.py b/pyopendds/dev/itl2py/PythonOutput.py index e4f6617..023979b 100644 --- a/pyopendds/dev/itl2py/PythonOutput.py +++ b/pyopendds/dev/itl2py/PythonOutput.py @@ -10,10 +10,10 @@ class PythonOutput(Output): ''' primitive_types = { # (Python Type, Default Default Value) + PrimitiveType.Kind.bool: ('bool', 'False'), PrimitiveType.Kind.byte: ('UByte', '0x00'), PrimitiveType.Kind.u8: ('UByte', '0x00'), PrimitiveType.Kind.i8: ('Byte', '0x00'), - PrimitiveType.Kind.i8: ('int', '0'), PrimitiveType.Kind.u16: ('int', '0'), PrimitiveType.Kind.i16: ('int', '0'), PrimitiveType.Kind.u32: ('int', '0'), diff --git a/pyopendds/init_opendds.py b/pyopendds/init_opendds.py index 50c5f7a..b6db09d 100644 --- a/pyopendds/init_opendds.py +++ b/pyopendds/init_opendds.py @@ -26,5 +26,5 @@ def init_opendds(*args, default_rtps=True, opendds_debug_level=0): raise ValueError('OpenDDS debug level must be between 0 and 10!') args.extend(['-DCPSDebugLevel', str(opendds_debug_level)]) - from _pyopendds import init_opendds_impl # noqa + from _pyopendds import init_opendds_impl # noqa init_opendds_impl(*args, default_rtps=default_rtps) From a29b3b9da9c100ab7b9e768ff85d213bcd0205c6 Mon Sep 17 00:00:00 2001 From: Andrea Ruffino Date: Wed, 6 Oct 2021 10:44:23 +0200 Subject: [PATCH 38/55] Add unsigned integer types --- pyopendds/dev/include/pyopendds/basictype.hpp | 23 ++++++++++++++----- pyopendds/dev/itl2py/PythonOutput.py | 6 ++--- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/pyopendds/dev/include/pyopendds/basictype.hpp b/pyopendds/dev/include/pyopendds/basictype.hpp index 48c19a6..5fd88e1 100644 --- a/pyopendds/dev/include/pyopendds/basictype.hpp +++ b/pyopendds/dev/include/pyopendds/basictype.hpp @@ -54,7 +54,7 @@ template class IntegerType { public: typedef std::numeric_limits limits; - typedef std::conditional LongType; +// typedef std::conditional LongType; static PyObject* get_python_class() { @@ -126,20 +126,20 @@ class IntegerType { { T value; if (limits::is_signed) { - if (sizeof(cpp) > sizeof(long)) { + if (sizeof(T) == sizeof(long long)) { value = PyLong_AsLongLong(py); } else { - if (sizeof(cpp) <= sizeof(int8_t)) { + if (sizeof(T) <= sizeof(int8_t)) { value = static_cast(PyByte_AsLong(py)); } else { value = PyLong_AsLong(py); } } } else { - if (sizeof(cpp) > sizeof(long)) { + if (sizeof(T) == sizeof(long long)) { value = PyLong_AsUnsignedLongLong(py); } else { - if (sizeof(cpp) <= sizeof(uint8_t)) { + if (sizeof(T) <= sizeof(uint8_t)) { value = static_cast(PyUByte_AsUnsignedLong(py)); } else { value = PyLong_AsUnsignedLong(py); @@ -264,18 +264,29 @@ 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 {}; -// TODO: Put Other Integer Types Here typedef ::CORBA::Float f32; template<> class Type: public FloatingType {}; diff --git a/pyopendds/dev/itl2py/PythonOutput.py b/pyopendds/dev/itl2py/PythonOutput.py index 023979b..c6fe202 100644 --- a/pyopendds/dev/itl2py/PythonOutput.py +++ b/pyopendds/dev/itl2py/PythonOutput.py @@ -11,9 +11,9 @@ class PythonOutput(Output): primitive_types = { # (Python Type, Default Default Value) PrimitiveType.Kind.bool: ('bool', 'False'), - PrimitiveType.Kind.byte: ('UByte', '0x00'), - PrimitiveType.Kind.u8: ('UByte', '0x00'), - PrimitiveType.Kind.i8: ('Byte', '0x00'), + 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'), From f9afdbb52bebc574f816b4eb572b07b79454396f Mon Sep 17 00:00:00 2001 From: David Pierret Date: Fri, 8 Oct 2021 14:54:13 +0200 Subject: [PATCH 39/55] add unsigned 8 and 32 bits integer Signed-off-by: David Pierret --- pyopendds/dev/include/pyopendds/user.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyopendds/dev/include/pyopendds/user.hpp b/pyopendds/dev/include/pyopendds/user.hpp index d5d7348..b768a10 100644 --- a/pyopendds/dev/include/pyopendds/user.hpp +++ b/pyopendds/dev/include/pyopendds/user.hpp @@ -115,13 +115,16 @@ typedef ::CORBA::LongLong i64; template<> class Type: public IntegerType {}; typedef ::CORBA::Long i32; +typedef ::CORBA::Long u32; template<> class Type: public IntegerType {}; typedef ::CORBA::Short i16; template<> class Type: public IntegerType {}; typedef ::CORBA::Char c8; +typedef ::CORBA::Char u8; template<> class Type: public IntegerType {}; + // TODO: Put Other Integer Types Here const char* string_data(const std::string& cpp) From b2b8971c313de489b76f0bba85648fb65cca4053 Mon Sep 17 00:00:00 2001 From: Andrea Ruffino Date: Mon, 11 Oct 2021 18:51:16 +0200 Subject: [PATCH 40/55] Revert back merging from sequences branch. (Reverse commit so that we mark the merging resolved as rejected). --- pyopendds/dev/itl2py/CppOutput.py | 66 ++++++++-------------------- pyopendds/dev/itl2py/PythonOutput.py | 4 +- pyopendds/dev/itl2py/itl.py | 4 +- 3 files changed, 23 insertions(+), 51 deletions(-) diff --git a/pyopendds/dev/itl2py/CppOutput.py b/pyopendds/dev/itl2py/CppOutput.py index d570215..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, SequenceType +from .ast import PrimitiveType, StructType, EnumType, SequenceType, ArrayType from .Output import Output @@ -15,6 +15,8 @@ def cpp_type_name(type_node): 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 @@ -57,30 +59,14 @@ def visit_struct(self, struct_type): field_node.type_node.is_string() is_sequence = isinstance(field_node.type_node, SequenceType) - if is_sequence: - to_lines = [ - 'Ref field_elem;', - 'field_value = PyList_New(0);', - 'for (int i = 0; i < cpp.{field_name}.length(); i++) {{', - ' {pyopendds_type} elem = cpp.{field_name}[i];', - ' field_elem = nullptr;', - ' Type<{pyopendds_type}>::cpp_to_python(elem', - ' #ifdef CPP11_IDL', - ' ()', - ' #endif', - ' , *field_elem' + (', "{default_encoding}"' if is_string else '') + ');', - ' PyList_Append(*field_value, *field_elem);', - '}}' - ] - else: - to_lines = [ - 'Type<{pyopendds_type}>::cpp_to_python(cpp.{field_name}', - '#ifdef CPP11_IDL', - ' ()', - '#endif', - ' , *field_value' - + (', "{default_encoding}"' if is_string else '') + ');', - ] + to_lines = [ + 'Type<{pyopendds_type}>::cpp_to_python(cpp.{field_name}', + '#ifdef CPP11_IDL', + ' ()', + '#endif', + ' , *field_value' + + (', "{default_encoding}"' if is_string else '') + ');', + ] from_lines = [ 'if (PyObject_HasAttrString(py, "{field_name}")) {{', @@ -102,28 +88,14 @@ def visit_struct(self, struct_type): ]) if from_lines: - if is_sequence: - from_lines.extend([ - 'cpp.{field_name}.length(PyList_Size(*field_value));', - 'for (int i = 0; i < PyList_Size(*field_value); i++) {{', - ' ::ContTrajSegment elem = cpp.{field_name}[i];', - ' Type<{pyopendds_type}>::python_to_cpp(PyList_GetItem(*field_value, i), elem', - '#ifdef CPP11_IDL', - ' ()', - '#endif', - ' ' + (', "{default_encoding}"' if is_string else '') + ');', - ' cpp.{field_name}[i] = elem;', - '}}' - ]) - else: - from_lines.extend([ - 'Type<{pyopendds_type}>::python_to_cpp(*field_value, cpp.{field_name}', - '#ifdef CPP11_IDL', - ' ()', - '#endif', - ' ' - + (', "{default_encoding}"' if is_string else '') + ');' - ]) + 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 [''] + [ diff --git a/pyopendds/dev/itl2py/PythonOutput.py b/pyopendds/dev/itl2py/PythonOutput.py index d369c50..c6fe202 100644 --- a/pyopendds/dev/itl2py/PythonOutput.py +++ b/pyopendds/dev/itl2py/PythonOutput.py @@ -1,4 +1,4 @@ -from .ast import PrimitiveType, StructType, EnumType, SequenceType +from .ast import PrimitiveType, StructType, EnumType, SequenceType, ArrayType from .Output import Output @@ -76,6 +76,8 @@ def get_python_default_value_string(self, field_type): 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") diff --git a/pyopendds/dev/itl2py/itl.py b/pyopendds/dev/itl2py/itl.py index f248211..efb9ca0 100644 --- a/pyopendds/dev/itl2py/itl.py +++ b/pyopendds/dev/itl2py/itl.py @@ -74,7 +74,7 @@ def parse_string(details): def parse_sequence(types, details): - base_type = parse_type(types, list(types)[0]) + base_type = parse_type(types, details["type"]) sequence_max_count = details.get("capacity", None) array_dimensions = details.get("size", None) if array_dimensions is not None: @@ -139,8 +139,6 @@ def parse_type(types, details): if details_type is str: if details in types: return types[details] - elif 'sequence' in details : - return parse_sequence(types, {'type':types, 'capacity': 1, 'size': None}) else: raise ValueError("Invalid Type: " + details) elif details_type is dict: From 6e63ad2a8589078dcdc9c41aec52b967759a72c5 Mon Sep 17 00:00:00 2001 From: Andrea Ruffino Date: Mon, 11 Oct 2021 19:32:33 +0200 Subject: [PATCH 41/55] Split long code lines in user.cpp with stringstream insertion. --- pyopendds/dev/itl2py/templates/user.cpp | 30 ++++++++++++++++++++----- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/pyopendds/dev/itl2py/templates/user.cpp b/pyopendds/dev/itl2py/templates/user.cpp index 2c2cd9f..86d2213 100644 --- a/pyopendds/dev/itl2py/templates/user.cpp +++ b/pyopendds/dev/itl2py/templates/user.cpp @@ -22,13 +22,28 @@ class Type { { PyObject* python_class = nullptr; if (!python_class) { - Ref module = PyImport_ImportModule("/*{{ package_name }}*//*{% for name in type.name_parts -%}*/./*{{name}}*//*{%- endfor %}*/"); - if (!module) - throw Exception("Could not import module /*{{ package_name }}*//*{% for name in type.name_parts -%}*/./*{{name}}*//*{%- endfor %}*/", PyExc_ImportError); + std::stringstream mod_ss; + mod_ss << "/*{{ package_name }}*/"; + /*{% for name in type.name_parts -%}*/ + 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("/*{{ type.local_name }}*/ does not exist in /*{{ package_name }}*//*{% for name in type.name_parts -%}*/./*{{name}}*//*{%- endfor %}*/", PyExc_ImportError); + 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; } @@ -63,7 +78,10 @@ class Type { 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(" << actual_type << ") is not of type /*{{ type.local_name }}*/ nor is not parent class."; + 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 { From c2b76c0bd8beb2d078bdb6a7dd856de35cf715e3 Mon Sep 17 00:00:00 2001 From: Andrea Ruffino Date: Tue, 26 Oct 2021 00:40:37 +0200 Subject: [PATCH 42/55] Add pyidl console script: - pyidl supports multiple .idl files embed in one packages, related or not with include preprocessor directive --- pyopendds/dev/pyidl/__init__.py | 0 pyopendds/dev/pyidl/__main__.py | 199 ++++++++++++++++++ pyopendds/dev/pyidl/gencmakefile.py | 50 +++++ pyopendds/dev/pyidl/gensetupfile.py | 16 ++ setup.py | 4 +- tata.idl | 0 tests/typeSupport_test/TypeSupport_test.idl | 23 ++ .../TypeSupport_to_include.idl | 6 + tests/typeSupport_test/main.py | 11 + toto.idl | 0 10 files changed, 308 insertions(+), 1 deletion(-) create mode 100644 pyopendds/dev/pyidl/__init__.py create mode 100644 pyopendds/dev/pyidl/__main__.py create mode 100644 pyopendds/dev/pyidl/gencmakefile.py create mode 100644 pyopendds/dev/pyidl/gensetupfile.py create mode 100644 tata.idl create mode 100644 tests/typeSupport_test/TypeSupport_test.idl create mode 100644 tests/typeSupport_test/TypeSupport_to_include.idl create mode 100644 tests/typeSupport_test/main.py create mode 100644 toto.idl 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/setup.py b/setup.py index 5ed1314..91b97e2 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,7 @@ ], }, install_requires=[ - 'jinja2', + 'Jinja2', + 'MarkupSafe', ], ) diff --git a/tata.idl b/tata.idl new file mode 100644 index 0000000..e69de29 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..2270f47 --- /dev/null +++ b/tests/typeSupport_test/main.py @@ -0,0 +1,11 @@ + +from icecream import ic +# from pyTypeSupport_test.TypeSupportTest import MySample1 +from pyToto.TypeSupportTest import MySample1 +from pyToto import Tata + +if __name__ == "__main__": + sample_1 = MySample1() + sample_tata = Tata() + ic(sample_1) + ic(sample_tata) diff --git a/toto.idl b/toto.idl new file mode 100644 index 0000000..e69de29 From 8327eb957300f1f792d8203d5c2b36fad75daa36 Mon Sep 17 00:00:00 2001 From: Andrea Ruffino Date: Tue, 26 Oct 2021 00:41:13 +0200 Subject: [PATCH 43/55] Lower cmake_minimum_required from 3.12 to 3.10 --- pyopendds/dev/itl2py/templates/CMakeLists.txt | 2 +- pyopendds/ext/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyopendds/dev/itl2py/templates/CMakeLists.txt b/pyopendds/dev/itl2py/templates/CMakeLists.txt index f9d2d37..1f3f5d5 100644 --- a/pyopendds/dev/itl2py/templates/CMakeLists.txt +++ b/pyopendds/dev/itl2py/templates/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.12) +cmake_minimum_required(VERSION 3.10) project({{ native_package_name }}) find_package(Python3 COMPONENTS Development REQUIRED) diff --git a/pyopendds/ext/CMakeLists.txt b/pyopendds/ext/CMakeLists.txt index 3f1baec..526e02b 100644 --- a/pyopendds/ext/CMakeLists.txt +++ b/pyopendds/ext/CMakeLists.txt @@ -1,5 +1,5 @@ # CMakeLists for Native Part of PyOpenDDS -cmake_minimum_required(VERSION 3.12) +cmake_minimum_required(VERSION 3.10) project(PyOpenDDS) # Find Python From 4af687dd16d4fec4880c3f387952e86903e4b9af Mon Sep 17 00:00:00 2001 From: Andrea Ruffino Date: Tue, 26 Oct 2021 01:26:41 +0200 Subject: [PATCH 44/55] Run shell script for TypeSupport test --- setup.py | 3 +-- tests/typeSupport_test/main.py | 12 +++++------- tests/typeSupport_test/run_test.sh | 7 +++++++ 3 files changed, 13 insertions(+), 9 deletions(-) create mode 100755 tests/typeSupport_test/run_test.sh diff --git a/setup.py b/setup.py index 91b97e2..22c8fea 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,6 @@ ], }, install_requires=[ - 'Jinja2', - 'MarkupSafe', + 'Jinja2' ], ) diff --git a/tests/typeSupport_test/main.py b/tests/typeSupport_test/main.py index 2270f47..c24259d 100644 --- a/tests/typeSupport_test/main.py +++ b/tests/typeSupport_test/main.py @@ -1,11 +1,9 @@ +from pyTypeSupportTest.TypeSupportTest import MySample1 +from pyTypeSupportTest import MySample2 -from icecream import ic -# from pyTypeSupport_test.TypeSupportTest import MySample1 -from pyToto.TypeSupportTest import MySample1 -from pyToto import Tata if __name__ == "__main__": sample_1 = MySample1() - sample_tata = Tata() - ic(sample_1) - ic(sample_tata) + 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 From 496d3f8b22ceb622d6d1410e91cbd0db40f34720 Mon Sep 17 00:00:00 2001 From: Andrea Ruffino Date: Thu, 28 Oct 2021 16:53:02 +0200 Subject: [PATCH 45/55] Revert to cmake_minimum_required v3.12 --- pyopendds/dev/itl2py/templates/CMakeLists.txt | 2 +- pyopendds/ext/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyopendds/dev/itl2py/templates/CMakeLists.txt b/pyopendds/dev/itl2py/templates/CMakeLists.txt index 1f3f5d5..f9d2d37 100644 --- a/pyopendds/dev/itl2py/templates/CMakeLists.txt +++ b/pyopendds/dev/itl2py/templates/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.10) +cmake_minimum_required(VERSION 3.12) project({{ native_package_name }}) find_package(Python3 COMPONENTS Development REQUIRED) diff --git a/pyopendds/ext/CMakeLists.txt b/pyopendds/ext/CMakeLists.txt index 526e02b..3f1baec 100644 --- a/pyopendds/ext/CMakeLists.txt +++ b/pyopendds/ext/CMakeLists.txt @@ -1,5 +1,5 @@ # CMakeLists for Native Part of PyOpenDDS -cmake_minimum_required(VERSION 3.10) +cmake_minimum_required(VERSION 3.12) project(PyOpenDDS) # Find Python From b6bb16d4004b7b963fc9513ee53697c267c9d5e4 Mon Sep 17 00:00:00 2001 From: Sdpierret <82807727+Sdpierret@users.noreply.github.com> Date: Tue, 2 Nov 2021 10:21:59 +0100 Subject: [PATCH 46/55] Delete tata.idl --- tata.idl | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 tata.idl diff --git a/tata.idl b/tata.idl deleted file mode 100644 index e69de29..0000000 From eeb23c16adde74b856a6a613f2214d0055bf74d4 Mon Sep 17 00:00:00 2001 From: Sdpierret <82807727+Sdpierret@users.noreply.github.com> Date: Tue, 2 Nov 2021 10:23:00 +0100 Subject: [PATCH 47/55] Delete toto.idl --- toto.idl | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 toto.idl diff --git a/toto.idl b/toto.idl deleted file mode 100644 index e69de29..0000000 From 3c74b4089767710da04b976304023916426d9fa4 Mon Sep 17 00:00:00 2001 From: swolferDF Date: Wed, 3 Nov 2021 10:58:07 +0100 Subject: [PATCH 48/55] qos dev try --- pyopendds/DataReader.py | 11 +- pyopendds/Subscriber.py | 1 + pyopendds/dev/include/pyopendds/utils.hpp | 60 ++++- pyopendds/ext/_pyopendds.cpp | 270 ++++++++++++++++------ tests/basic_test/CMakeLists.txt | 22 +- tests/basic_test/subscriber.py | 7 +- 6 files changed, 280 insertions(+), 91 deletions(-) diff --git a/pyopendds/DataReader.py b/pyopendds/DataReader.py index 1465ee6..3f4f3ad 100644 --- a/pyopendds/DataReader.py +++ b/pyopendds/DataReader.py @@ -18,13 +18,20 @@ def __init__(self, subscriber: Subscriber, topic: Topic, qos=None, listener: Opt self.listener = listener self.subscriber = subscriber self.qos = qos - self.update_qos(qos) + # self.qos = DataReaderQos + print(f"Iim in data reader !!!!!!!!") + print(f"qos") + print(self.qos) + # print(self.qos.durability.kind) + # self.update_qos(qos) subscriber.readers.append(self) from _pyopendds import create_datareader # noqa - create_datareader(self, subscriber, topic, self.on_data_available_callback) + create_datareader(self, subscriber, topic, self.on_data_available_callback, self.qos) def update_qos(self, qos: DataReaderQos): + # from _pyopendds import update_reader_qos + # update_reader_qos(self,qos) # TODO: Call cpp binding to implement QoS # return update_reader_qos(self, qos) pass diff --git a/pyopendds/Subscriber.py b/pyopendds/Subscriber.py index 9efc004..c4eb925 100644 --- a/pyopendds/Subscriber.py +++ b/pyopendds/Subscriber.py @@ -19,6 +19,7 @@ def __init__(self, participant: DomainParticipant, qos=None, listener=None): create_subscriber(self, participant) def create_datareader(self, topic: Topic, qos=None, listener=None) -> DataReader: + print(f"i creat data reader from subscriber") reader = DataReader(self, topic, qos, listener) self.readers.append(reader) return reader diff --git a/pyopendds/dev/include/pyopendds/utils.hpp b/pyopendds/dev/include/pyopendds/utils.hpp index 64e9841..c8d6537 100644 --- a/pyopendds/dev/include/pyopendds/utils.hpp +++ b/pyopendds/dev/include/pyopendds/utils.hpp @@ -14,6 +14,7 @@ namespace pyopendds { */ class Ref { public: + Ref(PyObject* o = nullptr) : object_(o) { } @@ -44,9 +45,11 @@ namespace pyopendds { { return object_; } + private: PyObject* object_; + }; /** @@ -83,8 +86,10 @@ namespace pyopendds { return true; const bool error = PyObject_SetAttrString(py, capsule_name, capsule); + printf("im in set_capsule ",error); + if (error==false){ printf("not good");} Py_DECREF(capsule); - + return error; } @@ -112,6 +117,59 @@ namespace pyopendds { return instance; } + template + T* get_class(PyObject* obj){ + // Py_Initialize(); + PyObject* module = PyImport_ImportModule("pyopendds.Qos"); + assert(module != NULL); + + T* return_value = nullptr; + // reliability.kind + PyObject* capsule = PyObject_GetAttrString(obj, "reliability"); // nr + + + PyObject* pyopendds_ = PyImport_ImportModule("pyopendds"); + if (!pyopendds_) printf("not find pyopendds"); + + PyObject* PyOpenDDS_qos_= PyObject_GetAttrString(pyopendds_, "Qos"); + if (!PyOpenDDS_qos_) printf("not find pyopendds.Qos"); + + DDS::DataReaderQos DataReaderQos_ = PyObject_GetAttrString(PyOpenDDS_qos_, "DataReaderQos"); + // if (!DataReaderQos_) printf("not find pyopendds.Qos.DataReaderQos"); + + + // if (capsule) { + // if (PyCapsule_IsValid(capsule, nullptr)) { + // return_value = static_cast(PyCapsule_GetPointer(capsule, nullptr)); + // printf(return_value); + // } + // Py_DECREF(capsule); + + + // PyObject* klass = PyObject_GetAttrString(module, "DataReaderQos"); + // assert(klass != NULL); + + // PyObject* instance = PyInstance_New(klass, NULL, NULL); + // assert(instance != NULL); + + // PyObject* result = PyObject_CallMethod(instance, "durability"); + // assert(result != NULL); + + // printf("1 + 2 = %ld\n", PyInt_AsLong(result)); + // Py_Finalize(); + return return_value; + } + + // MapType dictToMap(const Py::Dict& dict) + // { + // MapType map; + // for (auto key : dict.keys()) { + // map.emplace(key.str(), asElement(dict.getItem(key))); + // } + // return map; + // } + + } // namesapce pyopendds #endif // PYOPENDDS_UTILS_HEADER diff --git a/pyopendds/ext/_pyopendds.cpp b/pyopendds/ext/_pyopendds.cpp index 5126c28..3e651b3 100644 --- a/pyopendds/ext/_pyopendds.cpp +++ b/pyopendds/ext/_pyopendds.cpp @@ -26,6 +26,8 @@ class DataReaderListenerImpl : public virtual OpenDDS::DCPS::LocalObjectget_subscriber()->get_default_datareader_qos(qos); + + pydurability = PyObject_GetAttrString(*pyQos, "durability"); + if (!pydurability) + { + std::cout << "no pydurability\n"; + return false; + } + pydurability ++; + + pyreliability = PyObject_GetAttrString(*pyQos, "reliability"); + if (!pyreliability) return false; + pyreliability ++; + + pyhistory = PyObject_GetAttrString(*pyQos, "history"); + if (!pyhistory) return false; + pyhistory ++; + + pydurabilityKind = PyObject_GetAttrString(*pydurability, "kind"); + if (!pydurabilityKind) return false; + pydurabilityKind ++; + qos.durability.kind = (DDS::DurabilityQosPolicyKind) PyLong_AsLong(*pydurabilityKind); + + pyreliabilityKind = PyObject_GetAttrString(*pyreliability, "kind"); + if (!pyreliabilityKind) return false; + pyreliabilityKind ++; + //add by me : + pyreliabilityKindname = PyObject_GetAttrString(*pyreliabilityKind, "name"); + if (!pyreliabilityKindname) return false; + pyreliabilityKindname ++; + std::cout<<"pyreliabilityKindname : "<set_qos (qos); + if (ret != DDS::RETCODE_OK) + { + std::cout <<"set_qos NOT WORKING ! "<<"\n"; + ACE_ERROR (( + LM_ERROR, + ACE_TEXT("(%P|%t)ERROR: set qos reader returned %d.\n"), + ret)); + } + std::cout <<"fin_qos"<<"\n"; + return true; + // Py_RETURN_NONE; +} /** * create_datareader(datareader: DataReader, subscriber: Subscriber, topic: Topic, listener: pyObject) -> None */ -PyObject* create_datareader(PyObject* self, PyObject* args) +PyObject* create_datareader(PyObject* self, PyObject* args ) { Ref pydatareader; Ref pysubscriber; Ref pytopic; Ref pycallback; + Ref pyqos; - if (!PyArg_ParseTuple(args, "OOOO", - &*pydatareader, &*pysubscriber, &*pytopic, &*pycallback)) { + if (!PyArg_ParseTuple(args, "OOOOO", //add one O + &*pydatareader, &*pysubscriber, &*pytopic, &*pycallback,&*pyqos )) { return nullptr; } pydatareader++; pysubscriber++; pytopic++; pycallback++; + pyqos++; //add here + + // std::cout << "QOS : "<<*pyqos<<"\n"; + // std::cout << typeid(*pyqos).name()<<"\n"; + // std::cout << typeid(*pysubscriber).name()<<"\n"; // Get Subscriber DDS::Subscriber* subscriber = get_capsule(*pysubscriber); @@ -457,20 +544,39 @@ PyObject* create_datareader(PyObject* self, PyObject* args) // TODO : forced qos to RELIABLE_RELIABILITY_QOS // Create QoS - DDS::DataReaderQos qos; + DDS::DataReaderQos qos; + + // std::cout <(*pyqos); + // DDS::DataReaderQos* qos = get_capsule(*pyqos); + // qos = *pyqos; + std::cout <<"testing not sure"<<"\n"; + + // std::cout <get_default_datareader_qos(qos); qos.reliability.kind = DDS::RELIABLE_RELIABILITY_QOS; + std::cout <<"create reader"<<"\n"; // Create DataReader DDS::DataReader* datareader = subscriber->create_datareader( - topic, qos, listener, - OpenDDS::DCPS::DEFAULT_STATUS_MASK); + topic, qos, listener, + OpenDDS::DCPS::DEFAULT_STATUS_MASK); if (!datareader) { + std::cout <<"fail create reader"<<"\n"; PyErr_SetString(Errors::PyOpenDDS_Error(), "Failed to Create DataReader"); return nullptr; } - + std::cout <<"update qos"<<"\n"; + bool isgoodqos = update_reader_qos(datareader,*pyqos); + std::cout <<"fin update"<<"\n"; + if (isgoodqos == true) + { + std::cout <<"YESSSSSSS !!!!!"<<"\n"; + + } + // Attach OpenDDS DataReader to DataReader Python Object if (set_capsule(*pydatareader, datareader, delete_datareader_var)) { return nullptr; @@ -731,76 +837,88 @@ PyObject* update_writer_qos(PyObject* self, PyObject* args) Py_RETURN_NONE; } -PyObject* update_reader_qos(PyObject* self, PyObject* args) -{ - Ref pydatareader; - Ref pyQos; - - Ref pydurability; - Ref pyreliability; - Ref pyhistory; - Ref pydurabilityKind; - Ref pyreliabilityKind; - Ref pyhistoryKind; - Ref pyhistorydepth; - Ref pyreliabilitymax; - - if (!PyArg_ParseTuple(args, "OO", - &*pydatareader, &*pyQos)) { - return nullptr; - } - pydatareader++; - pyQos++; - - // Get DataReader - DDS::DataReader* reader = get_capsule(*pydatareader); - if (!reader) return nullptr; - - // Create Qos for the data writer according to the spec - DDS::DataReaderQos qos; - reader->get_subscriber()->get_default_datareader_qos(qos); - - pydurability = PyObject_GetAttrString(*pyQos, "durability"); - if (!pydurability) return nullptr; - pydurability ++; - - pyreliability = PyObject_GetAttrString(*pyQos, "reliability"); - if (!pyreliability) return nullptr; - pyreliability ++; - - pyhistory = PyObject_GetAttrString(*pyQos, "history"); - if (!pyhistory) return nullptr; - pyhistory ++; +// PyObject* update_reader_qos(PyObject* self, PyObject* args) +// { +// +// Ref pydatareader; +// Ref pyQos; + +// Ref pydurability; +// Ref pyreliability; +// Ref pyhistory; +// Ref pydurabilityKind; +// Ref pyreliabilityKind; +// Ref pyhistoryKind; +// Ref pyhistorydepth; +// Ref pyreliabilitymax; + +// if (!PyArg_ParseTuple(args, "OO", +// &*pydatareader, &*pyQos)) { +// return nullptr; +// } +// pydatareader++; +// pyQos++; + +// // Get DataReader +// std::cout << "begin Datareader\n"; +// DDS::DataReader* reader = get_capsule(*pydatareader); +// if (!reader) +// { +// std::cout << "no reader\n"; +// return nullptr; +// } + +// // Create Qos for the data writer according to the spec +// DDS::DataReaderQos qos; +// reader->get_subscriber()->get_default_datareader_qos(qos); + +// pydurability = PyObject_GetAttrString(*pyQos, "durability"); +// if (!pydurability) +// { +// std::cout << "no pydurability\n"; +// return nullptr; +// } +// pydurability ++; + +// pyreliability = PyObject_GetAttrString(*pyQos, "reliability"); +// if (!pyreliability) return nullptr; +// pyreliability ++; + +// pyhistory = PyObject_GetAttrString(*pyQos, "history"); +// if (!pyhistory) return nullptr; +// pyhistory ++; + +// pydurabilityKind = PyObject_GetAttrString(*pydurability, "kind"); +// if (!pydurabilityKind) return nullptr; +// pydurabilityKind ++; +// qos.durability.kind = (DDS::DurabilityQosPolicyKind) PyLong_AsLong(*pydurabilityKind); + +// pyreliabilityKind = PyObject_GetAttrString(*pyreliability, "kind"); +// if (!pyreliabilityKind) return nullptr; +// pyreliabilityKind ++; +// qos.reliability.kind = (DDS::ReliabilityQosPolicyKind) PyLong_AsLong(*pyreliabilityKind); + +// pyreliabilitymax = PyObject_GetAttrString(*pyreliability, "max_blocking_time"); +// if (!pyreliabilitymax) return nullptr; +// pyreliabilitymax ++; +// qos.history.depth = PyLong_AsLong(*pyreliabilitymax); + +// pyhistoryKind = PyObject_GetAttrString(*pyhistory, "kind"); +// if (!pyhistoryKind) return nullptr; +// pyhistoryKind ++; + +// qos.history.kind = (DDS::HistoryQosPolicyKind) PyLong_AsLong(*pyhistoryKind); + +// pyhistorydepth = PyObject_GetAttrString(*pyhistory, "depth"); +// if (!pyhistorydepth) return nullptr; +// pyhistorydepth ++; +// qos.history.depth = PyLong_AsLong(*pyhistorydepth); + +// reader->set_qos (qos); +// Py_RETURN_NONE; +// } - pydurabilityKind = PyObject_GetAttrString(*pydurability, "kind"); - if (!pydurabilityKind) return nullptr; - pydurabilityKind ++; - qos.durability.kind = (DDS::DurabilityQosPolicyKind) PyLong_AsLong(*pydurabilityKind); - pyreliabilityKind = PyObject_GetAttrString(*pyreliability, "kind"); - if (!pyreliabilityKind) return nullptr; - pyreliabilityKind ++; - qos.reliability.kind = (DDS::ReliabilityQosPolicyKind) PyLong_AsLong(*pyreliabilityKind); - - pyreliabilitymax = PyObject_GetAttrString(*pyreliability, "max_blocking_time"); - if (!pyreliabilitymax) return nullptr; - pyreliabilitymax ++; - qos.history.depth = PyLong_AsLong(*pyreliabilitymax); - - pyhistoryKind = PyObject_GetAttrString(*pyhistory, "kind"); - if (!pyhistoryKind) return nullptr; - pyhistoryKind ++; - - qos.history.kind = (DDS::HistoryQosPolicyKind) PyLong_AsLong(*pyhistoryKind); - - pyhistorydepth = PyObject_GetAttrString(*pyhistory, "depth"); - if (!pyhistorydepth) return nullptr; - pyhistorydepth ++; - qos.history.depth = PyLong_AsLong(*pyhistorydepth); - - reader->set_qos (qos); - Py_RETURN_NONE; -} /// Documentation for Internal Python Objects const char* internal_docstr = "Internal to PyOpenDDS, not for use directly!"; @@ -819,7 +937,7 @@ PyMethodDef pyopendds_Methods[] = { { "datareader_wait_for", datareader_wait_for, METH_VARARGS, internal_docstr }, { "datawriter_wait_for", datawriter_wait_for, METH_VARARGS, internal_docstr }, { "update_writer_qos", update_writer_qos, METH_VARARGS, internal_docstr }, - { "update_reader_qos", update_reader_qos, METH_VARARGS, internal_docstr }, + // { "update_reader_qos", update_reader_qos, METH_VARARGS, internal_docstr }, { nullptr, nullptr, 0, nullptr } }; diff --git a/tests/basic_test/CMakeLists.txt b/tests/basic_test/CMakeLists.txt index d36672a..213f35b 100644 --- a/tests/basic_test/CMakeLists.txt +++ b/tests/basic_test/CMakeLists.txt @@ -17,17 +17,17 @@ 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_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) diff --git a/tests/basic_test/subscriber.py b/tests/basic_test/subscriber.py index 6a3c49e..0efc873 100644 --- a/tests/basic_test/subscriber.py +++ b/tests/basic_test/subscriber.py @@ -1,6 +1,7 @@ import sys import time from datetime import timedelta +from pyopendds.Qos import DataReaderQos from pyopendds import \ init_opendds, DomainParticipant, StatusKind, PyOpenDDS_Error @@ -23,7 +24,11 @@ def listener_func(self, sample: Reading): domain = DomainParticipant(34) topic = domain.create_topic('Readings', Reading) subscriber = domain.create_subscriber() - reader = subscriber.create_datareader(topic=topic, listener=listener.listener_func) + datareaderqos = DataReaderQos() + 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...') From 79e63424b11c08d8b933ac33b4298e753dd87e89 Mon Sep 17 00:00:00 2001 From: swolferDF Date: Thu, 4 Nov 2021 14:45:28 +0100 Subject: [PATCH 49/55] p-e-s change config QOS --- pyopendds/Qos.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyopendds/Qos.py b/pyopendds/Qos.py index 92f50fa..f504a17 100644 --- a/pyopendds/Qos.py +++ b/pyopendds/Qos.py @@ -20,19 +20,19 @@ class HistoryQosPolicyKind(IntEnum): class DurabilityQosPolicy: def __init__(self): - self.kind = DurabilityQosPolicyKind.PERSISTENT_DURABILITY_QOS + self.kind = DurabilityQosPolicyKind.VOLATILE_DURABILITY_QOS class ReliabilityQosPolicy: def __init__(self): - self.kind = ReliabilityQosPolicyKind.RELIABLE_RELIABILITY_QOS + 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 = 0 + self.depth = 1 class DataWriterQos: From 58ab2652888f93b214ccccb0bc0343c561b17ee1 Mon Sep 17 00:00:00 2001 From: swolferDF Date: Fri, 5 Nov 2021 09:26:47 +0100 Subject: [PATCH 50/55] modified function takenextsample() not blocking when subscribe --- pyopendds/dev/include/pyopendds/topictype.hpp | 47 ++++++++++--------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/pyopendds/dev/include/pyopendds/topictype.hpp b/pyopendds/dev/include/pyopendds/topictype.hpp index 7152370..ef92f8a 100644 --- a/pyopendds/dev/include/pyopendds/topictype.hpp +++ b/pyopendds/dev/include/pyopendds/topictype.hpp @@ -135,29 +135,30 @@ class TopicType: public TopicTypeBase { 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 +// #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 + std::cout<<"i'm here take next sample pyopendds \n"<take_next_sample(sample, info); @@ -166,7 +167,7 @@ class TopicType: public TopicTypeBase { // PyErr_SetString(Errors::PyOpenDDS_Error(), "reader_impl->take_next_sample() failed"); return Py_None; } -#endif +// #endif PyObject* rv = nullptr; if (info.valid_data) { Type::cpp_to_python(sample, rv); From 79b43cd53d98384e2d4b434dd0052481c7311614 Mon Sep 17 00:00:00 2001 From: swolferDF Date: Fri, 5 Nov 2021 09:46:39 +0100 Subject: [PATCH 51/55] dataReader qos Testing --- pyopendds/ext/_pyopendds.cpp | 67 ++++++++++++++++++---------------- tests/basic_test/subscriber.py | 2 + 2 files changed, 38 insertions(+), 31 deletions(-) diff --git a/pyopendds/ext/_pyopendds.cpp b/pyopendds/ext/_pyopendds.cpp index 3e651b3..7a0bf53 100644 --- a/pyopendds/ext/_pyopendds.cpp +++ b/pyopendds/ext/_pyopendds.cpp @@ -420,9 +420,9 @@ void delete_datareader_var(PyObject* reader_capsule) } } -bool update_reader_qos(DDS::DataReader* reader, Ref pyQos) +bool update_reader_qos(Ref pyQos, DDS::DataReaderQos &qos) { - std::cout << "i am in apdate_reader qos cpp\n"; + std::cout << "i am in update_reader qos cpp\n"; Ref pydurability; Ref pyreliability; Ref pyhistory; @@ -435,9 +435,8 @@ bool update_reader_qos(DDS::DataReader* reader, Ref pyQos) Ref pyreliabilityKindname; //add by me // Create Qos for the data writer according to the spec - DDS::DataReaderQos qos; - reader->get_subscriber()->get_default_datareader_qos(qos); - + + pydurability = PyObject_GetAttrString(*pyQos, "durability"); if (!pydurability) { @@ -462,14 +461,15 @@ bool update_reader_qos(DDS::DataReader* reader, Ref pyQos) pyreliabilityKind = PyObject_GetAttrString(*pyreliability, "kind"); if (!pyreliabilityKind) return false; pyreliabilityKind ++; - //add by me : - pyreliabilityKindname = PyObject_GetAttrString(*pyreliabilityKind, "name"); - if (!pyreliabilityKindname) return false; - pyreliabilityKindname ++; - std::cout<<"pyreliabilityKindname : "<set_qos (qos); - if (ret != DDS::RETCODE_OK) - { - std::cout <<"set_qos NOT WORKING ! "<<"\n"; - ACE_ERROR (( - LM_ERROR, - ACE_TEXT("(%P|%t)ERROR: set qos reader returned %d.\n"), - ret)); - } + std::cout <<"qos.history.depth : "<set_qos (qos); + // if (ret != DDS::RETCODE_OK) + // { + // std::cout <<"set_qos NOT WORKING ! "<<"\n"; + // ACE_ERROR (( + // LM_ERROR, + // ACE_TEXT("(%P|%t)ERROR: set qos reader returned %d.\n"), + // ret)); + // } std::cout <<"fin_qos"<<"\n"; return true; // Py_RETURN_NONE; @@ -555,7 +556,10 @@ PyObject* create_datareader(PyObject* self, PyObject* args ) // std::cout <get_default_datareader_qos(qos); - qos.reliability.kind = DDS::RELIABLE_RELIABILITY_QOS; + std::cout <<"update qos"<<"\n"; + bool isgoodqos = update_reader_qos(*pyqos,qos); + std::cout <<"after qos "< Date: Fri, 5 Nov 2021 10:16:22 +0100 Subject: [PATCH 52/55] dev qos datawriter (publisher) --- pyopendds/DataReader.py | 12 +- pyopendds/DataWriter.py | 14 +- pyopendds/dev/include/pyopendds/utils.hpp | 56 +---- pyopendds/ext/_pyopendds.cpp | 261 ++++++---------------- tests/basic_test/publisher.py | 7 +- 5 files changed, 86 insertions(+), 264 deletions(-) diff --git a/pyopendds/DataReader.py b/pyopendds/DataReader.py index 3f4f3ad..b58e72c 100644 --- a/pyopendds/DataReader.py +++ b/pyopendds/DataReader.py @@ -13,7 +13,7 @@ class DataReader: - def __init__(self, subscriber: Subscriber, topic: Topic, qos=None, listener: Optional[Callable[..., None]] = None): + def __init__(self, subscriber: Subscriber, topic: Topic, qos=DataReaderQos(), listener: Optional[Callable[..., None]] = None): self.topic = topic self.listener = listener self.subscriber = subscriber @@ -22,19 +22,13 @@ def __init__(self, subscriber: Subscriber, topic: Topic, qos=None, listener: Opt print(f"Iim in data reader !!!!!!!!") print(f"qos") print(self.qos) - # print(self.qos.durability.kind) - # self.update_qos(qos) + + subscriber.readers.append(self) from _pyopendds import create_datareader # noqa create_datareader(self, subscriber, topic, self.on_data_available_callback, self.qos) - def update_qos(self, qos: DataReaderQos): - # from _pyopendds import update_reader_qos - # update_reader_qos(self,qos) - # TODO: Call cpp binding to implement QoS - # return update_reader_qos(self, qos) - pass def wait_for(self, timeout: TimeDurationType, status: StatusKind = StatusKind.SUBSCRIPTION_MATCHED): from _pyopendds import datareader_wait_for # noqa diff --git a/pyopendds/DataWriter.py b/pyopendds/DataWriter.py index 304a878..5532a93 100644 --- a/pyopendds/DataWriter.py +++ b/pyopendds/DataWriter.py @@ -14,20 +14,20 @@ class DataWriter: - def __init__(self, publisher: Publisher, topic: Topic, qos=None): + def __init__(self, publisher: Publisher, topic: Topic, qos=DataWriterQos()): self.topic = topic self.publisher = publisher self.qos = qos - self.update_qos(qos) + # self.update_qos(qos) publisher.writers.append(self) from _pyopendds import create_datawriter # noqa - create_datawriter(self, publisher, topic) + create_datawriter(self, publisher, topic,self.qos) - def update_qos(self, qos: DataWriterQos): - # TODO: Call cpp binding to implement QoS - # return update_writer_qos(self, qos) - pass + # def update_qos(self, qos: DataWriterQos): + # # TODO: Call cpp binding to implement QoS + # # return update_writer_qos(self, qos) + # pass def wait_for(self, timeout: TimeDurationType, status: StatusKind = StatusKind.PUBLICATION_MATCHED): from _pyopendds import datawriter_wait_for # noqa diff --git a/pyopendds/dev/include/pyopendds/utils.hpp b/pyopendds/dev/include/pyopendds/utils.hpp index c8d6537..ba61c6a 100644 --- a/pyopendds/dev/include/pyopendds/utils.hpp +++ b/pyopendds/dev/include/pyopendds/utils.hpp @@ -86,8 +86,7 @@ namespace pyopendds { return true; const bool error = PyObject_SetAttrString(py, capsule_name, capsule); - printf("im in set_capsule ",error); - if (error==false){ printf("not good");} + // if (error==false){ printf("not good");} Py_DECREF(capsule); return error; @@ -117,58 +116,7 @@ namespace pyopendds { return instance; } - template - T* get_class(PyObject* obj){ - // Py_Initialize(); - PyObject* module = PyImport_ImportModule("pyopendds.Qos"); - assert(module != NULL); - - T* return_value = nullptr; - // reliability.kind - PyObject* capsule = PyObject_GetAttrString(obj, "reliability"); // nr - - - PyObject* pyopendds_ = PyImport_ImportModule("pyopendds"); - if (!pyopendds_) printf("not find pyopendds"); - - PyObject* PyOpenDDS_qos_= PyObject_GetAttrString(pyopendds_, "Qos"); - if (!PyOpenDDS_qos_) printf("not find pyopendds.Qos"); - - DDS::DataReaderQos DataReaderQos_ = PyObject_GetAttrString(PyOpenDDS_qos_, "DataReaderQos"); - // if (!DataReaderQos_) printf("not find pyopendds.Qos.DataReaderQos"); - - - // if (capsule) { - // if (PyCapsule_IsValid(capsule, nullptr)) { - // return_value = static_cast(PyCapsule_GetPointer(capsule, nullptr)); - // printf(return_value); - // } - // Py_DECREF(capsule); - - - // PyObject* klass = PyObject_GetAttrString(module, "DataReaderQos"); - // assert(klass != NULL); - - // PyObject* instance = PyInstance_New(klass, NULL, NULL); - // assert(instance != NULL); - - // PyObject* result = PyObject_CallMethod(instance, "durability"); - // assert(result != NULL); - - // printf("1 + 2 = %ld\n", PyInt_AsLong(result)); - // Py_Finalize(); - return return_value; - } - - // MapType dictToMap(const Py::Dict& dict) - // { - // MapType map; - // for (auto key : dict.keys()) { - // map.emplace(key.str(), asElement(dict.getItem(key))); - // } - // return map; - // } - + } // namesapce pyopendds diff --git a/pyopendds/ext/_pyopendds.cpp b/pyopendds/ext/_pyopendds.cpp index 7a0bf53..b9bf105 100644 --- a/pyopendds/ext/_pyopendds.cpp +++ b/pyopendds/ext/_pyopendds.cpp @@ -420,6 +420,59 @@ void delete_datareader_var(PyObject* reader_capsule) } } +bool update_writer_qos(Ref pyQos, DDS::DataWriterQos &qos) +{ + + Ref pydurability; + Ref pyreliability; + Ref pyhistory; + Ref pydurabilityKind; + Ref pyreliabilityKind; + Ref pyhistoryKind; + + + 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); + + + std::cerr << "return" << std::endl; + return true; +} + bool update_reader_qos(Ref pyQos, DDS::DataReaderQos &qos) { std::cout << "i am in update_reader qos cpp\n"; @@ -462,13 +515,7 @@ bool update_reader_qos(Ref pyQos, DDS::DataReaderQos &qos) if (!pyreliabilityKind) return false; pyreliabilityKind ++; qos.reliability.kind = (DDS::ReliabilityQosPolicyKind) PyLong_AsLong(*pyreliabilityKind); - // add by me : - // pyreliabilityKindname = PyObject_GetAttrString(*pyreliabilityKind, "name"); - // if (!pyreliabilityKindname) return false; - // pyreliabilityKindname ++; - // std::cout<<"pyreliabilityKindname : "<set_qos (qos); - // if (ret != DDS::RETCODE_OK) - // { - // std::cout <<"set_qos NOT WORKING ! "<<"\n"; - // ACE_ERROR (( - // LM_ERROR, - // ACE_TEXT("(%P|%t)ERROR: set qos reader returned %d.\n"), - // ret)); - // } + std::cout <<"fin_qos"<<"\n"; return true; - // Py_RETURN_NONE; } /** * create_datareader(datareader: DataReader, subscriber: Subscriber, topic: Topic, listener: pyObject) -> None @@ -511,7 +548,7 @@ PyObject* create_datareader(PyObject* self, PyObject* args ) Ref pycallback; Ref pyqos; - if (!PyArg_ParseTuple(args, "OOOOO", //add one O + if (!PyArg_ParseTuple(args, "OOOOO", &*pydatareader, &*pysubscriber, &*pytopic, &*pycallback,&*pyqos )) { return nullptr; } @@ -519,11 +556,8 @@ PyObject* create_datareader(PyObject* self, PyObject* args ) pysubscriber++; pytopic++; pycallback++; - pyqos++; //add here + pyqos++; - // std::cout << "QOS : "<<*pyqos<<"\n"; - // std::cout << typeid(*pyqos).name()<<"\n"; - // std::cout << typeid(*pysubscriber).name()<<"\n"; // Get Subscriber DDS::Subscriber* subscriber = get_capsule(*pysubscriber); @@ -543,21 +577,17 @@ PyObject* create_datareader(PyObject* self, PyObject* args ) } } - // TODO : forced qos to RELIABLE_RELIABILITY_QOS + // Create QoS DDS::DataReaderQos qos; - // std::cout <(*pyqos); - // DDS::DataReaderQos* qos = get_capsule(*pyqos); - // qos = *pyqos; std::cout <<"testing not sure"<<"\n"; // std::cout <get_default_datareader_qos(qos); std::cout <<"update qos"<<"\n"; - bool isgoodqos = update_reader_qos(*pyqos,qos); + bool isgoodqos = update_reader_qos(*pyqos,qos); std::cout <<"after qos "<(*pypublisher); @@ -630,8 +654,9 @@ PyObject* create_datawriter(PyObject* self, PyObject* args) // Create QoS DDS::DataWriterQos qos; publisher->get_default_datawriter_qos(qos); - qos.reliability.kind = DDS::RELIABLE_RELIABILITY_QOS; - + // qos.reliability.kind = DDS::RELIABLE_RELIABILITY_QOS; + bool isgoodwriterqos = update_writer_qos(pyqos,qos); + std::cout <<"after qos "<create_datawriter( topic, qos, nullptr, @@ -770,158 +795,8 @@ PyObject* datawriter_wait_for(PyObject* self, PyObject* args) Py_RETURN_NONE; } -PyObject* update_writer_qos(PyObject* self, PyObject* args) -{ - Ref pydatawriter; - Ref pyQos; - - Ref pydurability; - Ref pyreliability; - Ref pyhistory; - Ref pydurabilityKind; - Ref pyreliabilityKind; - Ref pyhistoryKind; - - if (!PyArg_ParseTuple(args, "OO", - &*pydatawriter, &*pyQos)) { - return nullptr; - } - pydatawriter++; - pyQos++; - - // Get DataWriter - DDS::DataWriter* writer = get_capsule(*pydatawriter); - if (!writer) return nullptr; - - std::cerr << "get default qos" << std::endl; - // Create Qos for the data writer according to the spec - DDS::DataWriterQos qos; - writer->get_publisher()->get_default_datawriter_qos(qos); - - std::cerr << "get durability" << std::endl; - pydurability = PyObject_GetAttrString(*pyQos, "durability"); - if (!pydurability) return nullptr; - pydurability ++; - - std::cerr << "get reliability" << std::endl; - pyreliability = PyObject_GetAttrString(*pyQos, "reliability"); - if (!pyreliability) return nullptr; - pyreliability ++; - - std::cerr << "get history" << std::endl; - pyhistory = PyObject_GetAttrString(*pyQos, "history"); - if (!pyhistory) return nullptr; - pyhistory ++; - - std::cerr << "get dura kind" << std::endl; - pydurabilityKind = PyObject_GetAttrString(*pydurability, "kind"); - if (!pydurabilityKind) return nullptr; - 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 nullptr; - 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 nullptr; - pyhistoryKind ++; - - std::cerr << "AsLong" << std::endl; - qos.history.kind = (DDS::HistoryQosPolicyKind) PyLong_AsLong(*pyhistoryKind); - - std::cerr << "set QOS" << std::endl; - writer->set_qos (qos); - - std::cerr << "return" << std::endl; - Py_RETURN_NONE; -} -// PyObject* update_reader_qos(PyObject* self, PyObject* args) -// { -// -// Ref pydatareader; -// Ref pyQos; - -// Ref pydurability; -// Ref pyreliability; -// Ref pyhistory; -// Ref pydurabilityKind; -// Ref pyreliabilityKind; -// Ref pyhistoryKind; -// Ref pyhistorydepth; -// Ref pyreliabilitymax; - -// if (!PyArg_ParseTuple(args, "OO", -// &*pydatareader, &*pyQos)) { -// return nullptr; -// } -// pydatareader++; -// pyQos++; - -// // Get DataReader -// std::cout << "begin Datareader\n"; -// DDS::DataReader* reader = get_capsule(*pydatareader); -// if (!reader) -// { -// std::cout << "no reader\n"; -// return nullptr; -// } - -// // Create Qos for the data writer according to the spec -// DDS::DataReaderQos qos; -// reader->get_subscriber()->get_default_datareader_qos(qos); - -// pydurability = PyObject_GetAttrString(*pyQos, "durability"); -// if (!pydurability) -// { -// std::cout << "no pydurability\n"; -// return nullptr; -// } -// pydurability ++; - -// pyreliability = PyObject_GetAttrString(*pyQos, "reliability"); -// if (!pyreliability) return nullptr; -// pyreliability ++; - -// pyhistory = PyObject_GetAttrString(*pyQos, "history"); -// if (!pyhistory) return nullptr; -// pyhistory ++; - -// pydurabilityKind = PyObject_GetAttrString(*pydurability, "kind"); -// if (!pydurabilityKind) return nullptr; -// pydurabilityKind ++; -// qos.durability.kind = (DDS::DurabilityQosPolicyKind) PyLong_AsLong(*pydurabilityKind); - -// pyreliabilityKind = PyObject_GetAttrString(*pyreliability, "kind"); -// if (!pyreliabilityKind) return nullptr; -// pyreliabilityKind ++; -// qos.reliability.kind = (DDS::ReliabilityQosPolicyKind) PyLong_AsLong(*pyreliabilityKind); - -// pyreliabilitymax = PyObject_GetAttrString(*pyreliability, "max_blocking_time"); -// if (!pyreliabilitymax) return nullptr; -// pyreliabilitymax ++; -// qos.history.depth = PyLong_AsLong(*pyreliabilitymax); - -// pyhistoryKind = PyObject_GetAttrString(*pyhistory, "kind"); -// if (!pyhistoryKind) return nullptr; -// pyhistoryKind ++; - -// qos.history.kind = (DDS::HistoryQosPolicyKind) PyLong_AsLong(*pyhistoryKind); - -// pyhistorydepth = PyObject_GetAttrString(*pyhistory, "depth"); -// if (!pyhistorydepth) return nullptr; -// pyhistorydepth ++; -// qos.history.depth = PyLong_AsLong(*pyhistorydepth); - -// reader->set_qos (qos); -// Py_RETURN_NONE; -// } @@ -941,7 +816,7 @@ PyMethodDef pyopendds_Methods[] = { { "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 }, - { "update_writer_qos", update_writer_qos, METH_VARARGS, internal_docstr }, + // { "update_writer_qos", update_writer_qos, METH_VARARGS, internal_docstr }, // { "update_reader_qos", update_reader_qos, METH_VARARGS, internal_docstr }, { nullptr, nullptr, 0, nullptr } }; diff --git a/tests/basic_test/publisher.py b/tests/basic_test/publisher.py index d69fe0a..7afbd78 100644 --- a/tests/basic_test/publisher.py +++ b/tests/basic_test/publisher.py @@ -1,6 +1,7 @@ import sys import time from datetime import timedelta +from pyopendds.Qos import DataWriterQos from pyopendds import \ init_opendds, DomainParticipant, StatusKind, PyOpenDDS_Error @@ -15,7 +16,11 @@ domain = DomainParticipant(34) topic = domain.create_topic('Readings', Reading) publisher = domain.create_publisher() - writer = publisher.create_datawriter(topic) + datawriterqos = DataWriterQos() + datawriterqos.history.depth = 2 + + writer = publisher.create_datawriter(topic,qos = datawriterqos) + # Wait for Subscriber to Connect print('Waiting for Subscriber...') From 1c80664ec01ac6bca4527776716af0f22b2207d4 Mon Sep 17 00:00:00 2001 From: swolferDF Date: Fri, 5 Nov 2021 16:16:27 +0100 Subject: [PATCH 53/55] Qos Working !! :tada: :tada: Verify if listener as None on DataReader add update datawriter Testing working well :ok_hand: --- pyopendds/DataReader.py | 12 ++--- pyopendds/DataWriter.py | 5 -- pyopendds/dev/include/pyopendds/topictype.hpp | 2 +- pyopendds/ext/_pyopendds.cpp | 51 ++++++++----------- 4 files changed, 28 insertions(+), 42 deletions(-) diff --git a/pyopendds/DataReader.py b/pyopendds/DataReader.py index b58e72c..fdb9d5d 100644 --- a/pyopendds/DataReader.py +++ b/pyopendds/DataReader.py @@ -18,16 +18,14 @@ def __init__(self, subscriber: Subscriber, topic: Topic, qos=DataReaderQos(), li self.listener = listener self.subscriber = subscriber self.qos = qos - # self.qos = DataReaderQos - print(f"Iim in data reader !!!!!!!!") - print(f"qos") - print(self.qos) - - subscriber.readers.append(self) from _pyopendds import create_datareader # noqa - create_datareader(self, subscriber, topic, self.on_data_available_callback, self.qos) + #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): diff --git a/pyopendds/DataWriter.py b/pyopendds/DataWriter.py index 5532a93..8c77d8e 100644 --- a/pyopendds/DataWriter.py +++ b/pyopendds/DataWriter.py @@ -18,16 +18,11 @@ def __init__(self, publisher: Publisher, topic: Topic, qos=DataWriterQos()): self.topic = topic self.publisher = publisher self.qos = qos - # self.update_qos(qos) publisher.writers.append(self) from _pyopendds import create_datawriter # noqa create_datawriter(self, publisher, topic,self.qos) - # def update_qos(self, qos: DataWriterQos): - # # TODO: Call cpp binding to implement QoS - # # return update_writer_qos(self, qos) - # pass def wait_for(self, timeout: TimeDurationType, status: StatusKind = StatusKind.PUBLICATION_MATCHED): from _pyopendds import datawriter_wait_for # noqa diff --git a/pyopendds/dev/include/pyopendds/topictype.hpp b/pyopendds/dev/include/pyopendds/topictype.hpp index ef92f8a..e1cfffe 100644 --- a/pyopendds/dev/include/pyopendds/topictype.hpp +++ b/pyopendds/dev/include/pyopendds/topictype.hpp @@ -158,7 +158,7 @@ class TopicType: public TopicTypeBase { // } // #else // TODO: fallback to naive implementation - std::cout<<"i'm here take next sample pyopendds \n"<take_next_sample(sample, info); diff --git a/pyopendds/ext/_pyopendds.cpp b/pyopendds/ext/_pyopendds.cpp index b9bf105..5529260 100644 --- a/pyopendds/ext/_pyopendds.cpp +++ b/pyopendds/ext/_pyopendds.cpp @@ -429,53 +429,57 @@ bool update_writer_qos(Ref pyQos, DDS::DataWriterQos &qos) Ref pydurabilityKind; Ref pyreliabilityKind; Ref pyhistoryKind; + Ref pyhistorydepth; - std::cerr << "get durability" << std::endl; + // std::cerr << "get durability" << std::endl; pydurability = PyObject_GetAttrString(*pyQos, "durability"); if (!pydurability) return false; pydurability ++; - std::cerr << "get reliability" << std::endl; + // std::cerr << "get reliability" << std::endl; pyreliability = PyObject_GetAttrString(*pyQos, "reliability"); if (!pyreliability) return false; pyreliability ++; - std::cerr << "get history" << std::endl; + // std::cerr << "get history" << std::endl; pyhistory = PyObject_GetAttrString(*pyQos, "history"); if (!pyhistory) return false; pyhistory ++; - std::cerr << "get dura kind" << std::endl; + // std::cerr << "get dura kind" << std::endl; pydurabilityKind = PyObject_GetAttrString(*pydurability, "kind"); if (!pydurabilityKind) return false; pydurabilityKind ++; - std::cerr << "AsLong" << std::endl; + // std::cerr << "AsLong" << std::endl; qos.durability.kind = (DDS::DurabilityQosPolicyKind) PyLong_AsLong(*pydurabilityKind); - std::cerr << "get rela kind" << std::endl; + // std::cerr << "get rela kind" << std::endl; pyreliabilityKind = PyObject_GetAttrString(*pyreliability, "kind"); if (!pyreliabilityKind) return false; pyreliabilityKind ++; - std::cerr << "AsLong" << std::endl; + // std::cerr << "AsLong" << std::endl; qos.reliability.kind = (DDS::ReliabilityQosPolicyKind) PyLong_AsLong(*pyreliabilityKind); - std::cerr << "get histo kind" << std::endl; + // std::cerr << "get histo kind" << std::endl; pyhistoryKind = PyObject_GetAttrString(*pyhistory, "kind"); if (!pyhistoryKind) return false; pyhistoryKind ++; - std::cerr << "AsLong" << std::endl; + // 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 : "<get_default_datareader_qos(qos); - std::cout <<"update qos"<<"\n"; bool isgoodqos = update_reader_qos(*pyqos,qos); - std::cout <<"after qos "<create_datareader( topic, qos, listener, OpenDDS::DCPS::DEFAULT_STATUS_MASK); if (!datareader) { - std::cout <<"fail create reader"<<"\n"; PyErr_SetString(Errors::PyOpenDDS_Error(), "Failed to Create DataReader"); return nullptr; } @@ -650,13 +646,12 @@ PyObject* create_datawriter(PyObject* self, PyObject* args) DDS::Topic* topic = get_capsule(*pytopic); if (!topic) return nullptr; - // TODO : force qos to DDS::RELIABLE_RELIABILITY_QOS // 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 qos "<create_datawriter( topic, qos, nullptr, @@ -816,8 +811,6 @@ PyMethodDef pyopendds_Methods[] = { { "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 }, - // { "update_writer_qos", update_writer_qos, METH_VARARGS, internal_docstr }, - // { "update_reader_qos", update_reader_qos, METH_VARARGS, internal_docstr }, { nullptr, nullptr, 0, nullptr } }; From 6578eacb0e3bec0d5c75c004c328ef69d47e5e1b Mon Sep 17 00:00:00 2001 From: swolferDF Date: Fri, 5 Nov 2021 17:28:36 +0100 Subject: [PATCH 54/55] remove print --- pyopendds/Subscriber.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyopendds/Subscriber.py b/pyopendds/Subscriber.py index c4eb925..9efc004 100644 --- a/pyopendds/Subscriber.py +++ b/pyopendds/Subscriber.py @@ -19,7 +19,6 @@ def __init__(self, participant: DomainParticipant, qos=None, listener=None): create_subscriber(self, participant) def create_datareader(self, topic: Topic, qos=None, listener=None) -> DataReader: - print(f"i creat data reader from subscriber") reader = DataReader(self, topic, qos, listener) self.readers.append(reader) return reader From df314dfd471e489f5f4a82bb78f7214c4356c9bf Mon Sep 17 00:00:00 2001 From: swolferDF Date: Tue, 9 Nov 2021 10:33:40 +0100 Subject: [PATCH 55/55] add topicname optional arg callaback --- pyopendds/DataReader.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pyopendds/DataReader.py b/pyopendds/DataReader.py index fdb9d5d..1259fb1 100644 --- a/pyopendds/DataReader.py +++ b/pyopendds/DataReader.py @@ -37,8 +37,14 @@ def take_next_sample(self) -> Any: 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: - self.listener(sample) + try: # if callback have 2 arguments + self.listener(sample,topicname) + except : # if callback have 1 arguments + self.listener(sample)