├── VERSION
├── docs
├── vars.rst
├── requirements.txt
├── static
│ ├── favicon.ico
│ ├── overview.png
│ ├── py-logo.png
│ ├── xml_doc.png
│ ├── rti-logo-FINALv2-White-OrangeDot.png
│ └── theme_overrides.css
├── advanced.rst
├── errors.rst
├── make.bat
├── Makefile
├── index.rst
├── getting_started.rst
├── threading.rst
├── intro.rst
├── conf.py
├── connector.rst
├── output.rst
├── copyright_license.rst
├── input.rst
├── release_notes.rst
├── configuration.rst
├── features.rst
└── data.rst
├── __init__.py
├── .gitattributes
├── LICENSE.pdf
├── tox.ini
├── CONTRIBUTING.md
├── rticonnextdds_connector
└── __init__.py
├── examples
└── python
│ ├── simple
│ ├── README.md
│ ├── writer.py
│ └── reader.py
│ ├── transformation
│ ├── README.md
│ ├── reader.py
│ └── transform.py
│ ├── images
│ ├── README.md
│ ├── image_reader_file.py
│ ├── ImagesExample.xml
│ ├── image_writer.py
│ └── image_reader.py
│ └── ShapeExample.xml
├── resources
├── docker
│ ├── documentation.Dockerfile
│ └── Dockerfile
└── jenkins
│ ├── build_and_test.groovy
│ └── build_doc.groovy
├── .gitignore
├── test
├── xml
│ ├── TestConnector2.xml
│ ├── TestConnector3.xml
│ └── InvalidXml.xml
└── python
│ ├── test_utils.py
│ ├── README.md
│ ├── test_rticonnextdds_threading.py
│ ├── test_rticonnextdds_input.py
│ ├── test_rticonnextdds_data_iterators.py
│ ├── conftest.py
│ ├── test_rticonnextdds_connector.py
│ ├── test_rticonnextdds_dataflow.py
│ ├── test_rticonnextdds_performance.py
│ ├── test_rticonnextdds_discovery.py
│ └── test_rticonnextdds_output.py
├── README.md
├── README.rst
└── setup.py
/VERSION:
--------------------------------------------------------------------------------
1 | 6.1.2.21
--------------------------------------------------------------------------------
/docs/vars.rst:
--------------------------------------------------------------------------------
1 | .. |br| raw:: html
2 |
3 |
--------------------------------------------------------------------------------
/__init__.py:
--------------------------------------------------------------------------------
1 | from .rticonnextdds_connector import *
2 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | LICENSE.pdf filter=lfs diff=lfs merge=lfs -text
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/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/HEAD/docs/static/favicon.ico
--------------------------------------------------------------------------------
/docs/static/overview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rticommunity/rticonnextdds-connector-py/HEAD/docs/static/overview.png
--------------------------------------------------------------------------------
/docs/static/py-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rticommunity/rticonnextdds-connector-py/HEAD/docs/static/py-logo.png
--------------------------------------------------------------------------------
/docs/static/xml_doc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rticommunity/rticonnextdds-connector-py/HEAD/docs/static/xml_doc.png
--------------------------------------------------------------------------------
/LICENSE.pdf:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:9b280c8dd7e1ab60fbdcd4671ef10f6b302ba055d3db313c1d5352e6f97e23bd
3 | size 125010
4 |
--------------------------------------------------------------------------------
/docs/static/rti-logo-FINALv2-White-OrangeDot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rticommunity/rticonnextdds-connector-py/HEAD/docs/static/rti-logo-FINALv2-White-OrangeDot.png
--------------------------------------------------------------------------------
/docs/advanced.rst:
--------------------------------------------------------------------------------
1 | Advanced Topics
2 | ===============
3 |
4 | .. toctree::
5 | :maxdepth: 2
6 |
7 | data
8 | threading
9 | errors
10 | features
11 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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`
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/test/xml/TestConnector2.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 `__.
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 `__.
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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_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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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_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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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.
--------------------------------------------------------------------------------