├── .gitattributes ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE.pdf ├── README.md ├── README.rst ├── VERSION ├── __init__.py ├── docs ├── Makefile ├── advanced.rst ├── conf.py ├── configuration.rst ├── connector.rst ├── copyright_license.rst ├── data.rst ├── errors.rst ├── features.rst ├── getting_started.rst ├── index.rst ├── input.rst ├── intro.rst ├── make.bat ├── output.rst ├── release_notes.rst ├── requirements.txt ├── static │ ├── favicon.ico │ ├── overview.png │ ├── py-logo.png │ ├── rti-logo-FINALv2-White-OrangeDot.png │ ├── theme_overrides.css │ └── xml_doc.png ├── threading.rst └── vars.rst ├── examples └── python │ ├── ShapeExample.xml │ ├── images │ ├── ImagesExample.xml │ ├── README.md │ ├── image_reader.py │ ├── image_reader_file.py │ └── image_writer.py │ ├── simple │ ├── README.md │ ├── reader.py │ └── writer.py │ └── transformation │ ├── README.md │ ├── reader.py │ └── transform.py ├── resources ├── docker │ ├── Dockerfile │ └── documentation.Dockerfile └── jenkins │ ├── build_and_test.groovy │ └── build_doc.groovy ├── rticonnextdds_connector ├── __init__.py └── rticonnextdds_connector.py ├── setup.py ├── test ├── python │ ├── README.md │ ├── conftest.py │ ├── test_rticonnextdds_connector.py │ ├── test_rticonnextdds_data_access.py │ ├── test_rticonnextdds_data_iterators.py │ ├── test_rticonnextdds_dataflow.py │ ├── test_rticonnextdds_discovery.py │ ├── test_rticonnextdds_input.py │ ├── test_rticonnextdds_metadata.py │ ├── test_rticonnextdds_output.py │ ├── test_rticonnextdds_performance.py │ ├── test_rticonnextdds_threading.py │ └── test_utils.py └── xml │ ├── InvalidXml.xml │ ├── TestConnector.xml │ ├── TestConnector2.xml │ └── TestConnector3.xml └── tox.ini /.gitattributes: -------------------------------------------------------------------------------- 1 | LICENSE.pdf filter=lfs diff=lfs merge=lfs -text 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | 7 | # Distribution / packaging 8 | .Python 9 | env/ 10 | bin/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | eggs/ 15 | lib64/ 16 | parts/ 17 | sdist/ 18 | var/ 19 | *.egg-info/ 20 | .installed.cfg 21 | *.egg 22 | 23 | # Installer logs 24 | pip-log.txt 25 | pip-delete-this-directory.txt 26 | 27 | # Unit test / coverage reports 28 | htmlcov/ 29 | .tox/ 30 | .coverage 31 | .cache 32 | nosetests.xml 33 | coverage.xml 34 | 35 | # Translations 36 | *.mo 37 | 38 | # Mr Developer 39 | .mr.developer.cfg 40 | .project 41 | .pydevproject 42 | 43 | # Rope 44 | .ropeproject 45 | 46 | # Django stuff: 47 | *.log 48 | *.pot 49 | 50 | # Sphinx documentation 51 | docs/_build/ 52 | docs/ShapeExample.xml 53 | 54 | # node.js dependencies 55 | node_modules/ 56 | 57 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to RTI Connector for Python 2 | 3 | ### Contributor License Agreement (CLA) 4 | In order to accept your pull request, we need you to sign a Contributor License Agreement (CLA). Complete your CLA here: http://community.rti.com/cla. You only need to do this once, we cross-check your Github username with the list of contributors who have signed the CLA. 5 | -------------------------------------------------------------------------------- /LICENSE.pdf: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:9b280c8dd7e1ab60fbdcd4671ef10f6b302ba055d3db313c1d5352e6f97e23bd 3 | size 125010 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | RTI Connector for Python 3 | ======================== 4 | 5 | RTI® Connext® DDS is a connectivity software framework for integrating data 6 | sources of all types. At its core is the world's leading ultra-high performance, 7 | distributed networking databus. 8 | 9 | *RTI Connector* provides a quick and easy way to write applications that 10 | publish and subscribe to the *RTI Connext DDS databus* in Python and other 11 | languages. 12 | 13 | **Note**: With the introduction of the RTI Connext Python API in Connext 7.3.0 LTS, 14 | *Connector for Python* is deprecated for Connext DDS 6.1.2 users, 15 | and removed in Connext 7.3.0. To learn more about the Connext Python API, see 16 | [this documentation](https://community.rti.com/static/documentation/connext-dds/current/doc/api/connext_dds/api_python/index.html). 17 | 18 | ## Documentation 19 | 20 | To get started and learn more about *RTI Connector for Python* see the 21 | [documentation here](https://community.rti.com/static/documentation/connector/current/api/python/index.html). 22 | 23 | ## Examples 24 | 25 | The `examples/python` directory provides several examples: 26 | 27 | * `simple` shows how to create basic publisher and subscriber applications 28 | * In `transformation`, an application reads, transforms and publishes back the data 29 | * `images` shows how to manipulate more complex data types 30 | 31 | ## License 32 | RTI Connector for JavaScript and RTI Connector for Python are part of the Connext 33 | DDS Professional Package. If you have a valid license for the RTI Connext DDS 34 | Professional Package, such license shall govern your use of RTI Connector for 35 | Python and RTI Connector for JavaScript. All other use of this software shall 36 | be governed solely by the terms of RTI’s Software License for Non-Commercial 37 | Use #4040, included at the top level of the `Connector for Python repository -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | RTI Connector for Python 2 | ======================== 3 | 4 | RTI® Connext® DDS is a connectivity software framework for integrating 5 | data sources of all types. At its core is the world's leading ultra-high 6 | performance, distributed networking databus. 7 | 8 | *RTI Connector* provides a quick and easy way to write applications that 9 | publish and subscribe to the *RTI Connext DDS databus* in Python and 10 | other languages. 11 | 12 | **Note:** 13 | With the introduction of the RTI Connext Python API in Connext 7.3.0 LTS, 14 | *Connector for Python* is deprecated for Connext DDS 6.1.2 users, 15 | and removed in Connext 7.3.0. To learn more about the Connext Python API, see 16 | `this documentation `__. 17 | 18 | Documentation 19 | ------------- 20 | 21 | To get started and learn more about *RTI Connector for Python* see the 22 | `documentation 23 | here `__. 24 | 25 | Examples 26 | -------- 27 | 28 | The ``examples/python`` directory provides several examples: 29 | 30 | - ``simple`` shows how to create basic publisher and subscriber 31 | applications 32 | - In ``transformation``, an application reads, transforms and publishes 33 | back the data 34 | - ``images`` shows how to manipulate more complex data types 35 | 36 | License 37 | ------- 38 | 39 | RTI Connector for JavaScript and RTI Connector for Python are part of 40 | the Connext DDS Professional Package. If you have a valid license for 41 | the RTI Connext DDS Professional Package, such license shall govern your 42 | use of RTI Connector for Python and RTI Connector for JavaScript. All 43 | other use of this software shall be governed solely by the terms of 44 | RTI’s Software License for Non-Commercial Use #4040, included at the top 45 | level of the \`Connector for Python repository 46 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 6.1.2.21 -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | from .rticonnextdds_connector import * 2 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # Typical usage: 5 | # - make html - to build the documentation 6 | # - make test - to test the code snippets in the documentation 7 | 8 | # You can set these variables from the command line, and also 9 | # from the environment for the first two. 10 | SPHINXOPTS ?= 11 | SPHINXBUILD ?= sphinx-build 12 | SOURCEDIR = . 13 | BUILDDIR = _build 14 | 15 | # Put it first so that "make" without argument is like "make help". 16 | help: 17 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 18 | 19 | .PHONY: help Makefile 20 | 21 | 22 | # Executes the code snippets in the documentation 23 | test: Makefile 24 | @cp ../examples/python/ShapeExample.xml . 25 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) -b doctest $(O) 26 | 27 | # Catch-all target: route all unknown targets to Sphinx using the new 28 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 29 | %: Makefile 30 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 31 | -------------------------------------------------------------------------------- /docs/advanced.rst: -------------------------------------------------------------------------------- 1 | Advanced Topics 2 | =============== 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | data 8 | threading 9 | errors 10 | features 11 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # http://www.sphinx-doc.org/en/master/config 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | import os 14 | import sys 15 | sys.path.insert(0, os.path.abspath('../rticonnextdds_connector')) 16 | 17 | 18 | # -- Project information ----------------------------------------------------- 19 | 20 | project = 'RTI Connector for Python' 21 | copyright = '2024, Real-Time Innovations, Inc' 22 | author = 'Real-Time Innovations, Inc.' 23 | 24 | # The full version, including alpha/beta/rc tags 25 | version = '1.2.4' 26 | release = '1.2.4' 27 | 28 | master_doc = 'index' 29 | 30 | # -- General configuration --------------------------------------------------- 31 | 32 | # Add any Sphinx extension module names here, as strings. They can be 33 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 34 | # ones. 35 | extensions = [ 36 | "sphinx.ext.autodoc", 37 | "sphinx.ext.autosectionlabel", 38 | "sphinx.ext.doctest" 39 | ] 40 | 41 | # Add any paths that contain templates here, relative to this directory. 42 | templates_path = ['_templates'] 43 | 44 | # List of patterns, relative to source directory, that match files and 45 | # directories to ignore when looking for source files. 46 | # This pattern also affects html_static_path and html_extra_path. 47 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 48 | 49 | 50 | # -- Options for HTML output ------------------------------------------------- 51 | 52 | # The theme to use for HTML and HTML Help pages. See the documentation for 53 | # a list of builtin themes. 54 | # 55 | html_theme = 'sphinx_rtd_theme' 56 | 57 | # Add any paths that contain custom static files (such as style sheets) here, 58 | # relative to this directory. They are copied after the builtin static files, 59 | # so a file named "default.css" will overwrite the builtin "default.css". 60 | html_static_path = ['static'] 61 | 62 | def setup(app): 63 | app.add_stylesheet('theme_overrides.css') 64 | 65 | 66 | # Theme options are theme-specific and customize the look and feel of a theme 67 | # further. For a list of options available for each theme, see the 68 | # documentation. 69 | # 70 | html_theme_options = { 71 | "collapse_navigation" : False, 72 | "canonical_url" : "https://community.rti.com/static/documentation/connector/current/api/python/" 73 | } 74 | 75 | # The name of an image file (relative to this directory) to place at the top 76 | # of the sidebar. 77 | # 78 | html_logo = "static/rti-logo-FINALv2-White-OrangeDot.png" 79 | 80 | # The name of an image file (relative to this directory) to use as a favicon of 81 | # the docs. This file should be a Windows icon file (.ico) being 16x16 or 82 | # 32x32 pixels large. 83 | # 84 | html_favicon = "static/favicon.ico" 85 | 86 | -------------------------------------------------------------------------------- /docs/configuration.rst: -------------------------------------------------------------------------------- 1 | 2 | .. py:currentmodule:: rticonnextdds_connector 3 | 4 | Defining a DDS System in XML 5 | ============================ 6 | 7 | *Connector* loads the definition of a DDS system from an XML configuration file 8 | that includes the definition of domains, *DomainParticipants*, *Topics*, 9 | *DataReaders* and *DataWriters*, data types and quality of service. 10 | 11 | .. image:: static/xml_doc.png 12 | :align: center 13 | 14 | *Connector* uses the XML schema defined by RTI's 15 | `XML-Based Application Creation feature 16 | `__. 17 | 18 | .. hint:: 19 | The *Connext DDS* C, C++, Java and .NET APIs can also load the same XML files 20 | you write for *Connector*. 21 | 22 | The following table summarizes the XML tags, the DDS concepts they define, and 23 | how they are exposed in the *Connector* API: 24 | 25 | .. list-table:: XML Configuration Tags 26 | :header-rows: 1 27 | 28 | * - XML Tag 29 | - DDS Concept 30 | - Connector API 31 | * - ```` 32 | - *DDS data types* (the types associated with *Topics*) 33 | - Types used by inputs and outputs (:class:`Input` and :class:`Output`). 34 | * - ````, ````, ```` and ```` 35 | - *DDS Domain*, *Topic* 36 | - Defines the domain joined by a :class:`Connector` and the *Topics* used by 37 | its inputs and outputs (:class:`Input` and :class:`Output`). 38 | * - ```` and ```` 39 | - *DomainParticipant* 40 | - Each :class:`Connector` instance loads a ````. See :ref:`Loading a Connector`. 41 | * - ```` and ```` 42 | - *Publisher* and *DataWriter* 43 | - Each ```` defines an :class:`Output`. See :ref:`Writing Data (Output)`. 44 | * - ```` and ```` 45 | - *Subscriber* and *DataReader* 46 | - Each ```` defines an :class:`Input`. See :ref:`Reading Data (Input)`. 47 | * - ```` and ```` 48 | - Quality of service (QoS) 49 | - Quality of service used to configure :class:`Connector`, :class:`Input` 50 | and :class:`Output`. 51 | 52 | .. hint:: 53 | 54 | For an example configuration file, see `ShapeExample.xml 55 | `__. 56 | 57 | Data types 58 | ~~~~~~~~~~ 59 | 60 | The ```` tags define the data types associated with the *Topics* to be published 61 | or subscribed to. 62 | 63 | The following example defines a *ShapeType* with four members: ``color``, ``x``, ``y`` 64 | and ``shapesize``: 65 | 66 | .. code-block:: xml 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | Types are associated with *Topics*, as explained in the next section, :ref:`Domain Library`. 78 | 79 | .. hint:: 80 | You can define your types in IDL and convert them to XML with `rtiddsgen `__. 81 | For example: ``rtiddsgen -convertToXml MyTypes.idl`` 82 | 83 | For more information about defining types, see 84 | `Creating User Data Types with XML `__ 85 | in the *RTI Connext DDS Core Libraries User's Manual*. 86 | 87 | For more information about accessing data samples, see :ref:`Accessing the data`. 88 | 89 | Domain library 90 | ~~~~~~~~~~~~~~ 91 | 92 | A domain library is a collection of domains. A domain specifies: 93 | 94 | * A `domain id `__. 95 | * A set of registered types (from a subset of the types in ````). 96 | A registered type can have a local name. 97 | * A set of `topics `__, 98 | which are used by *DataReaders* and *DataWriters*. 99 | 100 | .. code-block:: xml 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | For more information about the format of a domain library, see 111 | `XML-Based Application Creation: Domain Library `__. 112 | 113 | Participant library 114 | ~~~~~~~~~~~~~~~~~~~ 115 | 116 | A *DomainParticipant* joins a domain and contains *Publishers* and *Subscribers*, 117 | which contain *DataWriters* and *DataReaders*, respectively. 118 | 119 | Each :class:`Connector` instance created by your application is associated with a 120 | ````, as explained in :ref:`Loading a Connector`. 121 | 122 | *DataReaders* and *DataWriters* are associated with a *DomainParticipant* and a 123 | *Topic*. In *Connector*, each ```` tag defines an :class:`Output`, 124 | as described in :ref:`Writing data (Output)`; and each ```` tag defines 125 | an :class:`Input`, as described in :ref:`Reading data (Input)`. 126 | 127 | .. code-block:: xml 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | For more information about the format of a participant library, see 144 | `XML-Based Application Creation: Participant Library 145 | `__. 146 | 147 | Quality of service 148 | ~~~~~~~~~~~~~~~~~~ 149 | 150 | All DDS entities have an associated `quality of service (QoS) `__. 151 | There are several ways to configure it. 152 | 153 | You can define a QoS profile and make it the default. The following example 154 | configures all *DataReaders* and *DataWriters* with reliable and transient-local QoS: 155 | 156 | .. code-block:: xml 157 | 158 | 159 | 160 | 161 | 162 | RELIABLE_RELIABILITY_QOS 163 | 164 | 165 | TRANSIENT_LOCAL_DURABILITY_QOS 166 | 167 | 168 | 169 | 170 | RELIABLE_RELIABILITY_QOS 171 | 172 | 173 | TRANSIENT_LOCAL_DURABILITY_QOS 174 | 175 | 176 | 177 | 178 | 179 | You can define the QoS for each individual entity: 180 | 181 | .. code-block:: xml 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | RELIABLE_RELIABILITY_QOS 191 | 192 | 193 | TRANSIENT_LOCAL_DURABILITY_QOS 194 | 195 | 196 | 197 | 198 | 199 | 200 | Or you can use profiles and override or define additional QoS policies for each 201 | entity: 202 | 203 | .. code-block:: xml 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | You can also use builtin profiles and QoS snippets. For example, the following 222 | profile is equivalent to *MyQosProfile* above: 223 | 224 | .. code-block:: xml 225 | 226 | 227 | 228 | 229 | BuiltinQosSnippetLib::QosPolicy.Durability.TransientLocal 230 | BuiltinQosSnippetLib::QosPolicy.Reliability.Reliable 231 | 232 | 233 | 234 | 235 | You can read more in the *RTI Connext DDS Core Libraries User's Manual*, 236 | `Configuring QoS with XML 237 | `__. 238 | 239 | Logging 240 | ^^^^^^^ 241 | 242 | Logging can be configured as explained in `Configuring Logging via XML `__. 243 | 244 | For example, to increase the logging verbosity from the default (ERROR) to 245 | WARNING, define a ``qos_profile`` with the attribute 246 | ``is_default_participant_factory_profile="true"``: 247 | 248 | .. code-block:: xml 249 | 250 | 251 | 252 | 253 | WARNING 254 | 255 | 256 | 257 | 258 | -------------------------------------------------------------------------------- /docs/connector.rst: -------------------------------------------------------------------------------- 1 | Loading a Connector 2 | =================== 3 | 4 | .. py:currentmodule:: rticonnextdds_connector 5 | 6 | Importing the Connector package 7 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 8 | 9 | To use the ``rticonnextdds_connector`` package, import it. For example: 10 | 11 | .. testcode:: 12 | 13 | import rticonnextdds_connector as rti 14 | 15 | Creating a new Connector 16 | ~~~~~~~~~~~~~~~~~~~~~~~~ 17 | 18 | To create a new :class:`Connector`, pass an XML file and a configuration name: 19 | 20 | .. testcode:: 21 | 22 | connector = rti.Connector("MyParticipantLibrary::MyParticipant", "ShapeExample.xml"); 23 | 24 | The XML file defines your types, QoS profiles, and DDS Entities. *Connector* 25 | uses the XML schema of `RTI's XML-Based Application Creation `__. 26 | 27 | The previous code loads the ```` named *MyParticipant* in 28 | the ```` named *MyParticipantLibrary*, which is defined in the 29 | file ``ShapeExample.xml``:: 30 | 31 | 32 | 33 | ... 34 | 35 | 36 | 37 | See the full file here: `ShapeExample.xml `__. 38 | 39 | When you create a :class:`Connector`, the DDS *DomainParticipant* that you selected 40 | and all its contained entities (*Topics*, *Subscribers*, *DataReaders*, 41 | *Publishers*, *DataWriters*) are created. 42 | 43 | For more information about the DDS entities, see `Core Concepts `__ 44 | in the *RTI Connext DDS Core Libraries User's Manual*. 45 | 46 | .. note:: 47 | 48 | Operations on the same :class:`Connector` instance or its contained entities are 49 | not protected for multi-threaded access. See :ref:`Threading model` for more 50 | information. 51 | 52 | Closing a Connector 53 | ~~~~~~~~~~~~~~~~~~~ 54 | 55 | To destroy all the DDS entities that belong to a previously created :class:`Connector`, 56 | call :meth:`Connector.close()`: 57 | 58 | .. testcode:: 59 | 60 | connector = rti.Connector("MyParticipantLibrary::MyParticipant", "ShapeExample.xml") 61 | # ... 62 | connector.close() 63 | 64 | Alternatively, you can use the :meth:`open_connector` resource manager to open 65 | and automatically close the connector: 66 | 67 | .. testcode:: 68 | 69 | with rti.open_connector("MyParticipantLibrary::MyParticipant", "ShapeExample.xml") as connector: 70 | # Use connector 71 | input = connector.get_input("MySubscriber::MySquareReader") 72 | # ... 73 | 74 | 75 | Getting the Inputs and Outputs 76 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 77 | 78 | Once you have created a :class:`Connector` instance, :meth:`Connector.get_output()` 79 | returns the :class:`Output` that allows writing data, and :meth:`Connector.get_input()` 80 | returns the :class:`Input` that allows reading data. 81 | 82 | .. note:: 83 | 84 | If the ```` you load contains both ```` (Output) and 85 | ```` (Input) tags for the same *Topic* and they have matching QoS, 86 | when you write data, the Inputs will receive the data even before you call 87 | :meth:`Connector.get_input()`. To avoid that, you can configure the 88 | ```` that contains the ```` with 89 | ``//`` set to 90 | ``false``. Then the Inputs will only receive data after you call 91 | :meth:`Connector.get_input()`. 92 | 93 | For more information see: 94 | 95 | * :ref:`Writing data (Output)` 96 | * :ref:`Reading data (Input)` 97 | 98 | Class reference: Connector 99 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 100 | 101 | .. autoclass:: rticonnextdds_connector.Connector 102 | :members: 103 | 104 | .. autofunction:: rticonnextdds_connector.open_connector 105 | -------------------------------------------------------------------------------- /docs/copyright_license.rst: -------------------------------------------------------------------------------- 1 | 2 | .. include:: vars.rst 3 | 4 | .. _section-copyright: 5 | 6 | Copyrights and License 7 | ********************** 8 | 9 | © 2024 Real-Time Innovations, Inc. |br| 10 | All rights reserved. |br| 11 | Printed in U.S.A. First printing. |br| 12 | October 2024. |br| 13 | 14 | 15 | .. rubric:: License 16 | 17 | RTI Connector for JavaScript and RTI Connector for Python are part of the Connext 18 | DDS Professional Package. If you have a valid license for the RTI Connext DDS 19 | Professional Package, such license shall govern your use of RTI Connector for 20 | Python and RTI Connector for JavaScript. All other use of this software shall 21 | be governed solely by the terms of RTI’s Software License for Non-Commercial 22 | Use #4040, included at the top level of the `Connector for Python repository 23 | `__. 24 | 25 | 26 | .. rubric:: Trademarks 27 | 28 | RTI, Real-Time Innovations, Connext, NDDS, the RTI logo, 1RTI and the 29 | phrase, “Your Systems. Working as one.” are registered trademarks, trademarks 30 | or service marks of Real-Time Innovations, Inc. All other trademarks belong to 31 | their respective owners. 32 | 33 | .. rubric:: Copy and Use Restrictions 34 | 35 | No part of this publication may be reproduced, stored in a retrieval system, 36 | or transmitted in any form (including electronic, mechanical, photocopy, and 37 | facsimile) without the prior written permission of Real-Time Innovations, Inc. 38 | The software described in this document is furnished under and subject to the 39 | RTI software license agreement. The software may be used or copied only under 40 | the terms of the license agreement. 41 | 42 | This is an independent publication and is neither affiliated with, nor 43 | authorized, sponsored, or approved by, Microsoft Corporation. 44 | 45 | The security features of this product include software developed by the 46 | OpenSSL Project for use in the OpenSSL Toolkit (http://www.openssl.org/). 47 | This product includes cryptographic software written by Eric Young 48 | (eay@cryptsoft.com). This product includes software written by Tim Hudson 49 | (tjh@cryptsoft.com). 50 | 51 | Technical Support |br| 52 | Real-Time Innovations, Inc. |br| 53 | 232 E. Java Drive |br| 54 | Sunnyvale, CA 94089 |br| 55 | Phone: (408) 990-7444 |br| 56 | Email: support@rti.com |br| 57 | Website: https://support.rti.com/ |br| 58 | 59 | 60 | .. rubric:: External Third-Party Software Used in Connector 61 | 62 | **Lua** 63 | * The source code of this software is used to build the native libraries 64 | provided by *RTI Connector*. 65 | 66 | * Version 5.2. 67 | 68 | * License (https://www.lua.org/license.html): 69 | Copyright © 1994-2021 Lua.org, PUC-Rio. 70 | 71 | Permission is hereby granted, free of charge, to any person obtaining a 72 | copy of this software and associated documentation files (the "Software"), 73 | to deal in the Software without restriction, including without limitation 74 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 75 | and/or sell copies of the Software, and to permit persons to whom the 76 | Software is furnished to do so, subject to the following conditions: 77 | 78 | The above copyright notice and this permission notice shall be included 79 | in all copies or substantial portions of the Software. 80 | 81 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 82 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 83 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 84 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 85 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 86 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 87 | THE SOFTWARE. 88 | 89 | **json-parser** 90 | * The source code of this software (from https://github.com/json-parser/json-parser) 91 | is used to build the native libraries provided by *RTI Connector*. 92 | 93 | * License: 94 | 95 | Copyright (C) 2012-2021 the json-parser authors. All rights reserved. 96 | https://github.com/udp/json-parser 97 | 98 | Redistribution and use in source and binary forms, with or without 99 | modification, are permitted provided that the following conditions 100 | are met: 101 | 102 | 1. Redistributions of source code must retain the above copyright 103 | notice, this list of conditions and the following disclaimer. 104 | 105 | 2. Redistributions in binary form must reproduce the above copyright 106 | notice, this list of conditions and the following disclaimer in the 107 | documentation and/or other materials provided with the distribution. 108 | 109 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 110 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 111 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 112 | ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 113 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 114 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 115 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 116 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 117 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 118 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 119 | SUCH DAMAGE. 120 | -------------------------------------------------------------------------------- /docs/data.rst: -------------------------------------------------------------------------------- 1 | Accessing the data 2 | ================== 3 | 4 | .. py:currentmodule:: rticonnextdds_connector 5 | 6 | .. testsetup:: * 7 | 8 | import rticonnextdds_connector as rti, time 9 | connector = rti.Connector("MyParticipantLibrary::DataAccessTest", "../test/xml/TestConnector.xml") 10 | output = connector.get_output("TestPublisher::TestWriter") 11 | input = connector.get_input("TestSubscriber::TestReader") 12 | output.instance.set_number("my_int_sequence[9]", 10) 13 | output.instance.set_number("my_point_sequence[9].x", 10) 14 | output.instance.set_number("my_optional_long", 10) 15 | output.instance.set_number("my_optional_point.x", 10) 16 | output.write() 17 | input.wait(2000) 18 | input.take() 19 | 20 | The types you use to write or read data may included nested structs, sequences and 21 | arrays of primitive types or structs, etc. 22 | 23 | These types are defined in XML, as described in :ref:`Data Types`. 24 | 25 | To access the data, :class:`Instance` and :class:`SampleIterator` provide 26 | setters and getters that expect a ``fieldName`` string. This section describes 27 | the format of this string. 28 | 29 | We will use the following type definition of MyType: 30 | 31 | .. code-block:: xml 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | The above XML corresponds to the following IDL definition: 70 | 71 | .. code-block:: idl 72 | 73 | enum Color { 74 | RED, 75 | GREEN, 76 | BLUE 77 | }; 78 | 79 | struct Point { 80 | long x; 81 | long y; 82 | }; 83 | 84 | union MyUnion switch(Color) { 85 | case RED: Point point; 86 | case GREEN: string<512> my_string; 87 | }; 88 | 89 | struct MyType { 90 | long my_long; 91 | double my_double; 92 | Color my_enum; 93 | boolean my_boolean; 94 | string<512> my_string; 95 | Point my_point; 96 | MyUnion my_union; 97 | sequence my_int_sequence; 98 | sequence my_point_sequence; 99 | Point my_point_array[3]; 100 | @optional Point my_optional_point; 101 | @optional long my_optional_long; 102 | }; 103 | 104 | .. hint:: 105 | You can get the XML definition of an IDL file with ``rtiddsgen -convertToXml MyType.idl``. 106 | 107 | We will refer to an Output named ``output`` and 108 | an Input named ``input`` such that ``input.samples.length > 0``. 109 | 110 | Using dictionaries vs. accessing individual members 111 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 112 | 113 | For an Input or Output, you can access the data all at once by using a dictionary, 114 | or member by member. Using a dictionary is usually more efficient if you intend 115 | to access most or all of the data members of a large type. 116 | 117 | In an Output, :meth:`Instance.set_dictionary` receives a dictionary with all or 118 | some of the Output type members. In an Input, :meth:`SampleIterator.get_dictionary` 119 | retrieves all the members. 120 | 121 | It is also possible to provide a ``member_name`` to 122 | :meth:`SampleIterator.get_dictionary` to obtain 123 | a dictionary that only contains the fields of that nested member. 124 | 125 | The methods described in the following section receive a 126 | ``field_name`` argument to get or set a specific member. 127 | 128 | Accessing basic members (numbers, strings and booleans) 129 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 130 | 131 | To set a field in an :class:`Output`, use the appropriate setter. 132 | 133 | To set any numeric type, including enumerations: 134 | 135 | .. testcode:: 136 | 137 | output.instance.set_number("my_long", 2) 138 | output.instance.set_number("my_double", 2.14) 139 | output.instance.set_number("my_enum", 2) 140 | 141 | To set booleans: 142 | 143 | .. testcode:: 144 | 145 | output.instance.set_boolean("my_boolean", True) 146 | 147 | To set strings: 148 | 149 | .. testcode:: 150 | 151 | output.instance.set_string("my_string", "Hello, World!") 152 | 153 | 154 | As an alternative to the setters mentioned above, you can use the special 155 | method ``__setitem__`` as follows: 156 | 157 | .. testcode:: 158 | 159 | output.instance["my_double"] = 2.14 160 | output.instance["my_boolean"] = True 161 | output.instance["my_string"] = "Hello, World!" 162 | 163 | In all cases, the type of the assigned value must be consistent with the type 164 | of the field, as defined in the configuration file. 165 | 166 | Similarly, to get a field in an :class:`Input` sample, use the appropriate 167 | getter: :meth:`SampleIterator.get_number()`, :meth:`SampleIterator.get_boolean()`, 168 | :meth:`SampleIterator.get_string()`, or ``__getitem__``. ``get_string`` also works 169 | with numeric fields, returning the number as a string. For example: 170 | 171 | .. testcode:: 172 | 173 | for sample in input.samples.valid_data_iter: 174 | value = sample.get_number("my_double") 175 | value = sample.get_boolean("my_boolean") 176 | value = sample.get_string("my_string") 177 | 178 | # or alternatively: 179 | value = sample["my_double"] 180 | value = sample["my_boolean"] 181 | value = sample["my_string"] 182 | 183 | # get number as string: 184 | value = sample.get_string("my_double") 185 | 186 | 187 | .. note:: 188 | The typed getters and setters perform better than ``__setitem__`` 189 | and ``__getitem__`` in applications that write or read at high rates. 190 | We also recommend ``get_dictionary`` or ``set_dictionary`` over ``__setitem__`` 191 | or ``__getitem__`` when accessing all or most of the fields of a sample 192 | (see previous section). 193 | 194 | .. note:: 195 | If a field ``my_string``, defined as a string in the configuration file, 196 | contains a value that can be interpreted as a number, ``sample["my_string"]`` 197 | returns a number, not a string. 198 | 199 | 200 | .. _section-access-64-bit-integers: 201 | 202 | Accessing 64-bit integers 203 | ^^^^^^^^^^^^^^^^^^^^^^^^^ 204 | Internally, *Connector* relies on a framework that only contains a single number 205 | type, which is an IEEE-754 floating-point number. As a result, not all 64-bit integers 206 | can be represented with exact precision. If your type contains uint64 or int64 members, 207 | and you expect them to be larger than ``2^53`` (or smaller than ``-2^53``), then 208 | you must take the following into account. 209 | 210 | 64-bit values with an absolute value greater or equal to 2^53 can be set via: 211 | - The type-agnostic setter, ``__setitem__``. The values can be supplied as strings, or as numbers, e.g., ``the_output.instance["my_uint64"] = "18446744073709551615"``. 212 | - The :meth:`Instance.set_string()` method, e.g., ``the_output.instance.set_string("my_uint64", "18446744073709551615")`` 213 | - The :meth:`Instance.set_dictionary()` method, e.g., ``the_output.instance.set_dictionary({"my_uint64": "18446744073709551615"})`` 214 | 215 | 64-bit values with an absolute value greater than 2^53 can be retrieved via: 216 | - The type-agnostic getter, ``__getitem__``. The value will be an instance of ``int`` if larger than 2^53, otherwise a ``float``. e.g., ``sample["my_int64"] # 9223372036854775807`` 217 | - The :meth:`SampleIterator.get_string()`, method. 218 | The value will be returned as a string, e.g., ``sample.get_string("my_int64") # "9223372036854775807"`` 219 | - The :meth:`SampleIterator.get_dictionary()` method, e.g., ``sample.get_dictionary()["my_int64"] # "9223372036854775807"`` 220 | 221 | .. warning:: 222 | 223 | If :meth:`SampleIterator.get_number()` is used to retrieve a value > 2^53, an ``Error`` will be raised. 224 | 225 | .. warning:: 226 | 227 | If :meth:`Instance.set_number()` is used to set a value >= 2^53, an ``Error`` will be raised. 228 | 229 | .. note:: 230 | 231 | The :meth:`Instance.set_number()` operation can handle ``abs(value) < 2^53``, 232 | whereas :meth:`SampleIterator.get_number()` can handle ``abs(value) <= 2^53``. 233 | 234 | Accessing structs 235 | ^^^^^^^^^^^^^^^^^ 236 | 237 | To access a nested member, use ``.`` to identify the fully qualified ``field_name`` 238 | and pass it to the corresponding setter or getter. 239 | 240 | .. testcode:: 241 | 242 | output.instance.set_number("my_point.x", 10) 243 | output.instance.set_number("my_point.y", 20) 244 | 245 | # alternatively: 246 | output.instance["my_point.x"] = 10 247 | output.instance["my_point.y"] = 20 248 | 249 | It is possible to reset the value of a complex member back to its default: 250 | 251 | .. testcode:: 252 | 253 | output.instance.clear_member("my_point") # x and y are now 0 254 | 255 | Structs in dictionaries are set as follows: 256 | 257 | .. testcode:: 258 | 259 | output.instance.set_dictionary({"my_point":{"x":10, "y":20}}) 260 | 261 | When an member of a struct is not set, it retains its previous value. If we run 262 | the following code after the previous call to ``set_dictionary``: 263 | 264 | .. testcode:: 265 | 266 | output.instance.set_dictionary({"my_point":{"y":200}}) 267 | 268 | The value of ``my_point`` is now ``{"x":10, "y":200}`` 269 | 270 | It is possible to obtain the dictionary of a nested struct: 271 | 272 | .. testcode:: 273 | 274 | for sample in input.samples.valid_data_iter: 275 | point = sample.get_dictionary("my_point") 276 | 277 | ``member_name`` must be one of the following types: array, sequence, 278 | struct, value or union. If not, the call to ``get_dictionary()`` will fail:: 279 | 280 | for sample in input.samples.valid_data_iter: 281 | try: 282 | long = sample.get_dictionary("my_long") 283 | except rti.Error: 284 | print("ERROR, my_long is a basic type") 285 | 286 | It is also possible to obtain the dictionary of a struct using the ``__getitem__`` 287 | operator: 288 | 289 | .. testcode:: 290 | 291 | for sample in input.samples.valid_data_iter: 292 | point = sample["my_point"] 293 | # point is a dict 294 | 295 | The same limitations described in 296 | :ref:`Accessing basic members (numbers, strings and booleans)` 297 | about using ``__getitem__`` apply here. 298 | 299 | Accessing arrays and sequences 300 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 301 | 302 | Use ``"field_name[index]"`` to access an element of a sequence or array, 303 | where ``0 <= index < length``: 304 | 305 | .. testcode:: 306 | 307 | value = input.samples[0].get_number("my_int_sequence[1]") 308 | value = input.samples[0].get_number("my_point_sequence[2].y") 309 | 310 | You can get the length of a sequence: 311 | 312 | .. testcode:: 313 | 314 | length = input.samples[0].get_number("my_int_sequence#") 315 | 316 | Another option is to use ``SampleIterator.get_dictionary("field_name")`` to obtain 317 | a dictionary containing all of the elements of the array or sequence with name ``field_name``: 318 | 319 | .. testcode:: 320 | 321 | for sample in input.samples.valid_data_iter: 322 | point_sequence = sample.get_dictionary("my_point_sequence") # or sample["my_point_sequence"] 323 | # point_sequence is a list 324 | 325 | You can also get a specific element as a dictionary (if the element type is complex): 326 | 327 | .. testcode:: 328 | 329 | for sample in input.samples.valid_data_iter: 330 | point_element = sample.get_dictionary("my_point_sequence[1]") 331 | 332 | In an Output, sequences are automatically resized: 333 | 334 | .. testcode:: 335 | 336 | output.instance.set_number("my_int_sequence[5]", 10) # length is now 6 337 | output.instance.set_number("my_int_sequence[4]", 9) # length still 6 338 | 339 | To clear a sequence: 340 | 341 | .. testcode:: 342 | 343 | output.instance.clear_member("my_int_sequence") # my_int_sequence is now empty 344 | 345 | In dictionaries, sequences and arrays are represented as lists. For example: 346 | 347 | .. testcode:: 348 | 349 | output.instance.set_dictionary({ 350 | "my_int_sequence":[1, 2], 351 | "my_point_sequence":[{"x":1, "y":1}, {"x":2, "y":2}]}) 352 | 353 | Arrays have a constant length that can't be changed. If you don't set all the elements 354 | of an array, the remaining elements retain their previous value. However, sequences 355 | are always overwritten. See the following example: 356 | 357 | .. testcode:: 358 | 359 | output.instance.set_dictionary({ 360 | "my_point_sequence":[{"x":1, "y":1}, {"x":2, "y":2}], 361 | "my_point_array":[{"x":1, "y":1}, {"x":2, "y":2}, {"x":3, "y":3}]}) 362 | 363 | output.instance.set_dictionary({ 364 | "my_point_sequence":[{"x":100}], 365 | "my_point_array":[{"x":100}, {"y":200}]}) 366 | 367 | After the second call to ``set_dictionary()``, the contents of ``my_point_sequence`` 368 | are ``[{"x":100, "y":0}]``, but the contents of ``my_point_array`` are 369 | ``[{"x":100, "y":1}, {"x":2, "y":200}, {"x":3, "y":3}]``. 370 | 371 | Accessing optional members 372 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ 373 | 374 | An optional member is a member that applications can decide to send or not as 375 | part of every published sample. Therefore, optional members may or may not have 376 | a value. They are accessed the same way as non-optional members, except that 377 | ``None`` is a possible value. 378 | 379 | On an Input, any of the getters may return ``None`` if the field is optional: 380 | 381 | .. testcode:: 382 | 383 | if input.samples[0].get_number("my_optional_long") is None: 384 | print("my_optional_long not set") 385 | 386 | if input.samples[0].get_number("my_optional_point.x") is None: 387 | print("my_optional_point not set") 388 | 389 | :meth:`SampleIterator.get_dictionary()` returns a dictionary that doesn't include unset 390 | optional members. 391 | 392 | To set an optional member on an Output: 393 | 394 | .. testcode:: 395 | 396 | output.instance.set_number("my_optional_long", 10) 397 | 398 | If the type of the optional member is not primitive, when any of its members is 399 | first set, the rest are initialized to their default values: 400 | 401 | .. testcode:: 402 | 403 | output.instance.set_number("my_optional_point.x", 10) 404 | 405 | If ``my_optional_point`` was not previously set, the previous code also sets 406 | ``y`` to 0. 407 | 408 | There are several ways to reset an optional member. If the type is primitive: 409 | 410 | .. testcode:: 411 | 412 | output.instance.set_number("my_optional_long", None) # Option 1 413 | output.instance.clear_member("my_optional_long") # Option 2 414 | 415 | If the member type is complex: 416 | 417 | .. testcode:: 418 | 419 | output.instance.clear_member("my_optional_point") 420 | 421 | Note that :meth:`Instance.set_dictionary()` doesn't clear those members that are 422 | not specified; their value remains. For example: 423 | 424 | .. testcode:: 425 | 426 | output.instance.set_number("my_optional_long", 5) 427 | output.instance.set_dictionary({'my_double': 3.3, 'my_long': 4}) # my_optional_long is still 5 428 | 429 | To clear a member, set it to ``None`` explicitly:: 430 | 431 | output.instance.set_dictionary({'my_double': 3.3, 'my_long': 4, 'my_optional_long': None}) 432 | 433 | 434 | For more information about optional members in DDS, see 435 | `Optional Members `__ 436 | in the *Extensible Types Guide*. 437 | 438 | Accessing unions 439 | ^^^^^^^^^^^^^^^^ 440 | 441 | In an Output, the union member is automatically selected when you set it: 442 | 443 | .. testcode:: 444 | 445 | output.instance.set_number("my_union.point.x", 10) 446 | 447 | You can change it later: 448 | 449 | .. testcode:: 450 | 451 | output.instance.set_number("my_union.my_long", 10) 452 | 453 | In an Input, you can obtain the selected member as a string: 454 | 455 | .. testcode:: 456 | 457 | if input.samples[0].get_string("my_union#") == "point": 458 | value = input.samples[0].get_number("my_union.point.x") 459 | 460 | The ``__getitem__`` operator can be used to obtain unions: 461 | 462 | .. testcode:: 463 | 464 | for sample in input.samples.valid_data_iter: 465 | union = sample["my_union"] 466 | # union is a dict 467 | 468 | The type returned by the operator is a dict for unions. 469 | 470 | The same limitations described in 471 | :ref:`Accessing basic members (numbers, strings and booleans)` 472 | about using ``__getitem__`` apply here. 473 | 474 | Accessing key values of disposed samples 475 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 476 | 477 | Using :meth:`Output.write`, an :class:`Output` can write data, or dispose or 478 | unregister an instance. 479 | Depending on which of these operations is performed, the ``instance_state`` of the 480 | received sample will be ``'ALIVE'``, ``'NOT_ALIVE_NO_WRITERS'`` or ``'NOT_ALIVE_DISPOSED'``. 481 | If the instance was disposed, this ``instance_state`` will be ``'NOT_ALIVE_DISPOSED'``. 482 | In this state, it is possible to access the key fields of the instance that was disposed. 483 | 484 | .. note:: 485 | :attr:`SampleInfo.valid_data` will be false when the :attr:`SampleInfo.instance_state` 486 | is ``'NOT_ALIVE_DISPOSED'``. In this situation it's possible to access the 487 | key fields in the received sample. 488 | 489 | The key fields can be accessed as follows: 490 | 491 | .. testcode:: 492 | 493 | # The output and input are using the following type: 494 | # struct ShapeType { 495 | # @key string<128> color; 496 | # long x; 497 | # long y; 498 | # long shapesize; 499 | # } 500 | 501 | output.instance["x"] = 4 502 | output.instance["color"] = "Green" 503 | # Assume that some data associated with this instance has already been sent 504 | output.write(action="dispose") 505 | input.wait() 506 | input.take() 507 | sample = input.samples[0] 508 | 509 | if sample.info["instance_state"] == "NOT_ALIVE_DISPOSED": 510 | # sample.info.get('valid_data') will be false in this situation 511 | # Only the key-fields should be accessed 512 | color = sample["color"] # 'Green' 513 | # The fields 'x','y' and 'shapesize' cannot be retrieved because they're 514 | # not part of the key 515 | # You can also call get_dictionary() to get all of the key fields. 516 | # Again, only the key fields returned within the dictionary should 517 | # be accessed. 518 | key_values = sample.get_dictionary() # { "color": "Green", "x": 0, "y": 0, "shapesize": 0 } 519 | 520 | .. warning:: 521 | When the sample has an instance state of ``'NOT_ALIVE_DISPOSED'`` only the 522 | key fields should be accessed. -------------------------------------------------------------------------------- /docs/errors.rst: -------------------------------------------------------------------------------- 1 | Error handling 2 | =============== 3 | 4 | .. py:currentmodule:: rticonnextdds_connector 5 | 6 | *Connector* reports internal errors in *RTI Connext DDS* by raising an 7 | :class:`rticonnextdds_connector.Error`. This exception may contain a description 8 | of the error. 9 | 10 | A subclass, :class:`TimeoutError`, indicates that an operation that can block 11 | has timed out. 12 | 13 | Other errors may be raised as ``TypeError``, ``ValueError``, ``AttributeError``, 14 | or other built-in Python exceptions. 15 | 16 | Class reference: Error, TimeoutError 17 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 18 | 19 | Error class 20 | ^^^^^^^^^^^ 21 | 22 | .. autoclass:: rticonnextdds_connector.Error 23 | :members: 24 | 25 | 26 | TimeoutError class 27 | ^^^^^^^^^^^^^^^^^^ 28 | 29 | .. autoclass:: rticonnextdds_connector.TimeoutError 30 | :members: 31 | -------------------------------------------------------------------------------- /docs/features.rst: -------------------------------------------------------------------------------- 1 | Connext DDS features 2 | ==================== 3 | 4 | .. py:currentmodule:: rticonnextdds_connector 5 | 6 | Because *RTI Connector* is a simplified API, it provides access to a subset of the 7 | features in *RTI Connext DDS*. 8 | 9 | In addition to the functionality described in the rest of this documentation, this 10 | section summarizes the support that *Connector* provides for some notable 11 | *Connext DDS* features. 12 | 13 | General features 14 | ~~~~~~~~~~~~~~~~ 15 | 16 | .. list-table:: General Features 17 | :widths: 7 5 25 18 | :header-rows: 1 19 | 20 | * - Feature 21 | - Level of support 22 | - Notes 23 | * - `Quality of Service (QoS) `__ 24 | - Partial 25 | - Most QoS policies are supported because they can be configured in XML, but those that are 26 | designed to be mutable can't be changed in *Connector*. QoS policies that require 27 | a supporting API may have limited or no support. 28 | 29 | A few examples of QoS policies that are fully supported in *Connector*: 30 | 31 | * Reliability 32 | * Durability 33 | * History 34 | * Ownership 35 | 36 | A few examples of QoS policies that are supported but can't be changed in 37 | *Connector* even though they are mutable by design and changeable in other APIs: 38 | 39 | * Partition 40 | * Lifespan 41 | * Ownership Strength 42 | * Property and User Data 43 | * Time-Based Filter 44 | 45 | A few examples of QoS policies that have limited support because they require 46 | a supporting API that is not available in *Connector*: 47 | 48 | * Batch - fully supported except that manual flushing is not available 49 | * Entity Factory - *autoenable_created_entities* can be set to *false* only for a *subscriber*, in 50 | order to enable an `Input` only when :meth:`Connector.get_input` is called. 51 | * Property - Properties can be set in XML, but they can't be looked up in *Connector* 52 | 53 | `Topic Qos `__ is not supported in *Connector*. Use DataReader QoS and DataWriter QoS directly. 54 | * - `Entity Statuses `__ 55 | - Partial 56 | - Only :meth:`Input.wait` (data available), :meth:`Input.wait_for_publications`, and :meth:`Output.wait_for_subscriptions` are supported. 57 | * - `Managing Data Instances `__ 58 | - Partial 59 | - On an ``Output``, it is possible to dispose or unregister an instance (see :meth:`Output.write`). Instances are automatically registered when first written. On an ``Input`` the instance state can be obtained, alongside the key fields of a disposed instance (see :ref:`Accessing key values of disposed samples`). Instance handles are not exposed. 60 | * - `Application Acknowledgment `__ 61 | - Partial 62 | - *DDS_APPLICATION_AUTO_ACKNOWLEDGMENT_MODE* is supported. If enabled, when a call to :meth:`Input.take` or :meth:`Input.read` is followed by another call, the second one automatically acknowledges the samples read in the first one. 63 | 64 | *DDS_APPLICATION_EXPLICIT_ACKNOWLEDGMENT_MODE* is not supported. 65 | * - `Request-Reply `__ 66 | - Partial 67 | - The correlation between two samples can be established at the application level: 68 | 69 | * The *Requester* application writes by calling :meth:`Output.write` with the parameter ``identity=A`` (the *request* sample) 70 | * The *Replier* application receives the *request* sample, obtains the ``identity`` (A), from ":attr:`SampleIterator.info` and writes a new sample with ``related_sample_identity=A`` (the *reply* sample) 71 | * The *Requester* application receives the *reply* sample, and correlates the ``related_sample_identity`` from :attr:`SampleIterator.info` with the ``identity`` it used in the first step. 72 | 73 | * - `Topic Queries `__ 74 | - Partial 75 | - ``Input`` doesn't have the API to create a *TopicQuery*, but in the configuration file a *data_writer* can enable support for *TopicQuery* so other *Connext DDS Subscribers* can query the *Connector Publisher*. 76 | * - `Zero Copy Transfer Over Shared Memory `__ 77 | - Not supported 78 | - Only available in C and C++. 79 | * - `Built-in Topics `__ 80 | - Not supported 81 | - API not available. 82 | * - `Transport Plugins `__ 83 | - Partial 84 | - The built-in transports can be configured in XML. 85 | * - Add-on Libraries 86 | (such as `Monitoring `__, 87 | `Security Plugins `__ ) 88 | - Supported 89 | - See :ref:`Loading Connext DDS Add-On Libraries`. 90 | 91 | Features related to sending data 92 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 93 | 94 | .. list-table:: Features Related to Sending Data 95 | :widths: 7 5 25 96 | :header-rows: 1 97 | 98 | * - Feature 99 | - Level of support 100 | - Notes 101 | * - `Waiting for Acknowledgments `__ 102 | - Supported 103 | - See :meth:`Output.wait`. 104 | * - `Coherent Sets `__ 105 | - Not supported 106 | - API not available. 107 | * - `Flow Controllers `__ 108 | - Partial 109 | - Most functionality is available via XML QoS configuration. 110 | * - `Asserting Liveliness Manually `__ 111 | - Not supported 112 | - API not available. 113 | * - `Collaborative DataWriters `__ 114 | - Limited 115 | - The virtual GUID can be set per writer in XML, but not per sample. 116 | 117 | Features related to receiving data 118 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 119 | 120 | .. list-table:: Features Related to Receiving Data 121 | :widths: 7 5 25 122 | :header-rows: 1 123 | 124 | * - Feature 125 | - Level of support 126 | - Notes 127 | * - `Content-Filtered Topics `__ 128 | - Partial 129 | - `Configurable in XML `__ but it can't be modified after creation 130 | * - `Sample Info `__ 131 | - Partial 132 | - See :attr:`SampleIterator.info` 133 | * - `Query Conditions `__ 134 | - Not supported 135 | - API not available 136 | * - `Group-Ordered Access `__ 137 | - Not supported 138 | - API not available 139 | * - `Waiting for Historical Data `__ 140 | - Not supported 141 | - API not available 142 | 143 | Features related to the type system 144 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 145 | 146 | .. list-table:: Features Related to the Type System 147 | :widths: 7 5 25 148 | :header-rows: 1 149 | 150 | * - Feature 151 | - Level of support 152 | - Notes 153 | * - `DDS type system `__ 154 | - Supported 155 | - *Connector* can use any DDS type. Types are defined in XML. 156 | * - `Type extensibility `__ 157 | - Supported 158 | - *Connector* supports type extensibility, including mutable types in the XML definition of types. It also supports type-consistency enforcement 159 | and sample-assignability enforcement; these checks are performed by the *RTI Connext DDS* Core. 160 | * - `Optional members `__ 161 | - Supported 162 | - See :ref:`Accessing optional members`. 163 | * - `Default values `__ 164 | - Supported 165 | - For example, to declare a default value for a member:: 166 | 167 | 168 | 169 | 170 | 171 | 172 | Now the value for *my_int* when you call :meth:`Output.write` without 173 | setting it explicitly is 20. And when you receive a data sample in an 174 | ``Input`` from a *Publisher* whose type is compatible, but doesn't have the 175 | field *my_int*, the value you receive will be 20. 176 | 177 | * - `Unbounded data `__ 178 | - Supported 179 | - To declare an unbounded sequence or string, set its max length to *-1*:: 180 | 181 | 182 | 183 | 184 | 185 | 186 | For any ``Output`` using a topic for a type with unbounded members, set the 187 | following in the ```` QoS policy:: 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | dds.data_writer.history.memory_manager.fast_pool.pool_buffer_max_size 196 | 197 | 4096 198 | 199 | 200 | 201 | 202 | 203 | The value *4096* is a threshold that indicates *Connext DDS* should allocate 204 | memory dynamically for data samples that exceed that size. For samples below 205 | that threshold, memory comes from pre-allocated buffers. 206 | 207 | If the unbounded member is a *key*, then in any ``Input`` that uses the type, 208 | set the following:: 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | dds.data_reader.history.memory_manager.fast_pool.pool_buffer_max_size 217 | 218 | 4096 219 | 220 | 221 | 222 | 223 | 224 | * - `FlatData Language Binding `__ 225 | - Not supported 226 | - However, an ``Input`` can receive data published by other *Connext DDS* applications that use FlatData. 227 | 228 | Loading Connext DDS Add-On Libraries 229 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 230 | 231 | *Connector* supports features that require the loading of additional *Connext DDS* 232 | libraries, such as 233 | `Monitoring `__ 234 | and `Security Plugins `__. 235 | 236 | The Monitoring and Security plugins are configured in XML, as described in the previous 237 | links. 238 | 239 | To use RTI Connext DDS add-ons you need an RTI Connext DDS installation. To 240 | configure your environment so that Connector can load these additional libraries: 241 | 242 | - Set your environment using:: 243 | 244 | $ source /resource/scripts/rtisetenv_.bash 245 | 246 | or:: 247 | 248 | > \resource\scripts\rtisetenv_.bat 249 | 250 | - Or set your system's library path to:: 251 | 252 | \lib\\ 253 | 254 | .. note:: 255 | Each version of Connector can only load add-on libraries from its 256 | corresponding Connext DDS release. You can see this correspondence in the 257 | :ref:`release notes`. For example, Connector 1.1.0 can only 258 | load Connext DDS 6.1.0 add-on libraries. -------------------------------------------------------------------------------- /docs/getting_started.rst: -------------------------------------------------------------------------------- 1 | 2 | .. py:currentmodule:: rticonnextdds_connector 3 | 4 | Getting Started 5 | =============== 6 | 7 | Installing RTI Connector for Python 8 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 9 | 10 | Install *RTI Connector for Python* with *pip*: 11 | 12 | .. code:: bash 13 | 14 | $ pip install rticonnextdds-connector 15 | 16 | The above command installs the latest version of *Connext* by default. To 17 | install a specific version, use this command: 18 | 19 | .. code-block:: console 20 | 21 | $ pip install rticonnextdds-connector== 22 | 23 | where ```` is any valid *Connext* version in the 24 | `Release history `__ 25 | at pypi.org. 26 | 27 | And then run your *Connector* applications: 28 | 29 | .. code:: bash 30 | 31 | $ python my_connector_app.py 32 | 33 | 34 | Running the examples 35 | ~~~~~~~~~~~~~~~~~~~~ 36 | 37 | The examples are in the `examples/python `__ 38 | directory of the *RTI Connector* for Python GitHub repository. 39 | 40 | In the simple example, ``writer.py`` periodically publishes data for a 41 | *Square* topic, and ``reader.py`` subscribes to the topic and prints all the 42 | data samples it receives. 43 | 44 | Run the reader as follows: 45 | 46 | .. code:: bash 47 | 48 | python examples/python/simple/reader.py 49 | 50 | In another shell, run the writer: 51 | 52 | .. code:: bash 53 | 54 | python examples/python/simple/writer.py 55 | 56 | This is what ``reader.py`` looks like: 57 | 58 | .. literalinclude:: ../examples/python/simple/reader.py 59 | :lines: 18- 60 | 61 | And this is ``writer.py``: 62 | 63 | .. literalinclude:: ../examples/python/simple/writer.py 64 | :lines: 18- 65 | 66 | You can run the reader and the writer in any order, and you can run multiple 67 | instances of each at the same time. You can also run any other *DDS* application 68 | that publishes or subscribes to the *Square* topic. For example, you can use 69 | `RTI Shapes Demo `__. -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. rticonnextdds-connector-py 2 | 3 | .. py:currentmodule:: rticonnextdds_connector 4 | 5 | .. image:: static/py-logo.png 6 | :align: left 7 | 8 | Welcome to RTI Connector for Python! 9 | ==================================== 10 | 11 | *RTI® Connext® DDS* is a connectivity software framework for integrating data 12 | sources of all types. At its core is the world's leading ultra-high performance, 13 | distributed networking databus. 14 | 15 | *RTI Connector* provides a quick and easy way to write applications that 16 | publish and subscribe to the *RTI Connext DDS* databus in Python and other 17 | languages. 18 | 19 | You can learn how to use *RTI Connector* by reading the following sections, which 20 | include examples and detailed API reference. You can also find a specific type 21 | or function in the :ref:`genindex`. 22 | 23 | .. note:: 24 | 25 | With the introduction of the RTI Connext Python API in *RTI Connext* 26 | 7.0.0, *Connector for Python* is deprecated and will be removed in a 27 | future release, once the Connext Python API is fully supported. You are 28 | encouraged to try the 29 | `Connext Python API `__ (experimental in 7.0.0). 30 | 31 | 32 | Table of Contents 33 | ================= 34 | 35 | .. toctree:: 36 | :maxdepth: 2 37 | :numbered: 38 | 39 | intro 40 | getting_started 41 | configuration 42 | connector 43 | output 44 | input 45 | advanced 46 | release_notes 47 | copyright_license 48 | 49 | * :ref:`genindex` 50 | * :ref:`search` 51 | 52 | To learn more about *RTI Connext DDS*, see the 53 | `RTI Connext DDS Getting Started Guide `__. 54 | More documentation is available in the `RTI Community Portal `__. -------------------------------------------------------------------------------- /docs/input.rst: -------------------------------------------------------------------------------- 1 | Reading Data (Input) 2 | ==================== 3 | 4 | .. py:currentmodule:: rticonnextdds_connector 5 | 6 | .. testsetup:: * 7 | 8 | import rticonnextdds_connector as rti 9 | connector = rti.Connector("MyParticipantLibrary::MyParticipant", "ShapeExample.xml") 10 | 11 | 12 | Getting the input 13 | ~~~~~~~~~~~~~~~~~ 14 | 15 | To read/take samples, first get a reference to the :class:`Input`: 16 | 17 | .. testcode:: 18 | 19 | input = connector.get_input("MySubscriber::MySquareReader") 20 | 21 | :meth:`Connector.get_input()` returns a :class:`Input` object. This example 22 | obtains the input defined by the ```` named *MySquareReader* within 23 | the ```` named *MySubscriber*: 24 | 25 | .. code-block:: xml 26 | 27 | 28 | 29 | 30 | 31 | This ```` is defined inside the ```` selected 32 | to create this ``connector`` (see :ref:`Creating a new Connector`). 33 | 34 | Reading or taking the data 35 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 36 | 37 | Call :meth:`Input.take()` to access and remove the samples:: 38 | 39 | input.take() 40 | 41 | or :meth:`Input.read()` to access the samples but leave them available for 42 | a future ``read()`` or ``take()``:: 43 | 44 | input.read() 45 | 46 | The method :meth:`Input.wait()` can be used to identify when there is new data 47 | available on a specific :class:`Input`. It will block until either the supplied 48 | timeout expires (in which case it will raise a :class:`TimeoutError`) or until new 49 | data is available:: 50 | 51 | input.wait() 52 | 53 | The method :meth:`Connector.wait()` has the same behavior as :meth:`Input.wait()`, 54 | but will block until data is available on any of the :class:`Input` objects within 55 | the :class:`Connector`:: 56 | 57 | connector.wait() 58 | 59 | Accessing the data samples 60 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 61 | 62 | After calling :meth:`Input.read()` or :meth:`Input.take()`, :attr:`Input.samples` 63 | contains the data samples: 64 | 65 | .. testcode:: 66 | 67 | for sample in input.samples: 68 | if sample.valid_data: 69 | print(sample.get_dictionary()) 70 | 71 | :meth:`SampleIterator.get_dictionary()` retrieves all the fields of a sample. 72 | 73 | Unless the :attr:`Samples.valid_data_iter` is used, it is necessary to check if the 74 | sample contains valid data before accessing the fields. The only exception to this 75 | rule is if the ``instance_state`` of the sample is ``"NOT_ALIVE_DISPOSED"``. 76 | See :ref:`Accessing key values of disposed samples` for more information on this use 77 | case. 78 | 79 | If you don't need to access the meta-data (see :ref:`Accessing sample meta-data`), 80 | the simplest way to access the data is to use :attr:`Samples.valid_data_iter` to skip 81 | samples with invalid data: 82 | 83 | .. testcode:: 84 | 85 | for sample in input.samples.valid_data_iter: 86 | print(sample.get_dictionary()) 87 | 88 | It is also possible to access an individual sample: 89 | 90 | .. testcode:: 91 | 92 | if input.samples.length > 0: 93 | if input.samples[0].valid_data: 94 | print(input.samples[0].get_dictionary()) 95 | 96 | .. warning:: 97 | All the methods described in this section return iterators to samples. 98 | Calling read/take again invalidates all iterators currently in 99 | use. For that reason, it is not recommended to store any iterator. 100 | 101 | ``get_dictionary()`` can receive a ``field_name`` to only return the fields of a 102 | complex member. In addition to ``get_dictionary()``, you can get the values of 103 | specific primitive fields using :meth:`SampleIterator.get_number()`, 104 | :meth:`SampleIterator.get_boolean()` and :meth:`SampleIterator.get_string()`. 105 | For example: 106 | 107 | .. testcode:: 108 | 109 | for sample in input.samples.valid_data_iter: 110 | x = sample.get_number("x") # or just sample["x"] 111 | y = sample.get_number("y") 112 | size = sample.get_number("shapesize") 113 | color = sample.get_string("color") # or just sample["color"] 114 | 115 | See more information and examples in :ref:`Accessing the data`. 116 | 117 | Accessing sample meta-data 118 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 119 | 120 | Every sample contains an associated *SampleInfo* with meta-information about 121 | the sample: 122 | 123 | .. testcode:: 124 | 125 | for sample in input.samples: 126 | source_timestamp = sample.info["source_timestamp"] 127 | 128 | 129 | See :meth:`SampleIterator.info` for the list of available meta-data fields. 130 | 131 | 132 | *Connext DDS* can produce samples with invalid data, which contain meta-data only. 133 | For more information about this, see `Valid Data Flag 134 | `__ 135 | in the *RTI Connext DDS Core Libraries User's Manual*. 136 | These samples indicate a change in the instance state. Samples with invalid data 137 | still provide the following information: 138 | 139 | * The :class:`SampleInfo` 140 | * When an instance is disposed (``sample.info.get('instance_state')`` is 141 | ``'NOT_ALIVE_DISPOSED'``), the sample data contains the value of the key that 142 | has been disposed. You can access the key fields only. See 143 | :ref:`Accessing key values of disposed samples`. 144 | 145 | Returning data and meta-data samples 146 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 147 | 148 | The methods :meth:`Input.take()` and :meth:`Input.read()` make data accesible 149 | through the :meth:`Input.samples` collection. This data is available until the 150 | next call to :meth:`Input.take()` or :meth:`Input.read()`. 151 | 152 | In some situations, data may need to be returned sooner so that new data can be 153 | received. To do that, you can explicitly call :meth:`Input.return_samples()`: 154 | 155 | .. testcode:: 156 | 157 | input.return_samples() 158 | 159 | It is also possible to return samples when calling 160 | :meth:`Input.wait()` with the ``return_samples`` parameter set to ``True``: 161 | 162 | .. testcode:: 163 | 164 | input.wait(return_samples=True) 165 | 166 | Matching with a publication 167 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 168 | 169 | Use the method :meth:`Input.wait_for_publications()` to detect when a compatible 170 | DDS publication is matched or stops matching. It returns the change in the number of 171 | matched publications since the last time it was called:: 172 | 173 | change_in_matches = input.wait_for_publications() 174 | 175 | For example, if a new compatible publication is discovered within the specified 176 | ``timeout``, the function returns 1; if a previously matching publication 177 | no longer matches, it returns -1. 178 | 179 | You can obtain information about the existing matched publications with 180 | :attr:`Input.matched_publication`: 181 | 182 | .. testcode:: 183 | 184 | matched_pubs = input.matched_publications 185 | for pub_info in matched_pubs: 186 | pub_name = pub_info['name'] 187 | 188 | Class reference: Input, Samples, SampleIterator 189 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 190 | 191 | Input class 192 | ^^^^^^^^^^^ 193 | 194 | .. autoclass:: rticonnextdds_connector.Input 195 | :members: 196 | 197 | Samples class 198 | ^^^^^^^^^^^^^ 199 | 200 | .. autoclass:: rticonnextdds_connector.Samples 201 | :members: 202 | 203 | SampleIterator class 204 | ^^^^^^^^^^^^^^^^^^^^ 205 | 206 | .. autoclass:: rticonnextdds_connector.SampleIterator 207 | :members: 208 | 209 | 210 | ValidSampleIterator class 211 | ^^^^^^^^^^^^^^^^^^^^^^^^^ 212 | 213 | .. autoclass:: rticonnextdds_connector.ValidSampleIterator 214 | :members: 215 | -------------------------------------------------------------------------------- /docs/intro.rst: -------------------------------------------------------------------------------- 1 | 2 | .. py:currentmodule:: rticonnextdds_connector 3 | 4 | Introduction to RTI Connector 5 | ============================= 6 | 7 | *RTI Connext DDS* is a software connectivity framework for real-time distributed 8 | applications. It uses the publish-subscribe communications model to make 9 | data distribution efficient and robust. At its core is the world's 10 | leading ultra-high performance, distributed networking databus. 11 | 12 | *RTI Connector* is a family of simplified APIs for publishing and subscribing 13 | to the *Connext DDS* Databus in programming languages such as Python and JavaScript. 14 | 15 | .. note:: 16 | 17 | This documentation assumes you are already familiar with basic DDS 18 | concepts. You can learn about DDS in the *RTI Connext DDS 19 | Getting Started Guide*, *RTI Connext DDS Core Libraries User's Manual*, 20 | and the *Connext DDS* API documentation for C, 21 | C++, Java and .NET. These documents are available from the 22 | `RTI Community portal `__. 23 | 24 | In *Connector*, the DDS system is defined in XML. This includes the DDS entities 25 | and their data types and quality of service. Applications instantiate a 26 | :class:`Connector` object that loads an XML configuration and creates the entities 27 | that allow publishing and subscribing to DDS Topics. 28 | 29 | .. image:: static/overview.png 30 | :align: center 31 | 32 | By publishing and subscribing to DDS Topics, *Connector* works seamlessly 33 | with any other *DDS* applications, including *Connext DDS* user applications, and 34 | RTI Tools and Infrastructure Services. 35 | 36 | How to read this documentation 37 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 38 | 39 | * First learn how to install *Connector* and run the examples in :ref:`Getting Started`. 40 | 41 | * Learn how to define the DDS system in XML in :ref:`Defining a DDS system in XML`. 42 | 43 | * Learn how to write a *Connector* application in 44 | :ref:`Loading a Connector`, :ref:`Writing Data (Output)`, and :ref:`Reading Data (Input)`. 45 | These sections include examples and detailed type and function documentation. 46 | 47 | * See :ref:`Advanced Topics` for details on the different ways to 48 | access the data, the threading model, and error reporting. If you want to 49 | know whether a *Connext DDS* feature is supported in *Connector*, 50 | and how to use it, see :ref:`Connext DDS Features`. 51 | 52 | If you're looking for a specific class or function, go directly to the :ref:`genindex`. 53 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/output.rst: -------------------------------------------------------------------------------- 1 | Writing Data (Output) 2 | ===================== 3 | 4 | .. py:currentmodule:: rticonnextdds_connector 5 | 6 | 7 | .. testsetup:: * 8 | 9 | import rticonnextdds_connector as rti 10 | connector = rti.Connector("MyParticipantLibrary::MyParticipant", "ShapeExample.xml") 11 | 12 | Getting the Output 13 | ~~~~~~~~~~~~~~~~~~ 14 | 15 | To write a data sample, first look up an :class:`Output`: 16 | 17 | .. testcode:: 18 | 19 | output = connector.get_output("MyPublisher::MySquareWriter") 20 | 21 | :meth:`Connector.get_output()` returns an :class:`Output` object. This example 22 | obtains the output defined by the ```` named *MySquareWriter* within 23 | the ```` named *MyPublisher*: 24 | 25 | .. code-block:: xml 26 | 27 | 28 | 29 | 30 | 31 | This ```` is defined inside the ```` selected to create 32 | this ``connector`` (see :ref:`Creating a new Connector`). 33 | 34 | Populating the data sample 35 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 36 | 37 | Then set the ``output.instance`` fields. You can set them member by member: 38 | 39 | .. testcode:: 40 | 41 | output.instance.set_number("x", 1) 42 | output.instance.set_number("y", 2) 43 | output.instance.set_number("shapesize", 30) 44 | output.instance.set_string("color", "BLUE") 45 | 46 | Or using a dictionary: 47 | 48 | .. testcode:: 49 | 50 | output.instance.set_dictionary({"x":1, "y":2, "shapesize":30, "color":"BLUE"}) 51 | 52 | The name of each member corresponds to the type assigned to this output in XML. 53 | For example: 54 | 55 | .. code-block:: xml 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | See :class:`Instance` and :ref:`Accessing the data` for more information. 65 | 66 | Writing the data sample 67 | ~~~~~~~~~~~~~~~~~~~~~~~ 68 | 69 | To write the values you set in ``output.instance``, call :meth:`Output.write()`:: 70 | 71 | output.write() 72 | 73 | If the ```` is set up for reliable communication, you can use 74 | :meth:`Output.wait()` to block until all matching reliable *Subscribers* 75 | acknowledge reception of the data sample:: 76 | 77 | output.wait() 78 | 79 | The write method can receive several options. For example, to 80 | write with a specific timestamp: 81 | 82 | .. testcode:: 83 | 84 | output.write(source_timestamp=100000) 85 | 86 | It is also possible to dispose or unregister an instance: 87 | 88 | .. testcode:: 89 | 90 | output.write(action="dispose") 91 | output.write(action="unregister") 92 | 93 | In these two cases, only the *key* fields in the ``output.instance`` are relevant 94 | when determining the instance that is disposed or unregistered. 95 | 96 | See :meth:`Output.write` for more information on the supported parameters. 97 | 98 | Matching with a subscription 99 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 100 | 101 | Before writing, you can use the method :meth:`Output.wait_for_subscriptions()` to 102 | detect when a compatible DDS subscription is matched or stops matching. It returns 103 | the change in the number of matched subscriptions since the last time it was called:: 104 | 105 | change_in_matches = output.wait_for_subscriptions() 106 | 107 | For example, if a new compatible subscription is discovered within the specified 108 | ``timeout``, the function returns 1; if a previously matching subscription 109 | no longer matches (for example, due to the application being closed), it returns -1. 110 | 111 | You can obtain information about the currently matched subscriptions with 112 | :attr:`Output.matched_subscriptions`: 113 | 114 | .. testcode:: 115 | 116 | matched_subs = output.matched_subscriptions 117 | for sub_info in matched_subs: 118 | sub_name = sub_info['name'] 119 | 120 | Class reference: Output, Instance 121 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 122 | 123 | Output class 124 | ^^^^^^^^^^^^ 125 | 126 | .. autoclass:: rticonnextdds_connector.Output 127 | :members: 128 | 129 | Instance class 130 | ^^^^^^^^^^^^^^ 131 | 132 | .. autoclass:: rticonnextdds_connector.Instance 133 | :members: 134 | -------------------------------------------------------------------------------- /docs/release_notes.rst: -------------------------------------------------------------------------------- 1 | .. include:: vars.rst 2 | 3 | .. _section-release-notes: 4 | 5 | Release Notes 6 | ============= 7 | 8 | Supported Platforms 9 | ------------------- 10 | 11 | *RTI Connector* works with Python® 2.x and 3.x. It uses a native C library that 12 | runs on most Windows®, Linux® and macOS® platforms. 13 | 14 | *Connector* has been tested with Python 2.6+ and 3.6.8+, and on the following systems: 15 | 16 | **Linux** 17 | * CentOS™ 7.0 (x64) 18 | * Red Hat® Enterprise Linux 7, 7.3, 7.5, 7.6, 8 (x64) 19 | * SUSE® Linux Enterprise Server 12 SP2 (x64) 20 | * Ubuntu® 18.04 (x64, Arm v7, Arm v8) 21 | * Ubuntu 20.04, 22.04 LTS (x64) 22 | 23 | **macOS** 24 | * macOS 10.13-10.15, 12 (x64) 25 | * macOS 11 (x64 and Arm v8 tested via x64 libraries) 26 | 27 | **Windows** 28 | * Windows 10, 11 (x64) 29 | * Windows Server 2016 (x64) 30 | 31 | *Connector* is supported in other languages in addition to Python, see the 32 | `main Connector 33 | repository `__. 34 | 35 | 36 | Version 1.2.4 37 | ------------- 38 | *Connector* 1.2.4 is built on *RTI Connext DDS* 6.1.2.21. 39 | For details on what's new and fixed in 6.1.2.21, contact support@rti.com. 40 | 41 | What's Fixed in 1.2.4 42 | ^^^^^^^^^^^^^^^^^^^^^ 43 | 44 | Wait operation on Input may have led to deadlock 45 | """""""""""""""""""""""""""""""""""""""""""""""" 46 | Using the ``wait`` operation of an ``Input`` after doing ``read`` or ``take`` 47 | could have led to a deadlock scenario. This only happened if ``read`` or 48 | ``take`` returned the the maximum amount of samples that an ``Input`` can hold. 49 | 50 | To remedy this, a new ``return_samples`` operation has been added to Inputs. 51 | Additionally, the ``wait`` operation can take a ``return_samples=True`` keyword 52 | argument to trigger the functionality. This behavior is not enabled by default, 53 | to be backwards compatible. 54 | 55 | .. code:: python 56 | 57 | my_input.wait(return_samples=True) 58 | 59 | [RTI Issue ID CON-314] 60 | 61 | 62 | Previous Releases 63 | ----------------- 64 | 65 | Version 1.2.3 66 | ^^^^^^^^^^^^^ 67 | *Connector* 1.2.3 is built on *RTI Connext DDS* 6.1.2.17. 68 | For details on what's new and fixed in 6.1.2.17, contact support@rti.com. 69 | There are no changes to Connector itself. 70 | 71 | 72 | Version 1.2.2 73 | ^^^^^^^^^^^^^ 74 | 75 | What's New in 1.2.2 76 | """"""""""""""""""" 77 | 78 | *Connector* 1.2.2 is built on `RTI Connext DDS 6.1.2 `__. 79 | 80 | Native Windows libraries updated to Visual Studio 2015 81 | """""""""""""""""""""""""""""""""""""""""""""""""""""" 82 | .. CON-276 83 | 84 | Previously, the native libraries shipped with Connector were built using Visual 85 | Studio 2013 (and accompanied by Microsoft's mscvr120 redistributable). These 86 | libraries are now built using Visual Studio 2015. The redistributable that is 87 | shipped has been updated accordingly. 88 | 89 | Version 1.2.0 90 | ^^^^^^^^^^^^^ 91 | 92 | What's New in 1.2.0 93 | """"""""""""""""""" 94 | 95 | *Connector* 1.2.0 is built on `RTI Connext DDS 6.1.1 `__. 96 | 97 | New Platforms 98 | +++++++++++++ 99 | 100 | *Connector* has been validated on macOS 11 (Big Sur) systems on x64 and Arm v8 101 | CPUs (via x64 libraries). 102 | 103 | 104 | New API makes it easier to query what version of Connector is being used 105 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 106 | .. CON-92 107 | 108 | A new API, :meth:`rticonnextdds_connector.Connector.get_version`, has been added that provides the caller 109 | with the version of *Connector* and the version of the native libraries being used. 110 | 111 | 112 | What's Fixed in 1.2.0 113 | """"""""""""""""""""" 114 | 115 | Error logged when accessing string longer than 128 bytes 116 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 117 | Previously, on an input, when accessing a string longer than 128 bytes, the 118 | following error was printed: 119 | 120 | .. code-block:: 121 | 122 | Output buffer too small for member (name = "frame", id = 1). Provided size (128), requires size (x). 123 | 124 | This error message was innocuous; there was actually no issue with retrieving 125 | the string. The message is no longer printed. 126 | 127 | [RTI Issue ID CON-157] 128 | 129 | 130 | Deleting same Connector object twice may have resulted in segmentation fault 131 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 132 | A segmentation fault may have occurred when the same *Connector* object was 133 | deleted twice. This issue has been resolved. 134 | 135 | [RTI Issue ID CON-200] 136 | 137 | 138 | Support added for handling large 64-bit integers 139 | ++++++++++++++++++++++++++++++++++++++++++++++++ 140 | Support has been improved for both getting and setting large (greater than 2^53) 141 | 64-bit values. See :ref:`section-access-64-bit-integers` for more information. 142 | 143 | Note that on Windows systems, the string representations of Not a Number and infinity 144 | (e.g., ``'NaN'``, ``'Infinity'``) are not valid values for a Number. They are valid 145 | inputs on other systems. 146 | 147 | [RTI Issue ID CON-190] 148 | 149 | Version 1.1.1 150 | ^^^^^^^^^^^^^ 151 | *Connector* 1.1.1 is built on *RTI Connext DDS* 6.1.0.3, which fixes several 152 | bugs in the Core Libraries. If you want more details on the bugs fixed in 6.1.0.3, 153 | contact support@rti.com. These bugs are also fixed in 154 | `RTI Connext DDS 6.1.1 `__, 155 | upon which *RTI Connector* 1.2.0 is built. 156 | 157 | Version 1.1.0 158 | ^^^^^^^^^^^^^ 159 | 160 | What's New in 1.1.0 161 | """"""""""""""""""" 162 | 163 | *Connector* 1.1.0 is built on `RTI Connext DDS 6.1.0 `__. 164 | 165 | Support added for ARMv8 architectures 166 | +++++++++++++++++++++++++++++++++++++ 167 | .. CON-174 168 | 169 | Connector for Python now runs on ARMv8 architectures. Native libraries 170 | built for ARMv8 Ubuntu 16.04 are now shipped alongside Connector. These libraries 171 | have been tested on ARMv8 Ubuntu 16.04 and ARMv8 Ubuntu 18.04. 172 | 173 | Sample state, instance state, and view state can now be obtained in Connector 174 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 175 | .. CON-177 176 | 177 | The SampleInfo class in *Connector* has been extended to provide access to the 178 | sample state, view state, and instance state fields. These new fields work the 179 | same as the existing fields in the structure (in *Connector* for Python they are 180 | the keys to the dictionary, in *Connector* for JavaScript they are the keys to the 181 | JSON Object). 182 | 183 | Support for accessing the key values of disposed instances 184 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 185 | 186 | .. CON-188 187 | 188 | Support for disposing instances was added in *Connector* 1.0.0. 189 | However, it was not possible to access the key values of the disposed instance. 190 | This functionality is now available in the Python and JavaScript bindings. 191 | When a disposed sample is received, the key values can be accessed. 192 | The syntax for accessing these key values is the same as when the sample 193 | contains valid data (i.e., using type-specific getters, or obtaining the entire 194 | sample as an object). When the instance state is NOT_ALIVE_DISPOSED, only the 195 | key values in the sample should be accessed. 196 | 197 | Support for Security, Monitoring and other Connext DDS add-on libraries 198 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 199 | 200 | .. CON-221 201 | 202 | It is now possible to load additional Connext DDS libraries at runtime. This means 203 | that Connext DDS features such as Monitoring and Security Plugins are now supported. 204 | Refer to :ref:`Loading Connext DDS Add-On Libraries` for more information. 205 | 206 | What's Fixed in 1.1.0 207 | """""""""""""""""""""" 208 | 209 | Support for loading multiple configuration files 210 | ++++++++++++++++++++++++++++++++++++++++++++++++ 211 | 212 | A *Connector* object now supports loading multiple files. This allows separating 213 | the definition of types, QoS profiles, and *DomainParticipants* into different 214 | files: 215 | 216 | .. testcode:: 217 | 218 | c = rti.Connector("my_profiles.xml;my_types.xml;my_participants.xml", configName) 219 | 220 | [RTI Issue ID CON-209] 221 | 222 | Some larger integer values may have been corrupted by Connector's internal JSON parser 223 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 224 | 225 | The internal JSON parser used in *Connector* failed to identify integer numbers 226 | from double-precision floating-point numbers for certain values. 227 | For example, if a number could not be represented as a 64-bit integer, the 228 | parser may have incorrectly identified it as an integer, causing the value to 229 | become corrupted. This problem has been resolved. 230 | 231 | [RTI Issue ID CON-170] 232 | 233 | Creating two instances of Connector resulted in a license error 234 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 235 | 236 | Under some circumstances, it was not possible to create two *Connector* objects. 237 | The creation of the second *Connector* object failed due to a license error. 238 | This issue affected all of the *Connector* APIs (Python, JavaScript). 239 | This issue has been fixed. 240 | 241 | [RTI Issue ID CON-163] 242 | 243 | Creating a Connector instance with a participant_qos tag in the XML may have resulted in a license error 244 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 245 | 246 | In some cases, if the XML configuration file of *Connector* contained a 247 | `` tag within the definition of the *DomainParticipant*, 248 | the creation of the *Connector* would fail with a "license not found" error. 249 | This problem has been resolved. 250 | 251 | [RTI Issue ID CON-214] 252 | 253 | Version 1.0.0 254 | ^^^^^^^^^^^^^ 255 | 256 | 1.0.0 is the first official release of *RTI Connector for Python* as well as 257 | `RTI Connector for JavaScript `__. 258 | 259 | If you had access to previous experimental releases, this release makes the product 260 | more robust, modifies most of APIs and adds new functionality. However the old 261 | APIs have been preserved for backward compatibility as much as possible. 262 | 263 | *RTI Connector* 1.0.0 is built on `RTI Connext DDS 6.0.1 `__. 264 | 265 | Vulnerability Assessment 266 | ------------------------ 267 | 268 | Internally, *Connector* relies on Lua. RTI has assessed the current version of 269 | Lua used by *Connector*, version 5.2, and found that *Connector* is not currently 270 | affected by any of the publicly disclosed vulnerabilities in Lua 5.2. 271 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | docutils==0.18.1 2 | sphinx==5.3.0 3 | sphinx-rtd-theme==1.2.0 4 | -------------------------------------------------------------------------------- /docs/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rticommunity/rticonnextdds-connector-py/c35ee9993e5036487bc47addf4f312916f542ae8/docs/static/favicon.ico -------------------------------------------------------------------------------- /docs/static/overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rticommunity/rticonnextdds-connector-py/c35ee9993e5036487bc47addf4f312916f542ae8/docs/static/overview.png -------------------------------------------------------------------------------- /docs/static/py-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rticommunity/rticonnextdds-connector-py/c35ee9993e5036487bc47addf4f312916f542ae8/docs/static/py-logo.png -------------------------------------------------------------------------------- /docs/static/rti-logo-FINALv2-White-OrangeDot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rticommunity/rticonnextdds-connector-py/c35ee9993e5036487bc47addf4f312916f542ae8/docs/static/rti-logo-FINALv2-White-OrangeDot.png -------------------------------------------------------------------------------- /docs/static/theme_overrides.css: -------------------------------------------------------------------------------- 1 | .wy-nav-content { 2 | max-width: 950px; 3 | } 4 | 5 | .wy-table-responsive table td, 6 | .wy-table-responsive table th { 7 | white-space: initial; 8 | } 9 | 10 | .wy-side-nav-search { 11 | background: #004C97; 12 | } 13 | 14 | /* override table width restrictions */ 15 | @media screen and (min-width: 767px) { 16 | 17 | .wy-table-responsive table td { 18 | /* !important prevents the common CSS stylesheets from overriding 19 | this as on RTD they are loaded after this stylesheet */ 20 | white-space: normal !important; 21 | } 22 | 23 | .wy-table-responsive { 24 | overflow: visible !important; 25 | } 26 | } -------------------------------------------------------------------------------- /docs/static/xml_doc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rticommunity/rticonnextdds-connector-py/c35ee9993e5036487bc47addf4f312916f542ae8/docs/static/xml_doc.png -------------------------------------------------------------------------------- /docs/threading.rst: -------------------------------------------------------------------------------- 1 | Threading model 2 | =============== 3 | 4 | .. py:currentmodule:: rticonnextdds_connector 5 | 6 | .. testsetup:: * 7 | 8 | import rticonnextdds_connector as rti 9 | 10 | Operations on the same :class:`Connector` instance or any contained :class:`Input` 11 | or :class:`Output` are, in general, not protected for multi-threaded access. The only 12 | exceptions are the following *wait* operations. 13 | 14 | Thread-safe operations: 15 | * :meth:`Connector.wait` (wait for data on any ``Input``) 16 | * :meth:`Output.wait` (wait for acknowledgments) 17 | * :meth:`Output.wait_for_subscriptions` 18 | * :meth:`Input.wait` (wait for data on this ``Input``) 19 | * :meth:`Input.wait_for_publications` 20 | 21 | These operations can block a thread while the same :class:`Connector` is used in 22 | a different thread. 23 | 24 | .. note:: 25 | 26 | Currently :meth:`Input.wait` and :meth:`Input.wait_for_publications` cannot 27 | both be called at the same time on the same ``Input`` instance. 28 | 29 | .. note:: 30 | 31 | :meth:`Output.write` can block the current thread under certain 32 | circumstances, but :meth:`Output.write` is not thread-safe. 33 | 34 | All operations on **different** :class:`Connector` instances are thread-safe. 35 | 36 | Applications can implement their own thread-safety mechanism around a :class:`Connector` 37 | instance. The following section provides an example. 38 | 39 | Protecting calls to Connector 40 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 41 | 42 | This example shows how to use the Python ``threading`` package to 43 | protect calls to the same :class:`Connector`: 44 | 45 | .. testcode:: 46 | 47 | import threading 48 | 49 | connector = rti.Connector("MyParticipantLibrary::MyParticipant", "ShapeExample.xml") 50 | lock = threading.RLock() 51 | 52 | def read_thread(): 53 | with lock: # Protect access to methods on the same Connector 54 | input = connector.get_input("MySubscriber::MySquareReader") 55 | 56 | input.wait() # wait outside the lock 57 | 58 | with lock: # Take the lock again 59 | input.take(); 60 | for sample in input.samples.valid_data_iter: 61 | print(sample.get_dictionary()) 62 | 63 | def write_thread(): 64 | with lock: # Protect access to methods on the same Connector 65 | output = connector.get_output("MyPublisher::MySquareWriter") 66 | output.instance['x'] = 10 67 | output.write() 68 | 69 | # Spawn read_thread and write_thread... 70 | 71 | -------------------------------------------------------------------------------- /docs/vars.rst: -------------------------------------------------------------------------------- 1 | .. |br| raw:: html 2 | 3 |
-------------------------------------------------------------------------------- /examples/python/ShapeExample.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | Connector Shape Example 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 75 | 76 | 77 | 78 | 79 | 80 | 83 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /examples/python/images/ImagesExample.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 18 | 19 | 20 | 21 | KEEP_LAST_HISTORY_QOS 22 | 1 23 | 24 | 25 | 26 | 27 | 28 | KEEP_LAST_HISTORY_QOS 29 | 1 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /examples/python/images/README.md: -------------------------------------------------------------------------------- 1 | # Example: Images 2 | 3 | ## Example description 4 | In this example `image_writer.py` publishes images at a set rate. 5 | `image_reader.py` polls at the configured interval to retrieve 6 | the most recently received image and display it. 7 | 8 | This example shows how to manipulate a more complex type. 9 | 10 | ## Running the example 11 | This examples requires *Python 3.x* and the packages **matplotlib** and **numpy**[^1]: 12 | 13 | $ pip install matplotlib numpy 14 | 15 | To run the example, in different shells, run any number of instances of the writer and the reader in any order: 16 | 17 | $ python image_writer.py 18 | $ python image_reader.py 19 | 20 | `image_reader.py` requires a graphical backend for *matplotlib*. If your python 21 | installation doesn't have one, you can run `image_reader_file.py` instead, which 22 | will save the last image into a file called *image.png*. 23 | 24 | [^1]: On ARMv8 architectures, version 1.19.5 of numpy is not supported, due to 25 | [numpy issue 18131](https://github.com/numpy/numpy/issues/18131). 26 | Version 1.19.4 of numpy has been tested to work on ARMv8 architectures. 27 | -------------------------------------------------------------------------------- /examples/python/images/image_reader.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # (c) Copyright, Real-Time Innovations, 2019. All rights reserved. # 3 | # No duplications, whole or partial, manual or electronic, may be made # 4 | # without express written permission. Any such copies, or revisions thereof, # 5 | # must display this notice unaltered. # 6 | # This code contains trade secrets of Real-Time Innovations, Inc. # 7 | ############################################################################### 8 | 9 | """Reads images and displays them""" 10 | 11 | from __future__ import print_function 12 | from sys import path as sys_path 13 | from os import path as os_path 14 | import matplotlib.pyplot as plot 15 | import matplotlib.animation as animation 16 | import numpy 17 | 18 | # Updating the system path is not required if you have pip-installed 19 | # rticonnextdds-connector 20 | file_path = os_path.dirname(os_path.realpath(__file__)) 21 | sys_path.append(file_path + "/../../../") 22 | import rticonnextdds_connector as rti 23 | 24 | 25 | with rti.open_connector( 26 | config_name="MyParticipantLibrary::ImageSubParticipant", 27 | url=file_path + "/ImagesExample.xml") as connector: 28 | 29 | # Create a blank image 30 | fig = plot.figure() 31 | fig.canvas.set_window_title("No image received") 32 | blank_image = plot.imshow(numpy.zeros((40, 60, 3)), animated=True) 33 | 34 | input = connector.get_input("MySubscriber::ImageReader") 35 | 36 | def read_and_draw(_frame): 37 | """The animation function, called periodically in a set interval, reads the 38 | last image received and draws it""" 39 | 40 | # The Qos configuration guarantees we will only have the last sample; 41 | # we read (not take) so we can access it again in the next interval if 42 | # we don't receive a new one 43 | input.read() 44 | for sample in input.samples.valid_data_iter: 45 | # Get the received pixels (a linear sequence) 46 | raw_pixels = numpy.array(sample["pixels"]) 47 | 48 | # Convert the linear sequence of pixels into an Height x Width x 3 49 | # matrix of RGB pixels that we can draw with imgshow 50 | image_dim = ( 51 | int(sample["resolution.height"]), 52 | int(sample["resolution.width"]), 53 | 3) 54 | structured_pixels = raw_pixels.reshape(image_dim) 55 | 56 | # Draw the image 57 | image = plot.imshow(structured_pixels) 58 | 59 | # Set the title of the image 60 | fig.canvas.set_window_title("Image received from " + sample["source"]) 61 | return image, 62 | return blank_image, # when we haven't received any image 63 | 64 | # Create the animation and show 65 | ani = animation.FuncAnimation(fig, read_and_draw, interval=500, blit=True) 66 | 67 | # Show the image and block until the window is closed 68 | plot.show() 69 | print("Exiting...") 70 | -------------------------------------------------------------------------------- /examples/python/images/image_reader_file.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # (c) 2005-2015 Copyright, Real-Time Innovations. All rights reserved. # 3 | # No duplications, whole or partial, manual or electronic, may be made # 4 | # without express written permission. Any such copies, or revisions thereof, # 5 | # must display this notice unaltered. # 6 | # This code contains trade secrets of Real-Time Innovations, Inc. # 7 | ############################################################################### 8 | 9 | """Reads images and saves them into a file""" 10 | 11 | from __future__ import print_function 12 | from sys import path as sys_path 13 | from os import path as os_path 14 | from time import sleep 15 | import matplotlib.pyplot as plot 16 | import matplotlib 17 | import numpy 18 | 19 | # Updating the system path is not required if you have pip-installed 20 | # rticonnextdds-connector 21 | file_path = os_path.dirname(os_path.realpath(__file__)) 22 | sys_path.append(file_path + "/../../../") 23 | import rticonnextdds_connector as rti 24 | 25 | matplotlib.use('Agg') # Non-GUI backend 26 | 27 | with rti.open_connector( 28 | config_name="MyParticipantLibrary::ImageSubParticipant", 29 | url=file_path + "/ImagesExample.xml") as connector: 30 | 31 | # Create a blank image 32 | fig = plot.figure() 33 | blank_image = plot.imshow(numpy.zeros((40, 60, 3))) 34 | 35 | input = connector.get_input("MySubscriber::ImageReader") 36 | 37 | while True: 38 | input.read() 39 | for sample in input.samples.valid_data_iter: 40 | # Get the received pixels (a linear sequence) 41 | raw_pixels = numpy.array(sample["pixels"]) 42 | 43 | # Convert the linear sequence of pixels into an Height x Width x 3 44 | # matrix of RGB pixels that we can draw with imgshow 45 | image_dim = ( 46 | int(sample["resolution.height"]), 47 | int(sample["resolution.width"]), 48 | 3) 49 | structured_pixels = raw_pixels.reshape(image_dim) 50 | 51 | # Draw and save the image 52 | image = plot.imshow(structured_pixels) 53 | plot.savefig('image.png') 54 | 55 | sleep(.5) # Poll every half second 56 | -------------------------------------------------------------------------------- /examples/python/images/image_writer.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # (c) 2019 Copyright, Real-Time Innovations. All rights reserved. # 3 | # No duplications, whole or partial, manual or electronic, may be made # 4 | # without express written permission. Any such copies, or revisions thereof, # 5 | # must display this notice unaltered. # 6 | # This code contains trade secrets of Real-Time Innovations, Inc. # 7 | ############################################################################### 8 | 9 | """Publishes random images""" 10 | 11 | from sys import path as sys_path 12 | from os import path as os_path 13 | from os import getpid 14 | from time import sleep 15 | import numpy as np, random 16 | 17 | # Updating the system path is not required if you have pip-installed 18 | # rticonnextdds-connector 19 | file_path = os_path.dirname(os_path.realpath(__file__)) 20 | sys_path.append(file_path + "/../../../") 21 | import rticonnextdds_connector as rti 22 | 23 | def random_rgb(): 24 | """Generate a random RGB pixel""" 25 | return [int(255*random.random()), int(255*random.random()), int(255*random.random())] 26 | 27 | def generate_random_image(size): 28 | """Generate a random flat list of pixels of 3 different colors""" 29 | random_colors = [random_rgb() for i in range(3)] 30 | pixels = np.array([random.choice(random_colors) for i in range(size)]) 31 | return pixels.reshape(-1).tolist() # Flatten out and convert to list 32 | 33 | def update_image(pixels, _iteration): 34 | """Shift 3 elements (one pixel)""" 35 | return pixels[3:] + pixels[:3] 36 | 37 | with rti.open_connector( 38 | config_name="MyParticipantLibrary::ImagePubParticipant", 39 | url=file_path + "/ImagesExample.xml") as connector: 40 | 41 | output = connector.get_output("MyPublisher::ImageWriter") 42 | 43 | # Identify the source of the image with the process id 44 | output.instance["source"] = "ImageWriter " + str(getpid()) 45 | current_pixels = generate_random_image(40 * 60) 46 | 47 | print("Writing...") 48 | for i in range(1, 100): 49 | # Write images with different orientations 50 | if i % 10 != 0: 51 | output.instance.set_dictionary({"resolution":{"height":40, "width":60}}) 52 | else: 53 | output.instance.set_dictionary({"resolution":{"height":60, "width":40}}) 54 | 55 | # Create a new image 56 | current_pixels = update_image(current_pixels, i) 57 | output.instance.set_dictionary({"pixels":current_pixels}) 58 | 59 | output.write() 60 | sleep(.5) # Write at a rate of one sample every 1 seconds, for ex. 61 | 62 | # Note: we don't call output.wait() because this output is configured 63 | # with best-effort reliability, and therefore it won't wait for acknowledgments 64 | print("Exiting...") 65 | -------------------------------------------------------------------------------- /examples/python/simple/README.md: -------------------------------------------------------------------------------- 1 | # Simple Example 2 | 3 | ## Example description 4 | In this simple example, `writer.py` periodically publishes data for a 5 | *Square* topic, and `reader.py` subscribes to the topic and prints all the 6 | data samples it receives. 7 | 8 | ## Running the example 9 | 10 | Run the following commands in different shells: 11 | 12 | $ python reader.py 13 | $ python writer.py 14 | 15 | You can run these commands in any order, and you can run multiple instances of 16 | the writer and the reader. You can also run any other *DDS* application that 17 | publishes or subscribes to the *Square* topic. For example, you can use 18 | [RTI Shapes Demo](https://www.rti.com/free-trial/shapes-demo). 19 | 20 | -------------------------------------------------------------------------------- /examples/python/simple/reader.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # (c) 2005-2015 Copyright, Real-Time Innovations. All rights reserved. # 3 | # No duplications, whole or partial, manual or electronic, may be made # 4 | # without express written permission. Any such copies, or revisions thereof, # 5 | # must display this notice unaltered. # 6 | # This code contains trade secrets of Real-Time Innovations, Inc. # 7 | ############################################################################### 8 | 9 | from __future__ import print_function 10 | 11 | # Updating the system path is not required if you have pip-installed 12 | # rticonnextdds-connector 13 | from sys import path as sys_path 14 | from os import path as os_path 15 | file_path = os_path.dirname(os_path.realpath(__file__)) 16 | sys_path.append(file_path + "/../../../") 17 | 18 | import rticonnextdds_connector as rti 19 | 20 | with rti.open_connector( 21 | config_name="MyParticipantLibrary::MySubParticipant", 22 | url=file_path + "/../ShapeExample.xml") as connector: 23 | 24 | input = connector.get_input("MySubscriber::MySquareReader") 25 | 26 | print("Waiting for publications...") 27 | input.wait_for_publications() # wait for at least one matching publication 28 | 29 | print("Waiting for data...") 30 | for i in range(1, 500): 31 | input.wait() # wait for data on this input 32 | input.take() 33 | for sample in input.samples.valid_data_iter: 34 | # You can get all the fields in a get_dictionary() 35 | data = sample.get_dictionary() 36 | x = data['x'] 37 | y = data['y'] 38 | 39 | # Or you can access the field individually 40 | size = sample.get_number("shapesize") 41 | color = sample.get_string("color") 42 | print("Received x: " + repr(x) + " y: " + repr(y) + 43 | " size: " + repr(size) + " color: " + repr(color)) 44 | -------------------------------------------------------------------------------- /examples/python/simple/writer.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # (c) 2005-2015 Copyright, Real-Time Innovations. All rights reserved. # 3 | # No duplications, whole or partial, manual or electronic, may be made # 4 | # without express written permission. Any such copies, or revisions thereof, # 5 | # must display this notice unaltered. # 6 | # This code contains trade secrets of Real-Time Innovations, Inc. # 7 | ############################################################################### 8 | 9 | from time import sleep 10 | 11 | # Updating the system path is not required if you have pip-installed 12 | # rticonnextdds-connector 13 | from sys import path as sys_path 14 | from os import path as os_path 15 | file_path = os_path.dirname(os_path.realpath(__file__)) 16 | sys_path.append(file_path + "/../../../") 17 | 18 | import rticonnextdds_connector as rti 19 | 20 | with rti.open_connector( 21 | config_name="MyParticipantLibrary::MyPubParticipant", 22 | url=file_path + "/../ShapeExample.xml") as connector: 23 | 24 | output = connector.get_output("MyPublisher::MySquareWriter") 25 | 26 | print("Waiting for subscriptions...") 27 | output.wait_for_subscriptions() 28 | 29 | print("Writing...") 30 | for i in range(1, 100): 31 | output.instance.set_number("x", i) 32 | output.instance.set_number("y", i*2) 33 | output.instance.set_number("shapesize", 30) 34 | output.instance.set_string("color", "BLUE") 35 | output.write() 36 | 37 | sleep(0.5) # Write at a rate of one sample every 0.5 seconds, for ex. 38 | 39 | print("Exiting...") 40 | output.wait() # Wait for all subscriptions to receive the data before exiting -------------------------------------------------------------------------------- /examples/python/transformation/README.md: -------------------------------------------------------------------------------- 1 | # Example: Data Transformation 2 | 3 | ## Example description 4 | In this example `transform.py` subscribes to the *Square* topic, applies 5 | a simple transformation to the data it receives, and publishes it into a topic of 6 | the same type, *Circle*. 7 | 8 | ## Running the example 9 | To run the example: 10 | 11 | * Run any *DDS* application that publishes the *Square* topic. For example, run 12 | `python ../simple/writer.py`; or run 13 | [RTI Shapes Demo](https://www.rti.com/free-trial/shapes-demo) and create 14 | a *Square* publisher. 15 | * Run any *DDS* application that subscribes to the *Circle* topic. For example, 16 | run `python reader.py`; or run 17 | [RTI Shapes Demo](https://www.rti.com/free-trial/shapes-demo) and create a 18 | *Circle* subscriber. 19 | * Run the transformation application: `python transform.py` -------------------------------------------------------------------------------- /examples/python/transformation/reader.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # (c) 2019 Copyright, Real-Time Innovations. All rights reserved. # 3 | # No duplications, whole or partial, manual or electronic, may be made # 4 | # without express written permission. Any such copies, or revisions thereof, # 5 | # must display this notice unaltered. # 6 | # This code contains trade secrets of Real-Time Innovations, Inc. # 7 | ############################################################################### 8 | 9 | """Reads and prints the Circle topic""" 10 | 11 | from __future__ import print_function 12 | from sys import path as sys_path 13 | from os import path as os_path 14 | 15 | file_path = os_path.dirname(os_path.realpath(__file__)) 16 | sys_path.append(file_path + "/../../../") 17 | 18 | import rticonnextdds_connector as rti 19 | 20 | with rti.open_connector( 21 | config_name="MyParticipantLibrary::CircleSubParticipant", 22 | url=file_path + "/../ShapeExample.xml") as connector: 23 | 24 | input = connector.get_input("MySubscriber::MyCircleReader") 25 | 26 | for i in range(1, 500): 27 | input.wait() # wait for data on this input 28 | input.take() 29 | for sample in input.samples.valid_data_iter: 30 | print("Received circle: " + str(sample.get_dictionary())) 31 | -------------------------------------------------------------------------------- /examples/python/transformation/transform.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # (c) Copyright, Real-Time Innovations, 2019. All rights reserved. # 3 | # No duplications, whole or partial, manual or electronic, may be made # 4 | # without express written permission. Any such copies, or revisions thereof, # 5 | # must display this notice unaltered. # 6 | # This code contains trade secrets of Real-Time Innovations, Inc. # 7 | ############################################################################### 8 | 9 | """Reads Squares, transforms them and writes them as Circles.""" 10 | 11 | from sys import path as sys_path 12 | from os import path as os_path 13 | 14 | file_path = os_path.dirname(os_path.realpath(__file__)) 15 | sys_path.append(file_path + "/../../../") 16 | import rticonnextdds_connector as rti 17 | 18 | with rti.open_connector( 19 | config_name="MyParticipantLibrary::TransformationParticipant", 20 | url=file_path + "/../ShapeExample.xml") as connector: 21 | 22 | input = connector.get_input("MySubscriber::MySquareReader") 23 | output = connector.get_output("MyPublisher::MyCircleWriter") 24 | 25 | # Read data from the input, transform it and write it into the output 26 | print("Waiting for data...") 27 | while True: 28 | input.wait() # Wait for data in the input 29 | input.take() 30 | for sample in input.samples.valid_data_iter: 31 | data = sample.get_dictionary() 32 | 33 | data["x"], data["y"] = data["y"], data["x"] 34 | data["color"] = "RED" 35 | 36 | output.instance.set_dictionary(data) 37 | output.write() 38 | -------------------------------------------------------------------------------- /resources/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | # (c) 2024 Copyright, Real-Time Innovations, Inc. All rights reserved. 2 | # No duplications, whole or partial, manual or electronic, may be made 3 | # without express written permission. Any such copies, or revisions thereof, 4 | # must display this notice unaltered. 5 | # This code contains trade secrets of Real-Time Innovations, Inc. 6 | FROM quay.io/pypa/manylinux2010_x86_64:2021-02-06-3d322a5 7 | 8 | ARG USER_NAME=jenkins 9 | ARG USER_UID 10 | 11 | RUN adduser $USER_NAME -u $USER_UID 12 | 13 | # Install Python dependencies 14 | RUN /opt/python/cp35-cp35m/bin/pip install --upgrade pip 15 | RUN /opt/python/cp35-cp35m/bin/pip install cmake==3.12.0 wheel twine 16 | 17 | ENV PATH=/home/jenkins/.local/bin:$PATH 18 | ENV PATH=/opt/python/cp27-cp27m/bin:$PATH 19 | ENV PATH=/opt/python/cp35-cp35m/bin:$PATH 20 | ENV PATH=/opt/python/cp36-cp36m/bin:$PATH 21 | -------------------------------------------------------------------------------- /resources/docker/documentation.Dockerfile: -------------------------------------------------------------------------------- 1 | # (c) 2024 Copyright, Real-Time Innovations, Inc. All rights reserved. 2 | # No duplications, whole or partial, manual or electronic, may be made 3 | # without express written permission. Any such copies, or revisions thereof, 4 | # must display this notice unaltered. 5 | # This code contains trade secrets of Real-Time Innovations, Inc. 6 | FROM python:3.6 7 | 8 | ARG USER_NAME=jenkins 9 | ARG USER_UID 10 | 11 | RUN adduser $USER_NAME -u $USER_UID 12 | ENV PATH=/home/jenkins/.local/bin:$PATH 13 | 14 | RUN pip install cmake==3.12.0 15 | 16 | RUN mkdir -p /tmp/awscli/ \ 17 | && curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "/tmp/awscli/awscliv2.zip" \ 18 | && unzip /tmp/awscli/awscliv2.zip -d /tmp/awscli \ 19 | && /tmp/awscli/aws/install \ 20 | && rm -rf /tmp/awscli 21 | -------------------------------------------------------------------------------- /resources/jenkins/build_and_test.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * (c) Copyright, Real-Time Innovations, 2024. All rights reserved. 3 | * RTI grants Licensee a license to use, modify, compile, and create derivative 4 | * works of the software solely for use with RTI Connext DDS. Licensee may 5 | * redistribute copies of the software provided that all such copies are subject 6 | * to this license. The software is provided "as is", with no warranty of any 7 | * type, including any warranty for fitness for any purpose. RTI is under no 8 | * obligation to maintain or support the software. RTI shall not be liable for 9 | * any incidental or consequential damages arising out of the use or inability 10 | * to use the software. 11 | */ 12 | 13 | pipeline { 14 | agent { 15 | dockerfile { 16 | additionalBuildArgs "--build-arg USER_UID=789" 17 | dir 'resources/docker' 18 | label 'docker' 19 | } 20 | } 21 | 22 | options { 23 | disableConcurrentBuilds() 24 | /* 25 | To avoid excessive resource usage in server, we limit the number 26 | of builds to keep in pull requests 27 | */ 28 | buildDiscarder( 29 | logRotator( 30 | artifactDaysToKeepStr: '', 31 | artifactNumToKeepStr: '', 32 | daysToKeepStr: '', 33 | /* 34 | For pull requests only keep the last 10 builds, for regular 35 | branches keep up to 20 builds. 36 | */ 37 | numToKeepStr: changeRequest() ? '10' : '20' 38 | ) 39 | ) 40 | // Set a timeout for the entire pipeline 41 | timeout(time: 30, unit: 'MINUTES') 42 | } 43 | 44 | stages { 45 | stage('Download dependencies') { 46 | steps { 47 | downloadAndExtract( 48 | installDirectory: '.', 49 | flavour: 'connectorlibs' 50 | ) 51 | } 52 | } 53 | 54 | stage('Test') { 55 | steps { 56 | sh "pip install tox" 57 | sh "tox" 58 | } 59 | 60 | post { 61 | always { 62 | junit(testResults: 'tests-py*.xml') 63 | } 64 | } 65 | } 66 | 67 | stage('Publish') { 68 | when { 69 | beforeAgent true 70 | tag pattern: /v\d+\.\d+\.\d+-dev/, comparator: "REGEXP" 71 | } 72 | 73 | steps { 74 | sh 'python setup.py sdist' 75 | sh 'python setup.py bdist_wheel' 76 | 77 | withCredentials ([ 78 | usernamePassword(credentialsId: 'pypi-credentials', usernameVariable: 'TWINE_USERNAME', passwordVariable: 'TWINE_PASSWORD'), 79 | string(credentialsId: 'pypi-server', variable: 'TWINE_REPOSITORY_URL') 80 | ]) { 81 | sh 'twine upload dist/*' 82 | } 83 | } 84 | } 85 | } 86 | 87 | post { 88 | cleanup { 89 | cleanWs() 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /resources/jenkins/build_doc.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * (c) Copyright, Real-Time Innovations, 2024. All rights reserved. 3 | * RTI grants Licensee a license to use, modify, compile, and create derivative 4 | * works of the software solely for use with RTI Connext DDS. Licensee may 5 | * redistribute copies of the software provided that all such copies are subject 6 | * to this license. The software is provided "as is", with no warranty of any 7 | * type, including any warranty for fitness for any purpose. RTI is under no 8 | * obligation to maintain or support the software. RTI shall not be liable for 9 | * any incidental or consequential damages arising out of the use or inability 10 | * to use the software. 11 | */ 12 | 13 | pipeline { 14 | agent { 15 | dockerfile { 16 | additionalBuildArgs "--build-arg USER_UID=789" 17 | dir 'resources/docker' 18 | filename 'documentation.Dockerfile' 19 | label 'docker' 20 | } 21 | } 22 | 23 | options { 24 | disableConcurrentBuilds() 25 | /* 26 | To avoid excessive resource usage in server, we limit the number 27 | of builds to keep in pull requests 28 | */ 29 | buildDiscarder( 30 | logRotator( 31 | artifactDaysToKeepStr: '', 32 | artifactNumToKeepStr: '', 33 | daysToKeepStr: '', 34 | /* 35 | For pull requests only keep the last 10 builds, for regular 36 | branches keep up to 20 builds. 37 | */ 38 | numToKeepStr: changeRequest() ? '10' : '20' 39 | ) 40 | ) 41 | // Set a timeout for the entire pipeline 42 | timeout(time: 30, unit: 'MINUTES') 43 | } 44 | 45 | stages { 46 | stage('Download dependencies') { 47 | steps { 48 | downloadAndExtract( 49 | installDirectory: ".", 50 | flavour: 'connectorlibs' 51 | ) 52 | 53 | sh 'pip install --upgrade pip' 54 | sh 'pip install -r docs/requirements.txt --no-cache-dir' 55 | } 56 | } 57 | 58 | stage('Build doc') { 59 | steps { 60 | dir('docs') { 61 | sh 'make html' 62 | } 63 | } 64 | 65 | post { 66 | success { 67 | publishHTML( 68 | [ 69 | allowMissing: false, 70 | alwaysLinkToLastBuild: false, 71 | keepAll: false, 72 | reportDir: 'docs/_build/html/', 73 | reportFiles: 'index.html', 74 | reportName: 'Connector Documentation', 75 | reportTitles: 'Connector Documentation' 76 | ] 77 | ) 78 | } 79 | } 80 | } 81 | 82 | stage('Publish doc') { 83 | when { 84 | tag pattern: /v\d+\.\d+\.\d+-doc/, comparator: "REGEXP" 85 | } 86 | 87 | steps { 88 | script { 89 | def docVersion = env.TAG_NAME.split('-')[0] 90 | docVersion = docVersion.replace('v', '') 91 | 92 | withAWSCredentials { 93 | withCredentials([ 94 | string(credentialsId: 's3-doc-bucket', variable: 'S3_DOC_BUCKET'), 95 | ]) { 96 | sh "aws s3 sync --acl public-read docs/_build/html/ s3://\$S3_DOC_BUCKET/documentation/connector/${docVersion}/api/python/" 97 | } 98 | } 99 | } 100 | } 101 | } 102 | } 103 | 104 | post { 105 | cleanup { 106 | cleanWs() 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /rticonnextdds_connector/__init__.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # (c) 2005-2015 Copyright, Real-Time Innovations. All rights reserved. # 3 | # No duplications, whole or partial, manual or electronic, may be made # 4 | # without express written permission. Any such copies, or revisions thereof, # 5 | # must display this notice unaltered. # 6 | # This code contains trade secrets of Real-Time Innovations, Inc. # 7 | ############################################################################### 8 | 9 | from .rticonnextdds_connector import * 10 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """A setuptools based setup module. 2 | 3 | See: 4 | https://packaging.python.org/en/latest/distributing.html 5 | https://github.com/pypa/sampleproject 6 | """ 7 | 8 | # Always prefer setuptools over distutils 9 | from setuptools import setup, find_packages 10 | # To use a consistent encoding 11 | from codecs import open 12 | from os import path, walk 13 | from shutil import copytree 14 | 15 | here = path.abspath(path.dirname(__file__)) 16 | 17 | # Get the long description from the README file 18 | with open(path.join(here, 'README.rst'), encoding='utf-8') as f: 19 | long_description = f.read() 20 | 21 | 22 | # get list of dds lib binaries to include 23 | lib_files = [] 24 | for root, dirs, files in walk('rticonnextdds-connector/lib'): 25 | for file in files: 26 | lib_files.append(path.abspath(path.join(root, file))) 27 | 28 | module_dir = 'rticonnextdds_connector' 29 | 30 | setup( 31 | name=module_dir, 32 | 33 | # Versions should comply with PEP440. For a discussion on single-sourcing 34 | # the version across setup.py and the project code, see 35 | # https://packaging.python.org/en/latest/single_source_version.html 36 | version='1.2.4', 37 | 38 | description='RTI Connector for Python', 39 | long_description=long_description, 40 | 41 | # The project's main homepage. 42 | url='https://github.com/rticommunity/rticonnextdds-connector-py', 43 | 44 | # Author details 45 | author='RTI', 46 | author_email='support@rti.com', 47 | 48 | # Choose your license 49 | license='RTI', 50 | 51 | # See https://pypi.python.org/pypi?%3Aaction=list_classifiers 52 | classifiers=[ 53 | # How mature is this project? Common values are 54 | # 3 - Alpha 55 | # 4 - Beta 56 | # 5 - Production/Stable 57 | 'Development Status :: 5 - Production/Stable', 58 | 59 | # Indicate who your project is intended for 60 | 'Intended Audience :: Developers', 61 | 'Topic :: System :: Distributed Computing', 62 | 63 | # Pick your license as you wish (should match "license" above) 64 | 'License :: Other/Proprietary License', 65 | 66 | # Specify the Python versions you support here. In particular, ensure 67 | # that you indicate whether you support Python 2, Python 3 or both. 68 | 'Programming Language :: Python :: 2', 69 | 'Programming Language :: Python :: 2.6', 70 | 'Programming Language :: Python :: 2.7', 71 | 'Programming Language :: Python :: 3', 72 | ], 73 | 74 | # What does your project relate to? 75 | keywords='rti dds connext connector pub sub pub-sub iot', 76 | 77 | # You can just specify the packages manually here if your project is 78 | # simple. Or you can use find_packages(). 79 | packages=[module_dir], 80 | 81 | # Alternatively, if you want to distribute just a my_module.py, uncomment 82 | # this:ex 83 | #py_modules=["rticonnextdds_connector"], 84 | 85 | # List run-time dependencies here. These will be installed by pip when 86 | # your project is installed. For an analysis of "install_requires" vs pip's 87 | # requirements files see: 88 | # https://packaging.python.org/en/latest/requirements.html 89 | install_requires=[''], 90 | 91 | # List additional groups of dependencies here (e.g. development 92 | # dependencies). You can install these using the following syntax, 93 | # for example: 94 | # $ pip install -e .[dev,test] 95 | extras_require={}, 96 | 97 | # If there are data files included in your packages that need to be 98 | # installed, specify them here. If using Python 2.6 or less, then these 99 | # have to be included in MANIFEST.in as well. 100 | package_data={ 101 | module_dir: lib_files 102 | }, 103 | 104 | # Although 'package_data' is the preferred approach, in some case you may 105 | # need to place data files outside of your packages. See: 106 | # http://docs.python.org/3.4/distutils/setupscript.html#installing-additional-files # noqa 107 | # In this case, 'data_file' will be installed into '/my_data' 108 | data_files=[], 109 | 110 | # To provide executable scripts, use entry points in preference to the 111 | # "scripts" keyword. Entry points provide cross-platform support and allow 112 | # pip to create the appropriate form of executable for the target platform. 113 | entry_points={}, 114 | ) 115 | -------------------------------------------------------------------------------- /test/python/README.md: -------------------------------------------------------------------------------- 1 | # Python Testing Documentation 2 | 3 | To run the tests for python binding of **rticonnextdds_connector**: 4 | 5 | 1. Install [pytest](https://pytest.org/latest/contents.html) with: 6 | 7 | ``pip install pytest`` 8 | 9 | 2. To execute all the tests, issue the following command from the base directory: 10 | 11 | ``pytest ./test/python`` 12 | 13 | To execute each test individually, also include the name of the test file: 14 | 15 | ``pytest ./test/python/test_rticonnextdds_input.py`` 16 | 17 | **Note:** Some tests are marked to fail with ``@pytest.mark.xfail`` annotation either because those tests are expected to fail due to implicit type conversion or because the functionality being tested is not yet supported by the python connector library. These tests will be reported as ``xfail``. 18 | 19 | 20 | All the tests are documented in their respective source files following the [docstrings](https://www.python.org/dev/peps/pep-0257/) 21 | convention. [Sphinx apidoc](http://www.sphinx-doc.org/en/stable/man/sphinx-apidoc.html) can be used for automatically generating the test API documentation. 22 | 23 | Python tests are organized as follows: 24 | 25 | 1. ``conftest.py``: Contains [pytest fixtures](https://pytest.org/latest/fixture.html) that are used for configuring the tests. 26 | 2. ``test_rticonnextdds_connector.py``: Contains tests for ``rticonnextdds_connector.Connector`` object 27 | 3. ``test_rticonnextdds_input.py``: Contains tests for ``rticonnextdds_connector.Input`` object 28 | 3. ``test_rticonnextdds_output.py``: Contains tests for ``rticonnextdds_connector.Output`` object 29 | 4. ``test_rticonnextdds_dataflow.py``: Tests the dataflow between an ``rticonnextdds_connector.Input`` and ``rticonnextdds_connector.Output`` object. 30 | 4. ``test_rticonnextdds_data_access.py``: Tests the various methods available to access data from a ``rticonnextdds_connector.SampleIterator``object. 31 | -------------------------------------------------------------------------------- /test/python/conftest.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # (c) 2005-2019 Copyright, Real-Time Innovations. All rights reserved. # 3 | # No duplications, whole or partial, manual or electronic, may be made # 4 | # without express written permission. Any such copies, or revisions thereof, # 5 | # must display this notice unaltered. # 6 | # This code contains trade secrets of Real-Time Innovations, Inc. # 7 | ############################################################################### 8 | 9 | import sys, os, pytest, threading, time 10 | 11 | sys.path.append(os.path.dirname(os.path.realpath(__file__))+ "/../../") 12 | import rticonnextdds_connector as rti 13 | 14 | """ 15 | This module contains pytest fixtures used for testing connector code. 16 | """ 17 | 18 | @pytest.fixture(scope="session") 19 | def rtiConnectorFixture(request): 20 | """ 21 | This `pytest fixture `_ 22 | creates a session-scoped :class:`rticonnextdds_connector.Connector` object 23 | which is returned everytime this fixture method is referred. 24 | The initialized Connector object is cleaned up at the end 25 | of a testing session. 26 | 27 | ``MyParticipantLibrary::Zero`` `participant 28 | `_ 29 | profile in ``test/xml/TestConnector.xml`` `application profile 30 | `_ 31 | is used for initializing the Connector object. 32 | 33 | :param request: builtin pytest request fixture 34 | :type request: `pytest.FixtureRequest `_ 35 | :returns: session-scoped Connector for testing 36 | :rtype: :class:`rticonnextdds_connector.Connector` 37 | 38 | """ 39 | xml_path= os.path.join(os.path.dirname(os.path.realpath(__file__)), 40 | "../xml/TestConnector.xml") 41 | participant_profile="MyParticipantLibrary::Zero" 42 | rti_connector = rti.Connector(participant_profile,xml_path) 43 | 44 | def cleanup(): 45 | rti_connector.close() 46 | 47 | request.addfinalizer(cleanup) 48 | return rti_connector 49 | 50 | @pytest.fixture(scope="session") 51 | def rtiInputFixture(rtiConnectorFixture): 52 | """ 53 | This `pytest fixture `_ 54 | creates a session-scoped :class:`rticonnextdds_connector.Input` object 55 | which is returned everytime this fixture method is referred. 56 | The initialized Input object is cleaned up at the end 57 | of a testing session. 58 | 59 | ``MySubscriber::MySquareReader`` `datareader 60 | `_ in 61 | ``test/xml/TestConnector.xml`` `application profile 62 | `_ 63 | is used for initializing the Input object. 64 | 65 | :param rtiConnectorFixture: :func:`rtiConnectorFixture` 66 | :type rtiConnectorFixture: `pytest.fixture `_ 67 | :returns: session-scoped Input object for testing 68 | :rtype: :class:`rticonnextdds_connector.Input` 69 | 70 | """ 71 | 72 | return rtiConnectorFixture.get_input("MySubscriber::MySquareReader") 73 | 74 | @pytest.fixture(scope="session") 75 | def rtiOutputFixture(rtiConnectorFixture): 76 | """ 77 | This `pytest fixture `_ 78 | creates a session-scoped :class:`rticonnextdds_connector.Output` object 79 | which is returned everytime this fixture method is referred. 80 | The initialized Output object is cleaned up at the end 81 | of a testing session. 82 | 83 | ``MyPublisher::MySquareWriter`` `datawriter 84 | `_ in 85 | ``test/xml/TestConnector.xml`` `application profile 86 | `_ 87 | is used for initializing the Output object. 88 | 89 | :param rtiConnectorFixture: :func:`rtiConnectorFixture` 90 | :type rtiConnectorFixture: `pytest.fixture `_ 91 | :returns: session-scoped Output object for testing 92 | :rtype: :class:`rticonnextdds_connector.Output` 93 | """ 94 | 95 | return rtiConnectorFixture.getOutput("MyPublisher::MySquareWriter") 96 | 97 | @pytest.fixture 98 | def one_use_connector(request): 99 | """Creates a Connector only for one test. Use this when 100 | the test can't reuse a previously created connector""" 101 | 102 | xml_path = os.path.join( 103 | os.path.dirname(os.path.realpath(__file__)), 104 | "../xml/TestConnector.xml") 105 | 106 | participant_profile="MyParticipantLibrary::SingleUseParticipant" 107 | with rti.open_connector(participant_profile, xml_path) as rti_connector: 108 | yield rti_connector 109 | 110 | @pytest.fixture 111 | def one_use_output(one_use_connector): 112 | return one_use_connector.get_output("MyPublisher::MySquareWriter") 113 | 114 | @pytest.fixture 115 | def one_use_input(one_use_connector): 116 | return one_use_connector.get_input("MySubscriber::MySquareReader") 117 | 118 | def pytest_addoption(parser): 119 | parser.addoption("--iterations", action="store", default=100) 120 | 121 | def pytest_generate_tests(metafunc): 122 | # This is called for every test. Only get/set command line arguments 123 | # if the argument is specified in the list of test "fixturenames". 124 | option_value = metafunc.config.option.iterations 125 | if 'iterations' in metafunc.fixturenames and option_value is not None: 126 | metafunc.parametrize("iterations", [int(option_value)]) -------------------------------------------------------------------------------- /test/python/test_rticonnextdds_connector.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # (c) 2005-2015 Copyright, Real-Time Innovations. All rights reserved. # 3 | # No duplications, whole or partial, manual or electronic, may be made # 4 | # without express written permission. Any such copies, or revisions thereof, # 5 | # must display this notice unaltered. # 6 | # This code contains trade secrets of Real-Time Innovations, Inc. # 7 | ############################################################################### 8 | 9 | from platform import version 10 | import sys 11 | import os 12 | import re 13 | sys.path.append(os.path.dirname(os.path.realpath(__file__)) + "/../../") 14 | import rticonnextdds_connector as rti 15 | import pytest 16 | 17 | class TestConnector: 18 | """ 19 | This class tests the correct instantiation of 20 | :class:`rticonnextdds_connector.Connector` object. 21 | """ 22 | 23 | def test_invalid_xml_path(self): 24 | """ 25 | This test function ensures that a ValueError is raised if 26 | an incorrect xml path is passed to the 27 | Connector constructor. 28 | """ 29 | participant_profile = "MyParticipantLibrary::Zero" 30 | invalid_xml_path = "invalid/path/to/xml" 31 | with pytest.raises(rti.Error): 32 | rti.Connector(participant_profile, invalid_xml_path) 33 | 34 | def test_invalid_participant_profile(self): 35 | """ 36 | This test function ensures that a ValueError is raised if 37 | an invalid participant profile name is passed to the 38 | Connector constructor. 39 | """ 40 | invalid_participant_profile = "InvalidParticipantProfile" 41 | xml_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 42 | "../xml/TestConnector.xml") 43 | with pytest.raises(rti.Error): 44 | rti.Connector(invalid_participant_profile, xml_path) 45 | 46 | def test_ivalid_xml_profile(self): 47 | """ 48 | This test function ensures that a ValueError is raised if 49 | an invalid xml file is passed to the 50 | Connector constructor. 51 | """ 52 | participant_profile = "MyParticipantLibrary::Zero" 53 | invalid_xml = os.path.join(os.path.dirname(os.path.realpath(__file__)), 54 | "../xml/InvalidXml.xml") 55 | with pytest.raises(rti.Error): 56 | rti.Connector(participant_profile, invalid_xml) 57 | 58 | def test_connector_creation(self, rtiConnectorFixture): 59 | """ 60 | This function tests the correct instantiation of 61 | Connector object. 62 | """ 63 | assert rtiConnectorFixture is not None and isinstance( 64 | rtiConnectorFixture, rti.Connector) 65 | 66 | def test_multiple_connector_creation(self): 67 | """ 68 | This function tests the correct instantiation of multiple 69 | Connector objects in succession. 70 | """ 71 | connectors = [] 72 | xml_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 73 | "../xml/TestConnector.xml") 74 | participant_profile = "MyParticipantLibrary::Zero" 75 | for _ in range(0, 5): 76 | connectors.append(rti.Connector(participant_profile, xml_path)) 77 | assert all(x is not None and isinstance(x, rti.Connector) 78 | for x in connectors) 79 | 80 | def test_load_multiple_files(self): 81 | """ 82 | Tests that it is possible to load two xml files using the url group syntax 83 | """ 84 | xml_path1 = os.path.join(os.path.dirname(os.path.realpath(__file__)), 85 | "../xml/TestConnector.xml") 86 | xml_path2 = os.path.join(os.path.dirname(os.path.realpath(__file__)), 87 | "../xml/TestConnector2.xml") 88 | with rti.open_connector( 89 | config_name="MyParticipantLibrary2::MyParticipant2", 90 | url=xml_path1 + ';' + xml_path2) as connector: 91 | 92 | assert connector is not None 93 | output = connector.get_output("MyPublisher2::MySquareWriter2") 94 | assert output is not None 95 | 96 | def test_connector_creation_with_participant_qos(self): 97 | """ 98 | Tests that a domain_participant defined in XML alonside participant_qos 99 | can be used to create a Connector object. 100 | """ 101 | participant_profile = "MyParticipantLibrary::ConnectorWithParticipantQos" 102 | xml_path = os.path.join(os.path.dirname( 103 | os.path.realpath(__file__)), 104 | "../xml/TestConnector.xml") 105 | with rti.open_connector( 106 | config_name=participant_profile, 107 | url=xml_path) as connector: 108 | assert connector is not None 109 | 110 | def test_get_version(self): 111 | """ 112 | version is a static method that can be can be called 113 | either before or after the creation of a Connector instance. It returns 114 | a string providing information about the versions of the native libraries 115 | in use, and the version of the API. 116 | """ 117 | # Ensure that we can call version before creating a Connector instance 118 | version_string = rti.Connector.get_version() 119 | assert version_string is not None 120 | # The returned version should contain 4 pieces of information: 121 | # - the API version of Connector 122 | # - the build ID of core.1.0 123 | # - the build ID of dds_c.1.0 124 | # - the build ID of lua_binding.1.0 125 | # Each build ID can be of the form X.X.X[.X], where the last integer 126 | # is not always present 127 | version_regex = "([0-9]\\.){2,3}[0-9]{0,2}_[0-9]{8}T[0-9]{6}Z" 128 | assert bool(re.match("RTI Connector for Python, version (([0-9]\\.){2}[0-9]|unknown)", version_string, re.DOTALL)) == True 129 | assert bool(re.match(".*NDDSCORE_BUILD_" + version_regex, version_string, re.DOTALL)) == True 130 | assert bool(re.match(".*NDDSC_BUILD_" + version_regex, version_string, re.DOTALL)) == True 131 | assert bool(re.match(".*RTICONNECTOR_BUILD_" + version_regex, version_string, re.DOTALL)) == True 132 | 133 | def test_setting_max_objects_per_thread(self): 134 | """ 135 | It should be possible to modify max_objects_per_thread 136 | """ 137 | rti.Connector.set_max_objects_per_thread(2048) 138 | 139 | def test_connector_double_deletion(self): 140 | """Verify CON-200, that Connector does not segfault on double deletion""" 141 | participant_profile = "MyParticipantLibrary::ConnectorWithParticipantQos" 142 | xml_path = os.path.join(os.path.dirname( 143 | os.path.realpath(__file__)), 144 | "../xml/TestConnector.xml") 145 | with rti.open_connector( 146 | config_name=participant_profile, 147 | url=xml_path) as connector: 148 | assert connector is not None 149 | connector.close() 150 | # connector.close() will be called again here due to the with clause 151 | -------------------------------------------------------------------------------- /test/python/test_rticonnextdds_data_iterators.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # (c) 2005-2015 Copyright, Real-Time Innovations. All rights reserved. # 3 | # No duplications, whole or partial, manual or electronic, may be made # 4 | # without express written permission. Any such copies, or revisions thereof, # 5 | # must display this notice unaltered. # 6 | # This code contains trade secrets of Real-Time Innovations, Inc. # 7 | ############################################################################### 8 | 9 | import pytest,time,sys,os 10 | sys.path.append(os.path.dirname(os.path.realpath(__file__)) + "/../../") 11 | import rticonnextdds_connector as rti 12 | 13 | class TestDataIterators: 14 | 15 | """ 16 | This class tests the iteration of Input Samples 17 | """ 18 | @pytest.fixture(scope="class") 19 | def populatedInput(self, rtiOutputFixture, rtiInputFixture): 20 | """Class fixture that writes the test messages do they're available in the input 21 | 22 | """ 23 | 24 | rtiInputFixture.expected_count = 4 25 | 26 | # Add extra element so that if the iterators fail and advance one element 27 | # too many, we can detect it in the zip's below 28 | rtiInputFixture.expected_values = [1.0, 2.0, 3.0, None] 29 | 30 | rtiOutputFixture.instance.set_dictionary( 31 | {"x":1, "y":1, "z":True, "color":"BLUE", "shapesize":5}) 32 | rtiOutputFixture.write() 33 | 34 | rtiOutputFixture.instance.set_dictionary( 35 | {"x":2, "y":2, "z":False, "color":"RED", "shapesize":10}) 36 | rtiOutputFixture.write() 37 | 38 | rtiOutputFixture.instance.set_dictionary( 39 | {"x":3, "y":3, "z":True, "color":"YELLOW", "shapesize":15}) 40 | rtiOutputFixture.write() 41 | 42 | rtiOutputFixture.write(action="dispose") 43 | 44 | for i in range(1, 20): 45 | rtiInputFixture.read() 46 | if rtiInputFixture.samples.length == rtiInputFixture.expected_count: 47 | break 48 | time.sleep(.5) 49 | 50 | return rtiInputFixture 51 | 52 | def test_data_iterator(self, populatedInput): 53 | """Tests SampleIterator, Samples""" 54 | 55 | assert populatedInput.samples.length == populatedInput.expected_count 56 | 57 | count = 0 58 | for sample in populatedInput.samples: 59 | if count <= 2: 60 | assert sample.valid_data 61 | assert sample.get_number("x") == populatedInput.expected_values[count] 62 | assert sample.get_dictionary()["y"] == populatedInput.expected_values[count] 63 | else: 64 | assert not sample.valid_data 65 | count = count + 1 66 | assert count == populatedInput.expected_count 67 | 68 | assert populatedInput.samples[0].get_number("x") == populatedInput.expected_values[0] 69 | 70 | count = 0 71 | for i in range(populatedInput.samples.length): 72 | sample = populatedInput.samples[i] 73 | if count <= 2: 74 | assert sample.valid_data 75 | assert sample.get_number("x") == populatedInput.expected_values[i] 76 | assert sample.get_dictionary()["y"] == populatedInput.expected_values[i] 77 | else: 78 | assert not sample.valid_data 79 | count = count + 1 80 | assert count == populatedInput.expected_count 81 | 82 | def test_valid_data_iterator(self, populatedInput): 83 | """Tests Samples.valid_data_iterator""" 84 | 85 | assert populatedInput.samples.length == populatedInput.expected_count 86 | 87 | count = 0 88 | for sample in populatedInput.samples.valid_data_iter: 89 | assert sample.valid_data 90 | assert sample.get_number("x") == populatedInput.expected_values[count] 91 | assert sample.get_dictionary()["y"] == populatedInput.expected_values[count] 92 | count = count + 1 93 | 94 | assert count == populatedInput.expected_count - 1 95 | 96 | def test_no_data(self, populatedInput): 97 | populatedInput.take() # First take removes samples (they were read() before) 98 | populatedInput.take() # Second take returns no data, leaves samples empty 99 | assert populatedInput.samples.length == 0 100 | had_data = False 101 | for _ in populatedInput.samples: 102 | had_data = True 103 | assert not had_data 104 | -------------------------------------------------------------------------------- /test/python/test_rticonnextdds_dataflow.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # (c) 2005-2015 Copyright, Real-Time Innovations. All rights reserved. # 3 | # No duplications, whole or partial, manual or electronic, may be made # 4 | # without express written permission. Any such copies, or revisions thereof, # 5 | # must display this notice unaltered. # 6 | # This code contains trade secrets of Real-Time Innovations, Inc. # 7 | ############################################################################### 8 | 9 | import pytest,time,sys,os 10 | sys.path.append(os.path.dirname(os.path.realpath(__file__)) + "/../../") 11 | import rticonnextdds_connector as rti 12 | 13 | class TestDataflow: 14 | """ 15 | This class tests the flow of data between 16 | an :class:`rticonnextdds_connector.Input` and an 17 | :class:`rticonnextdds_connector.Output` object. 18 | 19 | .. todo:: 20 | 21 | * No Exception is thrown when a non-existent field is 22 | accessed. ``AttributeError`` must be propagated 23 | to the user when a non-existent field is accessed with 24 | :func:`rticonnextdds_connector.Samples.getNumber`, 25 | :func:`rticonnextdds_connector.Samples.getString`, 26 | and :func:`rticonnextdds_connector.Samples.getBoolean`. 27 | 28 | * Address Segmentation fault on 0-index and out-of-bound access on 29 | :class:`rticonnextdds_connector.Infos` and :class:`rticonnextdds_connector.Samples`. 30 | 31 | * Behavior on inconsistent type access needs to be addressed: 32 | 33 | * Calling :func:`rticonnextdds_connector.Samples.getString` on Numeric field gives 34 | a string representation of the number and on a Boolean field gives None 35 | 36 | * Calling :func:`rticonnextdds_connector.Samples.getBoolean` on String or Numeric field 37 | gives an int with value of 0/1 and on a Boolean filed returns an int value of 0/1 38 | 39 | * Calling :func:`rticonnextdds_connector.Samples.getNumber` on a Boolean field 40 | gives a float value of 0.0 and on String field gives a float value of 0.0 41 | """ 42 | @pytest.fixture(scope="class") 43 | def testMsg(self): 44 | """ 45 | A class-scoped `pytest.fixture `_ 46 | which instantiates a test message to test the flow of data between an Input and Output object. 47 | 48 | :returns: A class scoped test message 49 | :rtype: `Dictionary `_ 50 | 51 | """ 52 | return {"x":1,"y":1,"z":True,"color":"BLUE","shapesize":5} 53 | 54 | @pytest.fixture(autouse=True,params=[{"wait":True,"method":"read"}, 55 | {"wait":True,"method":"take"},{"wait":False,"method":"read"}, 56 | {"wait":False,"method":"take"}]) 57 | def sendTestMsg(self,request,rtiInputFixture,rtiOutputFixture,testMsg): 58 | """ 59 | An `autouse `_ 60 | `pytest.fixture `_ 61 | which is executed before any test function in :class:`TestDataflow` class is executed. 62 | This fixture method uses session-scoped Input and Output objects to set-up the dataflow test. 63 | First any pre-existing messages in the middleware cache are taken and then the Output object 64 | sends one test message to the Input object. This fixture is 65 | `parameterized `_, 66 | so that each test is executed four times- first, where the Input object waits for 10 seconds 67 | for data and uses :func:`rticonnextdds_connector.Input.read` method to obtain the sent test message; 68 | second, where the Input object waits for 10 seconds for data to arrive and 69 | uses :func:`rticonnextdds_connector.Input.take` method to obtain the sent message; third, where Input 70 | object does not wait but uses :func:`rticonnextdds_connector.Input.read` method to obtain the sent message; 71 | and finally, where Input object does not wait and uses 72 | :func:`rticonnextdds_connector.Input.take` method to obtain the sent message. 73 | 74 | :param rtiInputFixture: :func:`conftest.rtiInputFixture` 75 | :type rtiInputFixture: `pytest.fixture `_ 76 | :param rtiOutputFixture: :func:`conftest.rtiOutputFixture` 77 | :type rtiOutputFixture: `pytest.fixture `_ 78 | :param testMsg: :func:`testMsg` 79 | :type testMsg: `pytest.fixture `_ 80 | 81 | """ 82 | # take any pre-existing samples from cache 83 | rtiInputFixture.take() 84 | rtiOutputFixture.instance.set_dictionary(testMsg) 85 | rtiOutputFixture.write() 86 | wait=request.param.get('wait') 87 | method=request.param.get('method') 88 | 89 | if wait: 90 | rtiInputFixture.wait(10) 91 | retrieve_func= getattr(rtiInputFixture,method) 92 | retrieve_func() 93 | else: 94 | # loop to allow sometime for discovery of Input and Output objects 95 | for i in range(1,20): 96 | time.sleep(.5) 97 | retrieve_func= getattr(rtiInputFixture,method) 98 | retrieve_func() 99 | if rtiInputFixture.samples.length > 0: 100 | break 101 | 102 | def test_samples_getLength(self,rtiInputFixture): 103 | """ 104 | This function tests the correct operation of 105 | :func:`rticonnextdds_connector.Samples.getLength` 106 | 107 | :param rtiInputFixture: :func:`conftest.rtiInputFixture` 108 | :type rtiInputFixture: `pytest.fixture `_ 109 | """ 110 | assert rtiInputFixture.samples.getLength() == 1 111 | 112 | def test_infos_getLength(self,rtiInputFixture): 113 | """ 114 | This function tests the correct operation of 115 | :func:`rticonnextdds_connector.Infos.getLength` 116 | 117 | :param rtiInputFixture: :func:`conftest.rtiInputFixture` 118 | :type rtiInputFixture: `pytest.fixture `_ 119 | """ 120 | assert rtiInputFixture.infos.getLength() == 1 121 | 122 | def test_infos_isValid(self,rtiInputFixture): 123 | """ 124 | This function tests the correct operation of 125 | :func:`rticonnextdds_connector.Infos.isValid` 126 | 127 | :param rtiInputFixture: :func:`conftest.rtiInputFixture` 128 | :type rtiInputFixture: `pytest.fixture `_ 129 | """ 130 | assert rtiInputFixture.infos.isValid(0)== True 131 | 132 | def test_getDictionary(self,rtiInputFixture,testMsg): 133 | """ 134 | This function tests the correct operation of 135 | :func:`rticonnextdds_connector.Samples.getDictionary`. 136 | Received message should be the same as the :func:`testMsg` 137 | 138 | :param rtiInputFixture: :func:`conftest.rtiInputFixture` 139 | :type rtiInputFixture: `pytest.fixture `_ 140 | :param testMsg: :func:`testMsg` 141 | :type testMsg: `pytest.fixture `_ 142 | """ 143 | received_msg = rtiInputFixture.samples.getDictionary(0) 144 | assert received_msg==testMsg 145 | 146 | def test_getTypes(self,rtiInputFixture,testMsg): 147 | """ 148 | This function tests the correct operation of 149 | :func:`rticonnextdds_connector.Samples.getString`, 150 | :func:`rticonnextdds_connector.Samples.getNumber` and 151 | :func:`rticonnextdds_connector.Samples.getBoolean`. 152 | Received values should be the same as that of :func:`testMsg` 153 | 154 | :param rtiInputFixture: :func:`conftest.rtiInputFixture` 155 | :type rtiInputFixture: `pytest.fixture `_ 156 | :param testMsg: :func:`testMsg` 157 | :type testMsg: `pytest.fixture `_ 158 | """ 159 | x = rtiInputFixture.samples.getNumber(0,"x") 160 | y = rtiInputFixture.samples.getNumber(0,"y") 161 | z = rtiInputFixture.samples.getBoolean(0,"z") 162 | color = rtiInputFixture.samples.getString(0,"color") 163 | shapesize = rtiInputFixture.samples.getNumber(0,"shapesize") 164 | assert x == testMsg['x'] and y == testMsg['y'] \ 165 | and z == testMsg['z'] and color == testMsg['color'] \ 166 | and shapesize == testMsg['shapesize'] 167 | 168 | x = rtiInputFixture.samples[0].get_number("x") 169 | y = rtiInputFixture.samples[0].get_number("y") 170 | z = rtiInputFixture.samples[0].get_boolean("z") 171 | color = rtiInputFixture.samples[0].get_string("color") 172 | shapesize = rtiInputFixture.samples[0].get_number("shapesize") 173 | assert x == testMsg['x'] and y == testMsg['y'] \ 174 | and z == testMsg['z'] and color == testMsg['color'] \ 175 | and shapesize == testMsg['shapesize'] 176 | -------------------------------------------------------------------------------- /test/python/test_rticonnextdds_discovery.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # (c) 2019 Copyright, Real-Time Innovations. All rights reserved. # 3 | # No duplications, whole or partial, manual or electronic, may be made # 4 | # without express written permission. Any such copies, or revisions thereof, # 5 | # must display this notice unaltered. # 6 | # This code contains trade secrets of Real-Time Innovations, Inc. # 7 | ############################################################################### 8 | 9 | import pytest,time,sys,os 10 | sys.path.append(os.path.dirname(os.path.realpath(__file__)) + "/../../") 11 | import rticonnextdds_connector as rti 12 | from test_utils import * 13 | 14 | class TestDiscovery: 15 | """ 16 | This class tests discovery between Connector entities. 17 | All the fixtures use scope-level of "function" to prevent unwanted interaction 18 | between tests. 19 | The entities used in the tests are defined in a different domain to those 20 | in other tests for the same reason. 21 | """ 22 | 23 | @pytest.fixture(scope="function") 24 | def discovery_connector(self): 25 | xml_path = os.path.join( 26 | os.path.dirname(os.path.realpath(__file__)), 27 | "../xml/TestConnector.xml") 28 | participant_profile="MyParticipantLibrary::DiscoveryTest" 29 | 30 | with rti.open_connector(participant_profile, xml_path) as rti_connector: 31 | yield rti_connector 32 | 33 | @pytest.fixture(scope="function") 34 | def discovery_connector_no_entity_names(self): 35 | xml_path = os.path.join( 36 | os.path.dirname(os.path.realpath(__file__)), 37 | "../xml/TestConnector.xml") 38 | participant_profile="MyParticipantLibrary::DiscoveryTestNoEntityName" 39 | 40 | with rti.open_connector(participant_profile, xml_path) as rti_connector: 41 | yield rti_connector 42 | 43 | @pytest.fixture(scope="function") 44 | def discovery_reader_only_connector(self): 45 | xml_path = os.path.join( 46 | os.path.dirname(os.path.realpath(__file__)), 47 | "../xml/TestConnector.xml") 48 | participant_profile="MyParticipantLibrary::DiscoveryTestReaderOnly" 49 | 50 | with rti.open_connector(participant_profile, xml_path) as rti_connector: 51 | yield rti_connector 52 | 53 | @pytest.fixture(scope="function") 54 | def discovery_writer_only_connector(self): 55 | xml_path = os.path.join( 56 | os.path.dirname(os.path.realpath(__file__)), 57 | "../xml/TestConnector.xml") 58 | participant_profile="MyParticipantLibrary::DiscoveryTestWriterOnly" 59 | 60 | with rti.open_connector(participant_profile, xml_path) as rti_connector: 61 | yield rti_connector 62 | 63 | @pytest.fixture(scope="function") 64 | def discovery_reader_only_input(self, discovery_reader_only_connector): 65 | return discovery_reader_only_connector.get_input("TestSubscriber::TestReader") 66 | 67 | @pytest.fixture(scope="function") 68 | def discovery_writer_only_output(self, discovery_writer_only_connector): 69 | return discovery_writer_only_connector.get_output("TestPublisher::TestWriter") 70 | 71 | def test_no_matches_input(self, discovery_reader_only_input): 72 | # We should not match with any outputs at this point 73 | matches = discovery_reader_only_input.matched_publications 74 | assert len(matches) == 0 75 | 76 | # We should timeout if we attempt to wait for a match 77 | with pytest.raises(rti.TimeoutError) as excinfo: 78 | change_in_matches = discovery_reader_only_input.wait_for_publications(1) 79 | assert change_in_matches == 0 80 | 81 | def test_no_matches_output(self, discovery_writer_only_output): 82 | # We should not match with any outputs at this point 83 | matches = discovery_writer_only_output.matched_subscriptions 84 | assert len(matches) == 0 85 | 86 | # We should timeout if we attempt to wait for a match 87 | with pytest.raises(rti.TimeoutError) as excinfo: 88 | change_in_matches = discovery_writer_only_output.wait_for_subscriptions(1) 89 | 90 | def test_simple_matching(self, discovery_connector): 91 | the_input = discovery_connector.get_input("MySubscriber::MyReader") 92 | the_output = discovery_connector.get_output("MyPublisher::MyWriter") 93 | 94 | # Both the input and output should match each other (and nothing else) 95 | change_in_matches = the_input.wait_for_publications(2000) 96 | matches = the_input.matched_publications 97 | assert change_in_matches == 1 98 | assert isinstance(matches, list) 99 | assert {'name': 'MyWriter'} in matches 100 | change_in_matches = the_output.wait_for_subscriptions(2000) 101 | matches = the_output.matched_subscriptions 102 | assert change_in_matches == 1 103 | assert isinstance(matches, list) 104 | assert {'name': 'MyReader'} in matches 105 | 106 | def test_multiple_inputs(self, discovery_connector, discovery_reader_only_input): 107 | the_output = discovery_connector.get_output("MyPublisher::MyWriter") 108 | 109 | total_matches = 0 110 | # the_output should match two inputs, the one from discovery_reader_only_input 111 | # and the one defined within discovery_connector 112 | while total_matches < 2: 113 | total_matches += the_output.wait_for_subscriptions(3000) 114 | assert total_matches == 2 115 | 116 | # Calling again to wait_for_subscriptions should timeout 117 | with pytest.raises(rti.TimeoutError) as excinfo: 118 | the_output.wait_for_subscriptions(1000) 119 | 120 | matches = the_output.matched_subscriptions 121 | assert {'name': 'MyReader'} in matches 122 | assert {'name': 'TestReader'} in matches 123 | 124 | def test_multiple_outputs(self, discovery_connector, discovery_writer_only_output): 125 | the_input = discovery_connector.get_input("MySubscriber::MyReader") 126 | 127 | total_matches = 0 128 | # the_input should match two outpouts, the one from discovery_writer_only_output 129 | # and the one defined within discovery_connector 130 | while total_matches < 2: 131 | total_matches += the_input.wait_for_publications(3000) 132 | assert total_matches == 2 133 | 134 | # Calling again to wait_for_publications should timeout 135 | with pytest.raises(rti.TimeoutError) as excinfo: 136 | the_input.wait_for_publications(1000) 137 | 138 | matches = the_input.matched_publications 139 | assert {'name': 'MyWriter'} in matches 140 | assert {'name': 'TestWriter'} in matches 141 | 142 | # Even though we are going to use the discovery_reader_only_input, don't use the 143 | # pytest fixture as we want to control the deletion of it from within the 144 | # test function 145 | def test_unmatched_input(self, discovery_writer_only_output): 146 | # To begin with, no matching occurs 147 | assert len(discovery_writer_only_output.matched_subscriptions) == 0 148 | 149 | # Create the Connector object containing the matching input 150 | reader_connector = rti.Connector( 151 | "MyParticipantLibrary::DiscoveryTestReaderOnly", 152 | os.path.join(os.path.dirname(os.path.realpath(__file__)),"../xml/TestConnector.xml")) 153 | the_input = reader_connector.get_input("TestSubscriber::TestReader") 154 | assert the_input is not None 155 | 156 | # Check that matching occurs 157 | change_in_matches = discovery_writer_only_output.wait_for_subscriptions(5000) 158 | assert change_in_matches == 1 159 | matches = discovery_writer_only_output.matched_subscriptions 160 | assert {'name': 'TestReader'} in matches 161 | change_in_matches = the_input.wait_for_publications(5000) 162 | assert change_in_matches == 1 163 | matches = the_input.matched_publications 164 | assert {'name': 'TestWriter'} in matches 165 | 166 | # If we now delete the reader_connector object which we created above, they 167 | # will unmatch 168 | reader_connector.close() 169 | change_in_matches = discovery_writer_only_output.wait_for_subscriptions(5000) 170 | assert change_in_matches == -1 171 | matches = discovery_writer_only_output.matched_subscriptions 172 | assert len(matches) == 0 173 | 174 | # Even though we are going to use the discovery_writer_only_output, don't use the 175 | # pytest fixture as we want to control the deletion of it from within the 176 | # test function 177 | def test_unmatched_output(self, discovery_reader_only_input): 178 | # To begin with, no matching occurs 179 | assert len(discovery_reader_only_input.matched_publications) == 0 180 | 181 | # Create the Connector object containing the matching input 182 | writer_connector = rti.Connector( 183 | "MyParticipantLibrary::DiscoveryTestWriterOnly", 184 | os.path.join(os.path.dirname(os.path.realpath(__file__)),"../xml/TestConnector.xml")) 185 | the_output = writer_connector.get_output("TestPublisher::TestWriter") 186 | assert the_output is not None 187 | 188 | # Check that matching occurs 189 | change_in_matches = discovery_reader_only_input.wait_for_publications() 190 | assert change_in_matches == 1 191 | matches = discovery_reader_only_input.matched_publications 192 | assert {'name': 'TestWriter'} in matches 193 | change_in_matches = the_output.wait_for_subscriptions(5000) 194 | assert change_in_matches == 1 195 | matches = the_output.matched_subscriptions 196 | assert {'name': 'TestReader'} in matches 197 | 198 | # If we now delete the reader_connector object which we created above, they 199 | # will unmatch 200 | writer_connector.close() 201 | change_in_matches = discovery_reader_only_input.wait_for_publications(5000) 202 | assert change_in_matches == -1 203 | matches = discovery_reader_only_input.matched_publications 204 | assert len(matches) == 0 205 | 206 | def test_empty_entity_names(self, discovery_connector_no_entity_names): 207 | the_output = discovery_connector_no_entity_names.get_output("MyPublisher::MyWriter") 208 | # Ensure that the entities match 209 | change_in_subs = the_output.wait_for_subscriptions(5000) 210 | assert change_in_subs == 1 211 | 212 | # Get the entity names from the matched subscriptions 213 | matched_subs = the_output.matched_subscriptions 214 | # The entity names set in the input are empty 215 | assert matched_subs == [{'name': ''}] 216 | 217 | def test_no_entity_names(self, discovery_writer_only_output): 218 | # We call the _create_test_scenario API to create a remote matching reader which 219 | # will not set any entity name. 220 | retcode = rti.connector_binding._create_test_scenario( 221 | discovery_writer_only_output.connector.native, 222 | 0, # RTI_Connector_testScenario_createReader 223 | discovery_writer_only_output.native) 224 | assert retcode == 0 225 | 226 | # Wait to match with the new reader 227 | discovery_writer_only_output.wait_for_subscriptions(5000) 228 | # Check that we can handle getting entity_name when it is NULL 229 | matches = discovery_writer_only_output.matched_subscriptions 230 | assert isinstance(matches, list) 231 | assert len(matches) == 1 232 | assert isinstance(matches[0], dict) 233 | assert matches[0]['name'] is None 234 | 235 | # It is not necessary to delete the entities created by the call to createTestScenario 236 | # since they were all created from the same DomainParticipant as discovery_writer_only_output 237 | # which will have delete_contained_entities called on it. 238 | -------------------------------------------------------------------------------- /test/python/test_rticonnextdds_input.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # (c) 2005-2019 Copyright, Real-Time Innovations. All rights reserved. # 3 | # No duplications, whole or partial, manual or electronic, may be made # 4 | # without express written permission. Any such copies, or revisions thereof, # 5 | # must display this notice unaltered. # 6 | # This code contains trade secrets of Real-Time Innovations, Inc. # 7 | ############################################################################### 8 | 9 | import pytest,sys,os,ctypes 10 | sys.path.append(os.path.dirname(os.path.realpath(__file__))+ "/../../") 11 | import rticonnextdds_connector as rti 12 | from test_utils import open_test_connector, wait_for_data, send_data 13 | 14 | class TestInput: 15 | """ 16 | This class tests the correct instantiation of 17 | :class:`rticonnextdds_connector.Input` object. 18 | 19 | .. todo:: 20 | 21 | * Move :func:`rticonnextdds_connector.Input.wait` to 22 | :class:`rticonnextdds_connector.Connector` 23 | 24 | """ 25 | 26 | def test_invalid_DR(self,rtiConnectorFixture): 27 | """ 28 | This test function ensures that a ValueError is raised if 29 | an incorrect DataReader name is passed to the 30 | Input constructor. 31 | 32 | :param rtiConnectorFixture: :func:`conftest.rtiConnectorFixture` 33 | :type rtiConnectorFixture: `pytest.fixture `_ 34 | 35 | """ 36 | invalid_DR = "InvalidDR" 37 | with pytest.raises(rti.Error): 38 | rtiConnectorFixture.getInput(invalid_DR) 39 | 40 | def test_creation_DR(self,rtiInputFixture): 41 | """ 42 | This function tests the correct instantiation of 43 | Input object. 44 | 45 | :param rtiInputFixture: :func:`conftest.rtiInputFixture` 46 | :type rtiInputFixture: `pytest.fixture `_ 47 | """ 48 | assert rtiInputFixture!=None and isinstance(rtiInputFixture,rti.Input) \ 49 | and rtiInputFixture.name == "MySubscriber::MySquareReader" \ 50 | and isinstance(rtiInputFixture.connector,rti.Connector) \ 51 | and isinstance(rtiInputFixture.samples,rti.Samples) \ 52 | and isinstance(rtiInputFixture.infos,rti.Infos) 53 | 54 | @pytest.mark.xfail(sys.platform.startswith("win"), reason="symbols not exported") 55 | def test_reader_native_call(self, rtiInputFixture): 56 | get_topic = rti.connector_binding.library.DDS_DataReader_get_topicdescription 57 | get_topic.restype = ctypes.c_void_p 58 | get_topic.argtypes = [ctypes.c_void_p] 59 | topic = get_topic(rtiInputFixture.native) 60 | 61 | get_name = rti.connector_binding.library.DDS_TopicDescription_get_name 62 | get_name.restype = ctypes.c_char_p 63 | get_name.argtypes = [ctypes.c_void_p] 64 | assert rti.fromcstring(get_name(topic)) == "Square" 65 | 66 | def test_no_autoenable(self): 67 | with open_test_connector("MyParticipantLibrary::TestNoAutoenableSubscriber") as connector: 68 | output = connector.get_output("TestPublisher::TestWriter") 69 | # The input is not enabled yet because the subscriber sets 70 | # autoenable_created_entities to false 71 | with pytest.raises(rti.Error) as excinfo: 72 | output.wait_for_subscriptions(200) 73 | 74 | # Connector enables the DataReader when it's first looked up 75 | input = connector.get_input("TestSubscriber::TestReader") 76 | output.wait_for_subscriptions(5000) 77 | matched_subs = output.matched_subscriptions 78 | assert {"name":"TestReader"} in matched_subs 79 | 80 | def test_unbounded(self): 81 | with open_test_connector("MyParticipantLibrary::TestUnbounded") as connector: 82 | output = connector.get_output("TestPublisher::TestWriter") 83 | input = connector.get_input("TestSubscriber::TestReader") 84 | output.instance.set_dictionary({ 85 | "my_sequence": [44] * 200, 86 | "my_string": "A" * 200 87 | }) 88 | 89 | sample = send_data(output, input) 90 | 91 | assert sample["my_sequence"] == [44] * 200 92 | assert sample["my_string"] == "A" * 200 93 | -------------------------------------------------------------------------------- /test/python/test_rticonnextdds_output.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # (c) 2005-2019 Copyright, Real-Time Innovations. All rights reserved. # 3 | # No duplications, whole or partial, manual or electronic, may be made # 4 | # without express written permission. Any such copies, or revisions thereof, # 5 | # must display this notice unaltered. # 6 | # This code contains trade secrets of Real-Time Innovations, Inc. # 7 | ############################################################################### 8 | 9 | import pytest,sys,os 10 | sys.path.append(os.path.dirname(os.path.realpath(__file__))+ "/../../") 11 | import rticonnextdds_connector as rti 12 | 13 | class TestOutput: 14 | """ 15 | This class tests the correct intantiation of 16 | :class:`rticonnextdds_connector.Output` object. 17 | """ 18 | 19 | def test_invalid_DW(self,rtiConnectorFixture): 20 | """ 21 | This test function ensures that a ``ValueError`` is raised if 22 | an incorrect DataWriter name is passed to the 23 | Output constructor. 24 | 25 | :param rtiConnectorFixture: :func:`conftest.rtiConnectorFixture` 26 | :type rtiConnectorFixture: `pytest.fixture `_ 27 | 28 | """ 29 | invalid_DW = "InvalidDW" 30 | with pytest.raises(rti.Error) as execinfo: 31 | op= rtiConnectorFixture.get_output(invalid_DW) 32 | print("\nException of type:"+str(execinfo.type)+ \ 33 | "\nvalue:"+str(execinfo.value)) 34 | 35 | def test_creation_DW(self,rtiOutputFixture): 36 | """ 37 | This function tests the correct instantiation of 38 | Output object. 39 | 40 | :param rtiOutputFixture: :func:`conftest.rtiOutputFixture` 41 | :type rtiOutputFixture: `pytest.fixture `_ 42 | """ 43 | assert isinstance(rtiOutputFixture,rti.Output) \ 44 | and rtiOutputFixture.name == "MyPublisher::MySquareWriter" \ 45 | and isinstance(rtiOutputFixture.connector,rti.Connector) \ 46 | and isinstance(rtiOutputFixture.instance, rti.Instance) 47 | 48 | class TestInstance: 49 | """ 50 | This class tests the correct invocation of functions on 51 | :class:`rticonnextdds_connector.Instance` object. 52 | 53 | .. todo:: 54 | 55 | * No Exception is thrown when a non-existent field is 56 | accessed. ``AttributeError`` must be propagated 57 | to the user when a non-existent field is accessed with 58 | :func:`rticonnextdds_connector.Instance.setNumber`, 59 | :func:`rticonnextdds_connector.Instance.setString`, 60 | and :func:`rticonnextdds_connector.Instance.setBoolean`. 61 | ``KeyError`` must be propagated for 62 | :func:`rticonnextdds_connector.Instance.setDictionary` 63 | when setting the Instance object using a dictionary 64 | with non-existent fields 65 | * An Instance object can be set with a dictionary containing 66 | inconsistent value types for existing field-names with 67 | :func:`rticonnextdds_connector.Instance.setDictionary`. 68 | A ``TypeError`` should be propagated to users when appropriate 69 | and implicit type conversion doesn't make sense. 70 | 71 | """ 72 | 73 | def test_instance_creation(self,rtiOutputFixture): 74 | """ 75 | This function tests that an Instance object is correctly 76 | initialized. 77 | 78 | :param rtiOutputFixture: :func:`conftest.rtiOutputFixture` 79 | :type rtiOutputFixture: `pytest.fixture `_ 80 | 81 | """ 82 | assert rtiOutputFixture.instance!=None and \ 83 | isinstance(rtiOutputFixture.instance, rti.Instance) 84 | 85 | def test_setNumber_on_nonexistent_field(self,rtiOutputFixture): 86 | """ 87 | This function tests that an ``AttributeError`` is raised when 88 | :func:`rticonnextdds_connector.Instance.setNumber` is called 89 | on a non-existent field name. 90 | 91 | :param rtiOutputFixture: :func:`conftest.rtiOutputFixture` 92 | :type rtiOutputFixture: `pytest.fixture `_ 93 | 94 | .. note:: This test is marked to fail as this case is not handled yet. 95 | 96 | """ 97 | non_existent_field="invalid_field" 98 | with pytest.raises(rti.Error) as execinfo: 99 | rtiOutputFixture.instance.setNumber(non_existent_field,1) 100 | 101 | def test_setString_on_nonexistent_field(self,rtiOutputFixture): 102 | """ 103 | This function tests that an ``AttributeError`` is raised when 104 | :func:`rticonnextdds_connector.Instance.setString` is called 105 | on a non-existent field name. 106 | 107 | :param rtiOutputFixture: :func:`conftest.rtiOutputFixture` 108 | :type rtiOutputFixture: `pytest.fixture `_ 109 | 110 | .. note:: This test is marked to fail as this case is not handled yet. 111 | 112 | """ 113 | non_existent_field="invalid_field" 114 | with pytest.raises(rti.Error) as execinfo: 115 | rtiOutputFixture.instance.set_string(non_existent_field,"1") 116 | 117 | def test_setBoolean_on_nonexistent_field(self,rtiOutputFixture): 118 | """ 119 | This function tests that an ``AttributeError`` is raised when 120 | :func:`rticonnextdds_connector.Instance.setBoolean` is called 121 | on a non-existent field name. 122 | 123 | :param rtiOutputFixture: :func:`conftest.rtiOutputFixture` 124 | :type rtiOutputFixture: `pytest.fixture `_ 125 | 126 | .. note:: This test is marked to fail as this case is not handled yet. 127 | 128 | """ 129 | non_existent_field="invalid_field" 130 | with pytest.raises(rti.Error) as execinfo: 131 | rtiOutputFixture.instance.set_boolean(non_existent_field,True) 132 | 133 | def test_setDictionary_with_nonexistent_fields(self,rtiOutputFixture): 134 | """ 135 | This function tests that a ``KeyError`` is raised when 136 | :func:`rticonnextdds_connector.Instance.setDictionary` is called 137 | with a dictionary containing non-existent field names. 138 | 139 | :param rtiOutputFixture: :func:`conftest.rtiOutputFixture` 140 | :type rtiOutputFixture: `pytest.fixture `_ 141 | 142 | .. note:: This test is marked to fail as this case is not handled yet. 143 | 144 | """ 145 | invalid_dictionary= {"non_existent_field":"value"} 146 | with pytest.raises(rti.Error, match=r".*non_existent_field*") as execinfo: 147 | rtiOutputFixture.instance.set_dictionary(invalid_dictionary) 148 | 149 | def test_setNumber_with_String(self,rtiOutputFixture): 150 | """ 151 | This function tests that a ``TypeError`` is raised when 152 | :func:`rticonnextdds_connector.Instance.setNumber` is called 153 | with a String value 154 | 155 | :param rtiOutputFixture: :func:`conftest.rtiOutputFixture` 156 | :type rtiOutputFixture: `pytest.fixture `_ 157 | 158 | """ 159 | number_field="x" 160 | with pytest.raises(TypeError) as execinfo: 161 | rtiOutputFixture.instance.set_number(number_field,"str") 162 | 163 | def test_setNumber_with_Dictionary(self,rtiOutputFixture): 164 | """ 165 | This function tests that a ``TypeError`` is raised when 166 | :func:`rticonnextdds_connector.Instance.setNumber` is called 167 | with a Dictionary value 168 | 169 | :param rtiOutputFixture: :func:`conftest.rtiOutputFixture` 170 | :type rtiOutputFixture: `pytest.fixture `_ 171 | 172 | """ 173 | number_field="x" 174 | with pytest.raises(TypeError) as execinfo: 175 | rtiOutputFixture.instance.set_number(number_field,{"x":1}) 176 | 177 | def test_setString_with_Boolean(self,rtiOutputFixture): 178 | """ 179 | This function tests that a ``TypeError`` is raised when 180 | :func:`rticonnextdds_connector.Instance.setString` is called 181 | with a Boolean value 182 | 183 | :param rtiOutputFixture: :func:`conftest.rtiOutputFixture` 184 | :type rtiOutputFixture: `pytest.fixture `_ 185 | 186 | """ 187 | string_field="color" 188 | with pytest.raises(TypeError) as execinfo: 189 | rtiOutputFixture.instance.set_string(string_field,True) 190 | 191 | def test_setString_with_Number(self,rtiOutputFixture): 192 | """ 193 | This function tests that a ``TypeError`` is raised when 194 | :func:`rticonnextdds_connector.Instance.setString` is called 195 | with a Number value 196 | 197 | :param rtiOutputFixture: :func:`conftest.rtiOutputFixture` 198 | :type rtiOutputFixture: `pytest.fixture `_ 199 | 200 | """ 201 | string_field="color" 202 | with pytest.raises(TypeError) as execinfo: 203 | rtiOutputFixture.instance.setString(string_field,55.55) 204 | 205 | def test_setString_with_Dictionary(self,rtiOutputFixture): 206 | """ 207 | This function tests that a ``TypeError`` is raised when 208 | :func:`rticonnextdds_connector.Instance.setString` is called 209 | with a Dictionary value 210 | 211 | :param rtiOutputFixture: :func:`conftest.rtiOutputFixture` 212 | :type rtiOutputFixture: `pytest.fixture `_ 213 | 214 | """ 215 | string_field="color" 216 | with pytest.raises(TypeError) as execinfo: 217 | rtiOutputFixture.instance.set_string(string_field,{"color":1}) 218 | 219 | def test_setBoolean_with_String(self,rtiOutputFixture): 220 | """ 221 | This function tests that a ``TypeError`` is raised when 222 | :func:`rticonnextdds_connector.Instance.setBoolean` is called 223 | with a String value 224 | 225 | :param rtiOutputFixture: :func:`conftest.rtiOutputFixture` 226 | :type rtiOutputFixture: `pytest.fixture `_ 227 | 228 | """ 229 | boolean_field="z" 230 | with pytest.raises(TypeError) as execinfo: 231 | rtiOutputFixture.instance.set_boolean(boolean_field,"str") 232 | 233 | # Implicit type conversion from number to Boolean 234 | def test_setBoolean_with_Number(self,rtiOutputFixture): 235 | """ 236 | This function tests that a ``TypeError`` is raised when 237 | :func:`rticonnextdds_connector.Instance.setBoolean` is called 238 | with a Number value 239 | 240 | :param rtiOutputFixture: :func:`conftest.rtiOutputFixture` 241 | :type rtiOutputFixture: `pytest.fixture `_ 242 | 243 | .. note:: This test is marked to fail as a Number value is implicitly converted to a 244 | Boolean value 245 | 246 | """ 247 | boolean_field="z" 248 | with pytest.raises(TypeError) as execinfo: 249 | rtiOutputFixture.instance.setBoolean(boolean_field,55.55) 250 | 251 | def test_setBoolean_with_Dictionary(self,rtiOutputFixture): 252 | """ 253 | This function tests that a ``TypeError`` is raised when 254 | :func:`rticonnextdds_connector.Instance.setBoolean` is called 255 | with a Dictionary value 256 | 257 | :param rtiOutputFixture: :func:`conftest.rtiOutputFixture` 258 | :type rtiOutputFixture: `pytest.fixture `_ 259 | 260 | """ 261 | boolean_field="z" 262 | with pytest.raises(TypeError) as excinfo: 263 | rtiOutputFixture.instance.set_boolean(boolean_field,{"color":1}) 264 | 265 | 266 | def test_wait_for_acknowledgments(self, rtiOutputFixture): 267 | rtiOutputFixture.write() 268 | rtiOutputFixture.wait() 269 | rtiOutputFixture.wait(1) # Should return immediately 270 | 271 | def test_write(self, rtiOutputFixture): 272 | """ 273 | This function tests that :func:`rticonnectdds_connector.Output.write` call 274 | fails (and an exception is raised) under a variety of circumstances. 275 | """ 276 | rtiOutputFixture.instance["color"] = "1" 277 | rtiOutputFixture.write() 278 | rtiOutputFixture.instance["color"] = "2" 279 | rtiOutputFixture.write() 280 | rtiOutputFixture.instance["color"] = "3" 281 | # Exception will be raised as we are about to hit max_instances 282 | with pytest.raises(rti.Error) as excinfo: 283 | rtiOutputFixture.write() 284 | 285 | 286 | def test_write_with_params(self, one_use_output): 287 | 288 | related_id_long = { 289 | "writer_guid": {"value": [10, 30, 1, 66, 0, 0, 29, 180, 0, 0, 0, 1, 128, 0, 0, 3]}, 290 | "sequence_number": {"high": 0, "low": 1} 291 | } 292 | one_use_output.write(identity=related_id_long) 293 | 294 | related_id_short = { 295 | "writer_guid": [10, 30, 1, 66, 0, 0, 29, 180, 0, 0, 0, 1, 128, 0, 0, 3], 296 | "sequence_number": 2**53 297 | } 298 | 299 | one_use_output.write(source_timestamp=100) 300 | one_use_output.write(related_sample_identity=related_id_short) 301 | one_use_output.write( 302 | source_timestamp=100, 303 | related_sample_identity=related_id_long, 304 | identity=related_id_short) 305 | 306 | # sequence number must increase 307 | with pytest.raises(rti.Error): 308 | one_use_output.write(identity=related_id_short) 309 | 310 | # nonexistent parameter 311 | with pytest.raises(rti.Error, match=r".*invalid_param.*") as execinfo: 312 | one_use_output.write(invalid_param="invalid") 313 | 314 | # invalid format 315 | with pytest.raises(rti.Error, match=r".*error parsing related_sample_identity.*") as execinfo: 316 | one_use_output.write(related_sample_identity="invalid") 317 | with pytest.raises(rti.Error, match=r".*error parsing identity.*") as execinfo: 318 | one_use_output.write(identity="invalid") 319 | with pytest.raises(rti.Error, match=r".*error parsing source_timestamp.*") as execinfo: 320 | one_use_output.write(source_timestamp=3.4) 321 | -------------------------------------------------------------------------------- /test/python/test_rticonnextdds_performance.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # (c) 2020 Copyright, Real-Time Innovations. All rights reserved. # 3 | # No duplications, whole or partial, manual or electronic, may be made # 4 | # without express written permission. Any such copies, or revisions thereof, # 5 | # must display this notice unaltered. # 6 | # This code contains trade secrets of Real-Time Innovations, Inc. # 7 | ############################################################################### 8 | 9 | import pytest,sys,os 10 | sys.path.append(os.path.dirname(os.path.realpath(__file__))+ "/../../") 11 | import rticonnextdds_connector as rti 12 | from test_utils import * 13 | import ctypes 14 | 15 | # iterations is configured by passing `--iterations <>` on the command line. 16 | # By default, pytest captures the stdout. Supply -s to view the results. 17 | 18 | # These tests currently take around 25 minutes to run with 100 iterations. 19 | # They were added to help verify performance of another product, connextdds-py. 20 | # If you want to run them, remove this decorator. 21 | # Maybe once CON-42 is implemented we can run them by default. 22 | @pytest.mark.skip(reason="Takes too long to run these tests, remove this line and run manually") 23 | class TestPerformance: 24 | """ 25 | This class tests the performance of Connector 26 | """ 27 | 28 | # The use-case of setting a sequence in Connector is slow, and likely will 29 | # be until we implement CON-42. 30 | # Here we time how long it takes to set a sequence element by element. 31 | def test_set_sequence_element_by_element(self, one_use_connector, iterations): 32 | # Get the input and output which communicate using the performance test type 33 | the_input = one_use_connector.get_input("MySubscriber::PerformanceTestReader") 34 | the_output = one_use_connector.get_output("MyPublisher::PerformanceTestWriter") 35 | 36 | # Wait for discovery between the entities 37 | the_input.wait_for_publications(5000) 38 | the_output.wait_for_subscriptions(5000) 39 | 40 | # Set each element of the sequence separately 41 | total_time = 0 42 | for i in range(0, iterations): 43 | start_time = time.time() 44 | for i in range (0, 600000): 45 | the_output.instance['myOctSeq[%d]' % (i)] = 2 46 | total_time += (time.time() - start_time) 47 | average_time = total_time / iterations 48 | print("Average time setting element-by-element: " + str(average_time)) 49 | # Average time setting element-by-element: 2.9646800351142883 50 | 51 | # The use-case of setting a sequence in Connector is slow, and likely will 52 | # be until we implement CON-42. 53 | # Here we time how long it takes to set a sequence from a Python list. 54 | # i.e., output.instance['myOctSeq'] = [1, 2, 3, 4, 5, ....] 55 | def test_set_sequence_from_list(self, one_use_connector, iterations): 56 | # Get the input and output which communicate using the performance test type 57 | the_input = one_use_connector.get_input("MySubscriber::PerformanceTestReader") 58 | the_output = one_use_connector.get_output("MyPublisher::PerformanceTestWriter") 59 | 60 | # Wait for discovery between the entities 61 | the_input.wait_for_publications(5000) 62 | the_output.wait_for_subscriptions(5000) 63 | 64 | # Create a python list which contains 600000 and set the sequence from it 65 | total_time = 0 66 | average_time = 0 67 | myOctSeq = [0] 68 | for i in range (1, 600000): 69 | myOctSeq.append(i) 70 | 71 | for i in range (0, iterations): 72 | start_time = time.time() 73 | the_output.instance['myOctSeq'] = myOctSeq 74 | total_time += (time.time() - start_time) 75 | average_time = total_time / iterations 76 | print("Average time setting entire list in one go: " + str(average_time)) 77 | # Note to self Average time: 6.60276771068573 78 | 79 | # The use-case of obtaining a dictionary containing a sequence in Connector 80 | # is slow, and likely will be until we implement CON-42. 81 | # Here we have a sequence with 600000 elements. We time how long it takes to 82 | # obtain this sequence as a Python list 83 | def test_get_sequence(self, one_use_connector, iterations): 84 | # Get the input and output which communicate using the performance test type 85 | the_input = one_use_connector.get_input("MySubscriber::PerformanceTestReader") 86 | the_output = one_use_connector.get_output("MyPublisher::PerformanceTestWriter") 87 | 88 | # Wait for discovery between the entities 89 | the_input.wait_for_publications(5000) 90 | the_output.wait_for_subscriptions(5000) 91 | # Set the sample on the writer (the performance of this operation is tested 92 | # in the test_set_sequence test) 93 | the_output.instance['myOctSeq[599999]'] = 2 94 | # Write the sample and receive it on the input 95 | sample = send_data(the_output, the_input) 96 | 97 | # Now we can test the performance. We time how long it takes to retrieve the 98 | # sequence as a dictionary, repeat x times and take the average 99 | total_time = 0 100 | for i in range (0, iterations): 101 | start_time = time.time() 102 | myOctSeq = sample['myOctSeq'] 103 | total_time += (time.time() - start_time) 104 | average_time = total_time / iterations 105 | print("Average time to get sequence as a list: " + str(average_time)) 106 | # Note to self Average time: 0.20733366489410401 107 | 108 | # Use the workaround of calling into the native DynamicData APIs directly. 109 | # We do not run this test on Windows as we would need an entire Connext 110 | # DDS Pro installation due to the symbols not being exported 111 | @pytest.mark.xfail(sys.platform.startswith("win"), reason="symbols not exported") 112 | def test_get_sequence_native(self, one_use_connector, iterations): 113 | # Get the input and output which communicate using the performance test type 114 | the_input = one_use_connector.get_input("MySubscriber::PerformanceTestReader") 115 | the_output = one_use_connector.get_output("MyPublisher::PerformanceTestWriter") 116 | # Wait for discovery between the entities 117 | the_input.wait_for_publications(5000) 118 | the_output.wait_for_subscriptions(5000) 119 | 120 | # We need to use the following native API: 121 | # DDS_ReturnCode_t DDS_DynamicData_set_octet_array( 122 | # DDS_DynamicData *, 123 | # const char *, 124 | # DDS_DynamicDataMemberId, 125 | # DDS_UnisgnedLong 126 | # const DDS_Octet *) 127 | DDS_DynamicData_set_octet_array = rti.connector_binding.library.DDS_DynamicData_set_octet_array 128 | DDS_DynamicData_set_octet_array.restype = ctypes.c_int 129 | DDS_DynamicData_set_octet_array.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_long, ctypes.c_ulong, ctypes.c_void_p] 130 | 131 | # Create Python list for what we are going to set 132 | myOctSeq = [b'f'] * 600000 133 | # Get native handle 134 | native_dynamic_data = the_output.instance.native 135 | # Get length as ctype 136 | array_length = ctypes.c_ulong(len(myOctSeq)) 137 | # Define ctype 138 | in_array_type = ctypes.c_char * len(myOctSeq) 139 | # Create instance of new type 140 | in_array = in_array_type(*myOctSeq) 141 | total_time = 0 142 | for i in range (0, iterations): 143 | start_time = time.time() 144 | DDS_DynamicData_set_octet_array( 145 | ctypes.cast(native_dynamic_data, ctypes.c_void_p), 146 | 'myOctSeq'.encode("utf8"), 147 | 0, 148 | array_length, 149 | ctypes.byref(in_array)) 150 | total_time += (time.time() - start_time) 151 | average_time = total_time / iterations 152 | 153 | print("Average time to set a sequence using native Dynamic Data APIs: " + str(average_time)) 154 | 155 | 156 | # Use the workaround of calling into the native DynamicData APIs directly. 157 | # We do not run this test on Windows as we would need an entire Connext 158 | # DDS Pro installation due to the symbols not being exported 159 | @pytest.mark.xfail(sys.platform.startswith("win"), reason="symbols not exported") 160 | def test_set_sequence_native(self, one_use_connector, iterations): 161 | # Get the input and output which communicate using the performance test type 162 | the_input = one_use_connector.get_input("MySubscriber::PerformanceTestReader") 163 | the_output = one_use_connector.get_output("MyPublisher::PerformanceTestWriter") 164 | # Wait for discovery between the entities 165 | the_input.wait_for_publications(5000) 166 | the_output.wait_for_subscriptions(5000) 167 | # Set the sample on the writer (the performance of this operation is tested 168 | # in the test_set_sequence test) 169 | the_output.instance['myOctSeq[599999]'] = 2 170 | # Write the sample and receive it on the input 171 | sample = send_data(the_output, the_input) 172 | 173 | # We need to use the following native API: 174 | # DDS_ReturnCode_t DDS_DynamicData_get_octet_array( 175 | # DDS_DynamicData * self, 176 | # DDS_Octet *array, 177 | # DDS_UnsignedLong *length, 178 | # const char *name, 179 | # DDS_DynamicDataMemberId member_id) 180 | DDS_DynamicData_get_octet_array = rti.connector_binding.library.DDS_DynamicData_get_octet_array 181 | DDS_DynamicData_get_octet_array.restype = ctypes.c_int 182 | DDS_DynamicData_get_octet_array.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_char_p, ctypes.c_ulong] 183 | 184 | # First creates an unsigned long for the sequence length 185 | array_length = ctypes.c_ulong(600000) 186 | # Now define a type that we will use to store the result 187 | in_array_type = ctypes.c_char * 600000 188 | # Create an instance of that type 189 | in_array = in_array_type() 190 | # Call the Native API 191 | total_time = 0 192 | for i in range (0, iterations): 193 | start_time = time.time() 194 | DDS_DynamicData_get_octet_array( 195 | ctypes.cast(sample.native, ctypes.c_void_p), 196 | ctypes.byref(in_array), 197 | ctypes.byref(array_length), 198 | 'myOctSeq'.encode("utf8"), 199 | 0) 200 | total_time += (time.time() - start_time) 201 | average_time = total_time / iterations 202 | print("Average time to obtain a sequence using native Dynamic Data APIs: " + str(average_time)) 203 | -------------------------------------------------------------------------------- /test/python/test_rticonnextdds_threading.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # (c) 2020 Copyright, Real-Time Innovations. All rights reserved. # 3 | # No duplications, whole or partial, manual or electronic, may be made # 4 | # without express written permission. Any such copies, or revisions thereof, # 5 | # must display this notice unaltered. # 6 | # This code contains trade secrets of Real-Time Innovations, Inc. # 7 | ############################################################################### 8 | 9 | import pytest,time,sys,os,ctypes,json 10 | sys.path.append(os.path.dirname(os.path.realpath(__file__)) + "/../../") 11 | import rticonnextdds_connector as rti 12 | from test_utils import send_data, wait_for_data, open_test_connector 13 | import threading 14 | 15 | # In order to be able to catch failures in threads, we need to wrap the 16 | # threading.Thread class such that it re-raises exceptions 17 | class ConnectorCreationThread(threading.Thread): 18 | def __init__(self, target): 19 | threading.Thread.__init__(self, target=target) 20 | self.error = None 21 | 22 | def run(self): 23 | try: 24 | threading.Thread.run(self) 25 | except BaseException as e: 26 | self.error = e 27 | 28 | def join(self, timeout=None): 29 | super(ConnectorCreationThread, self).join(timeout) 30 | if self.error is not None: 31 | raise self.error 32 | 33 | class TestThreading: 34 | """ 35 | Connector is not thread-safe, meaning that it is not supported to make concurrent 36 | calls to the native library. However, protecting these calls with a 3rd-party 37 | threading library (such as Python's 'Threading') is supported. Here we test 38 | that this works as intended. 39 | """ 40 | 41 | # In this test we create two Connector objects in separate threads. From one 42 | # of the connectors we create an input, from the other an output and check 43 | # that communication can occur. 44 | # In order to ensure we are testing for CON-163 bug, the XML file does not 45 | # contain a tag. 46 | def test_creation_of_multiple_connectors(self): 47 | sem = threading.Semaphore() 48 | xml_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../xml/TestConnector3.xml") 49 | def output_thread(): 50 | with sem: 51 | with rti.open_connector( 52 | config_name="MyParticipantLibrary::MyParticipant", 53 | url=xml_path) as connector: 54 | assert connector is not None 55 | the_output = connector.getOutput("MyPublisher::MyWriter") 56 | assert the_output is not None 57 | 58 | def input_thread(): 59 | with sem: 60 | with rti.open_connector( 61 | config_name="MyParticipantLibrary::MyParticipant", 62 | url=xml_path) as connector: 63 | assert connector is not None 64 | the_input = connector.getInput("MySubscriber::MyReader") 65 | assert the_input is not None 66 | 67 | input_thread = ConnectorCreationThread(input_thread) 68 | output_thread = ConnectorCreationThread(output_thread) 69 | input_thread.start() 70 | output_thread.start() 71 | input_thread.join(5000) 72 | output_thread.join(5000) 73 | -------------------------------------------------------------------------------- /test/python/test_utils.py: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # (c) 2019 Copyright, Real-Time Innovations. All rights reserved. # 3 | # No duplications, whole or partial, manual or electronic, may be made # 4 | # without express written permission. Any such copies, or revisions thereof, # 5 | # must display this notice unaltered. # 6 | # This code contains trade secrets of Real-Time Innovations, Inc. # 7 | ############################################################################### 8 | 9 | import sys, os, pytest, threading, time 10 | 11 | sys.path.append(os.path.dirname(os.path.realpath(__file__))+ "/../../") 12 | import rticonnextdds_connector as rti 13 | 14 | def open_test_connector(config_name): 15 | xml_path = os.path.join( 16 | os.path.dirname(os.path.realpath(__file__)), 17 | "../xml/TestConnector.xml") 18 | 19 | return rti.open_connector(config_name, xml_path) 20 | 21 | def wait_for_data(input, count = 1, do_take = True): 22 | """Waits until input has count samples""" 23 | 24 | for i in range(1, 5): 25 | input.read() 26 | assert input.samples.length <= count 27 | if input.samples.length == count: 28 | break 29 | input.wait(5000) 30 | 31 | assert input.samples.length == count 32 | if do_take: 33 | input.take() 34 | 35 | def send_data(output, input, **kwargs): 36 | """Writes one sample with optional arguments (kwargs) and returns the sample 37 | received by the input""" 38 | 39 | output.write(**kwargs) 40 | wait_for_data(input) 41 | return input.samples[0] 42 | -------------------------------------------------------------------------------- /test/xml/InvalidXml.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | UDPV4 | SHMEM 19 | 20 | 21 | 22 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /test/xml/TestConnector2.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /test/xml/TestConnector3.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | isolated_build = True 3 | envlist = py27,py36 4 | skip_missing_interpreters = true 5 | 6 | [testenv] 7 | whitelist_externals = poetry 8 | skip_install = True 9 | commands = 10 | pip install pytest 11 | pytest -rxXs ./test/python --junit-xml=tests-{envname}.xml 12 | --------------------------------------------------------------------------------