git commit -m "first commit"

This commit is contained in:
2026-05-28 10:29:58 +07:00
commit 167c52aeb6
2048 changed files with 740251 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
[flake8]
# The line length here has to match the black config in pyproject.toml
max-line-length = 120
exclude =
.git,
__pycache__
extend-ignore =
# See https://github.com/PyCQA/pycodestyle/issues/373
E203,

View File

@@ -0,0 +1,41 @@
name: Build and run ROS tests
on: [push, pull_request]
jobs:
build:
strategy:
matrix:
rosdistro: [noetic]
runs-on: ubuntu-latest
container:
image: ros:${{ matrix.rosdistro }}-ros-core
steps:
- name: Install apt dependencies
run: |
sudo apt-get update
sudo apt-get install -y build-essential clang-format-10 file git python3-catkin-lint python3-pip python3-rosdep
- name: Install pip dependencies
run: pip install pre-commit
- name: Checkout repository
uses: actions/checkout@v3
with:
path: src/rospy_message_converter
- name: Use rosdep to install remaining dependencies
run: |
sudo rosdep init
rosdep update
rosdep install --from-paths src -i -y --rosdistro ${{ matrix.rosdistro }}
- name: Build
run: |
. /opt/ros/${{ matrix.rosdistro }}/setup.sh
catkin_make install
- name: Run tests
run: |
. devel/setup.sh
CTEST_OUTPUT_ON_FAILURE=1 catkin_make test
cd src/rospy_message_converter
python3 src/rospy_message_converter/json_message_converter.py
python3 src/rospy_message_converter/message_converter.py
- name: Run pre-commit hooks
run: |
cd src/rospy_message_converter
pre-commit run -a

View File

@@ -0,0 +1,3 @@
*.pyc
.idea
*.iml

View File

@@ -0,0 +1,55 @@
# To use:
#
# pre-commit run -a
#
# Or:
#
# pre-commit install # (runs every time you commit in git)
#
# To update this file:
#
# pre-commit autoupdate
#
# See https://github.com/pre-commit/pre-commit
repos:
# Standard hooks
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.1.0
hooks:
- id: check-added-large-files
- id: check-case-conflict
- id: check-executables-have-shebangs
- id: check-docstring-first
- id: check-merge-conflict
- id: check-shebang-scripts-are-executable
- id: check-symlinks
- id: check-vcs-permalinks
- id: check-xml
- id: check-yaml
- id: debug-statements
- id: end-of-file-fixer
- id: fix-byte-order-marker
- id: mixed-line-ending
- id: trailing-whitespace
- repo: https://github.com/psf/black
rev: 22.3.0
hooks:
- id: black
- repo: https://github.com/PyCQA/flake8.git
rev: 5.0.4
hooks:
- id: flake8
- repo: local
hooks:
- id: catkin_lint
name: catkin_lint
description: Check package.xml and cmake files
entry: catkin_lint .
language: system
always_run: true
pass_filenames: false
args: [ "--strict" ]

View File

@@ -0,0 +1,161 @@
Change Log
==========
0.5.9 (2022-09-12)
------------------
* Fix flake8 errors
* Re-format code using black
* package.xml: Add missing build_export_depend
* Fix EOF and trailing whitespace
* Add pre-commit config
* README: Add note about branches
* pass down log_level to helper functions (`#60 <https://github.com/uos/rospy_message_converter/issues/60>`_)
* Declare file encoding
This is necessary on ROS Melodic (Python2), because I have added a
non-ASCII character (u umlaut) in my last commit.
* Add LICENSE file and license headers
* Contributors: Martin Günther, Yuri Rocha
0.5.8 (2022-03-03)
------------------
* add option to change log level (`#58 <https://github.com/uos/rospy_message_converter/issues/58>`_)
* Contributors: Yuri Rocha
0.5.7 (2021-09-08)
------------------
* Handle None values in dictionary
* Add tests for None
* Dockerfile-kinetic: Add --include-eol-distros
* Contributors: Martin Günther
0.5.6 (2021-03-01)
------------------
* Propagate strict_mode, check_missing_fields in _convert_to_ros_type
Previously, _convert_to_ros_type dropped strict_mode and
check_missing_fields in nested messages.
* Add NestedUint8ArrayTestService tests
* propagate check_types in _convert_to_ros_type (`#51 <https://github.com/uos/rospy_message_converter/issues/51>`_)
Co-authored-by: Martin Günther <martin.guenther@dfki.de>
* Fix binary_array_as_bytes=False with nested msgs
* Add param binary_array_as_bytes
Closes `#45 <https://github.com/uos/rospy_message_converter/issues/45>`_.
* Contributors: Marc Bosch-Jorge, Martin Günther, Otacon5555
0.5.5 (2020-11-09)
------------------
* Decode strings from ROS messages as UTF8
This makes the python2 behavior equal to python3.
* python3 only: Validate base64 strings
* Add bytes to python3 string types
This means that `bytes` will now also be base64-decoded, which fixes the following tests on python3:
* test_dictionary_with_uint8_array_bytes
* test_dictionary_with_uint8_array_bytes_unencoded
* test_dictionary_with_3uint8_array_bytes
On python2, `bytes` is just an alias for `str`, which is why it worked
without this.
* Fix and add tests
* Contributors: Martin Günther
0.5.4 (2020-10-13)
------------------
* Avoid numpy dependency
* Contributors: Martin Günther, betaboon
0.5.3 (2020-08-20)
------------------
* Add check_types parameter to convert_dictionary_to_ros_message (`#42 <https://github.com/uos/rospy_message_converter/issues/42>`_)
* Allow numpy numeric types in numeric fields (`#41 <https://github.com/uos/rospy_message_converter/issues/41>`_)
Fixes `#39 <https://github.com/uos/rospy_message_converter/issues/39>`_.
* perf: Remove remaining regexes
This is only a small speedup of about 1.03x.
* perf: Avoid regex in _is_field_type_a_primitive_array
This makes the function almost 3x faster.
* perf: Reorder type checks
Perform the cheaper checks first. This results in a speedup of about
1.2x.
* perf: Avoid regex in is_ros_binary_type
This makes is_ros_binary_type almost 5x faster and as a result the whole
convert_ros_message_to_dictionary function almost 2x faster.
* Compare types, not type names; improve error message
Old error message:
TypeError: Wrong type: '1.0' must be float64
New error message:
TypeError: Field 'x' has wrong type <type 'numpy.float64'> (valid types: [<type 'int'>, <type 'float'>])
* Remove unused python_to_ros_type_map
* added test for convert_dictionary_to_ros_message with int8 array
* python 3 fix for _convert_to_ros_binary
* Contributors: Martin Günther, Steffen Rühl
0.5.2 (2020-07-09)
------------------
* Check for wrong field types when converting from dict to ros msg
* Check for missing fields when converting from dict to ros msg
* Contributors: Martin Günther, alecarnevale
0.5.1 (2020-05-25)
------------------
* Initial release into Noetic
* Decode base64-encoded byte arrays as unicode
* Make tests compatible with python3
* add check for python3 str serializing `#33 <https://github.com/uos/rospy_message_converter/issues/33>`_ (`#34 <https://github.com/uos/rospy_message_converter/issues/34>`_)
* efficient conversion of primitive array to ros type (`#31 <https://github.com/uos/rospy_message_converter/issues/31>`_)
* efficient conversion of primitive array
* removed unused _convert_from_ros_primitive
* optionally ignore extra fields when deserializing (`#29 <https://github.com/uos/rospy_message_converter/issues/29>`_)
* Remove EOL distros indigo + lunar from CI
* travis CI: Use matrix to split ROS distros
* Update README (convert to md, add build status)
* Contributors: Martin Günther, George Hartt, Jannik Abbenseth, Omri Rozenzaft
0.5.0 (2019-01-17)
------------------
* Initial release into Lunar and Melodic
* Remove support for Jade (EOL)
* Change maintainer from Brandon Alexander to Martin Günther
* Move repo from baalexander to uos
* Add serialize_deserialize to unit tests, fix incorrect tests caught by this
* Remove dependency on ROS master in tests; all tests are now unit
tests (`#18 <https://github.com/uos/rospy_message_converter/issues/18>`_)
* Add service request/response support (`#17 <https://github.com/uos/rospy_message_converter/issues/17>`_)
* Fix fixed-size uint8 array conversion failure (`#15 <https://github.com/uos/rospy_message_converter/issues/15>`_)
* Fix unicode handling in string fields (`#13 <https://github.com/uos/rospy_message_converter/issues/13>`_)
* Enable testing only if CATKIN_ENABLE_TESTING is set (`#9 <https://github.com/uos/rospy_message_converter/issues/9>`_)
* Contributors: Martin Günther, Brandon Alexander, George Laurent, Jean-Baptiste Doyon, Viktor Schlegel, Rein Appeldoorn, Will Baker, neka-nat
0.4.0 (2015-12-13)
------------------
* Adds support for ROS Jade
* Removes support for ROS Groovy and Hydro (EOL)
* Uses single branch for all ROS versions
* Docker support for local development and Travis CI
0.3.0 (2014-06-03)
------------------
* Adds support for ROS Indigo
0.2.0 (2013-07-15)
------------------
* Updates to ROS Hydro
* Builds and runs tests with Travis CI
* Adds CHANGELOG
0.1.4 (2013-04-16)
------------------
* Documents Python functions
* Throws error if invalid JSON or dictionary
0.1.3 (2013-03-04)
------------------
* Adds rostest dependency
0.1.2 (2013-03-04)
------------------
* Adds missing build_depends and run_depends
0.1.1 (2013-03-01)
------------------
* Adds message_generation dependency to fix build
0.1.0 (2013-02-27)
------------------
* Initial release of rospy_message_converter

View File

@@ -0,0 +1,37 @@
cmake_minimum_required(VERSION 3.5.1)
cmake_policy(SET CMP0048 NEW)
project(rospy_message_converter)
find_package(catkin REQUIRED COMPONENTS message_generation std_msgs)
# Generate messages for testing (NOINSTALL)
add_message_files(
FILES
NestedUint8ArrayTestMessage.msg
TestArray.msg
Uint8Array3TestMessage.msg
Uint8ArrayTestMessage.msg
NOINSTALL
)
add_service_files(
FILES
NestedUint8ArrayTestService.srv
NOINSTALL
)
catkin_python_setup()
# Generate added messages and services with any dependencies listed here
generate_messages(
DEPENDENCIES
std_msgs
)
catkin_package(CATKIN_DEPENDS message_runtime std_msgs)
# Testing
if(CATKIN_ENABLE_TESTING)
catkin_add_nosetests(test/test_json_message_converter.py)
catkin_add_nosetests(test/test_message_converter.py)
endif()

View File

@@ -0,0 +1,18 @@
FROM ros:kinetic-ros-core
RUN apt-get update \
&& apt-get install -y build-essential python-rosdep cmake \
&& rm -rf /var/lib/apt/lists/{apt,dpkg,cache,log} /tmp/* /var/tmp/*
RUN rosdep init && rosdep update --include-eol-distros
# Create ROS workspace
COPY . /ws/src/rospy_message_converter
WORKDIR /ws
# Install the package and its dependencies
RUN rosdep install --from-paths src --ignore-src --rosdistro kinetic -y
# Set up the development environment
RUN /bin/bash -c "source /opt/ros/kinetic/setup.bash && \
catkin_make install"

View File

@@ -0,0 +1,18 @@
FROM ros:melodic-ros-core
RUN apt-get update \
&& apt-get install -y build-essential python-rosdep cmake \
&& rm -rf /var/lib/apt/lists/{apt,dpkg,cache,log} /tmp/* /var/tmp/*
RUN rosdep init && rosdep update
# Create ROS workspace
COPY . /ws/src/rospy_message_converter
WORKDIR /ws
# Install the package and its dependencies
RUN rosdep install --from-paths src --ignore-src --rosdistro melodic -y
# Set up the development environment
RUN /bin/bash -c "source /opt/ros/melodic/setup.bash && \
catkin_make install"

View File

@@ -0,0 +1,18 @@
FROM ros:noetic-ros-core
RUN apt-get update \
&& apt-get install -y build-essential python3-rosdep cmake \
&& rm -rf /var/lib/apt/lists/{apt,dpkg,cache,log} /tmp/* /var/tmp/*
RUN rosdep init && rosdep update
# Create ROS workspace
COPY . /ws/src/rospy_message_converter
WORKDIR /ws
# Install the package and its dependencies
RUN rosdep install --from-paths src --ignore-src --rosdistro noetic -y
# Set up the development environment
RUN /bin/bash -c "source /opt/ros/noetic/setup.bash && \
catkin_make install"

View File

@@ -0,0 +1,28 @@
Copyright (c) 2019-2022, Martin Günther (DFKI GmbH) and others
Copyright (c) 2013-2016, Brandon Alexander
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of this project nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,104 @@
rospy_message_converter
=======================
Rospy_message_converter is a lightweight ROS package and Python library to
convert from Python dictionaries and JSON messages to rospy messages, and vice
versa.
ROS 1 and ROS 2 branches
------------------------
ROS 1 users should use the `master` branch. ROS 2 users should use the appropriate
branch for their distro (`foxy`/`galactic`/`humble`/`rolling`/...).
Usage
-----
Convert a dictionary to a ROS message
```python
from rospy_message_converter import message_converter
from std_msgs.msg import String
dictionary = { 'data': 'Howdy' }
message = message_converter.convert_dictionary_to_ros_message('std_msgs/String', dictionary)
```
Convert a ROS message to a dictionary
```python
from rospy_message_converter import message_converter
from std_msgs.msg import String
message = String(data = 'Howdy')
dictionary = message_converter.convert_ros_message_to_dictionary(message)
```
Convert JSON to a ROS message
```python
from rospy_message_converter import json_message_converter
from std_msgs.msg import String
json_str = '{"data": "Hello"}'
message = json_message_converter.convert_json_to_ros_message('std_msgs/String', json_str)
```
Convert a ROS message to JSON
```python
from rospy_message_converter import json_message_converter
from std_msgs.msg import String
message = String(data = 'Hello')
json_str = json_message_converter.convert_ros_message_to_json(message)
```
Test
----
To run the tests:
```bash
catkin_make test
```
pre-commit Formatting Checks
----------------------------
This repo has a [pre-commit](https://pre-commit.com/) check that runs in CI.
You can use this locally and set it up to run automatically before you commit
something. To install, use pip:
```bash
pip3 install --user pre-commit
```
To run over all the files in the repo manually:
```bash
pre-commit run -a
```
To run pre-commit automatically before committing in the local repo, install the git hooks:
```bash
pre-commit install
```
License
-------
Project is released under the BSD license.
GitHub actions - Continuous Integration
---------------------------------------
[![Build Status](https://github.com/DFKI-NI/rospy_message_converter/actions/workflows/github-actions.yml/badge.svg)](https://github.com/DFKI-NI/rospy_message_converter/actions/workflows/github-actions.yml/)
ROS Buildfarm
-------------
| | binary deb | source deb | devel | doc |
|-----------|------------|------------|-------|-----|
| kinetic | [![Build Status](http://build.ros.org/buildStatus/icon?job=Kbin_uX64__rospy_message_converter__ubuntu_xenial_amd64__binary)](http://build.ros.org/job/Kbin_uX64__rospy_message_converter__ubuntu_xenial_amd64__binary/) | [![Build Status](http://build.ros.org/buildStatus/icon?job=Ksrc_uX__rospy_message_converter__ubuntu_xenial__source)](http://build.ros.org/job/Ksrc_uX__rospy_message_converter__ubuntu_xenial__source/) | [![Build Status](http://build.ros.org/buildStatus/icon?job=Kdev__rospy_message_converter__ubuntu_xenial_amd64)](http://build.ros.org/job/Kdev__rospy_message_converter__ubuntu_xenial_amd64) | [![Build Status](http://build.ros.org/buildStatus/icon?job=Kdoc__rospy_message_converter__ubuntu_xenial_amd64)](http://build.ros.org/job/Kdoc__rospy_message_converter__ubuntu_xenial_amd64) |
| melodic | [![Build Status](http://build.ros.org/buildStatus/icon?job=Mbin_uB64__rospy_message_converter__ubuntu_bionic_amd64__binary)](http://build.ros.org/job/Mbin_uB64__rospy_message_converter__ubuntu_bionic_amd64__binary) | [![Build Status](http://build.ros.org/buildStatus/icon?job=Msrc_uB__rospy_message_converter__ubuntu_bionic__source)](http://build.ros.org/job/Msrc_uB__rospy_message_converter__ubuntu_bionic__source/) | [![Build Status](http://build.ros.org/buildStatus/icon?job=Mdev__rospy_message_converter__ubuntu_bionic_amd64)](http://build.ros.org/job/Mdev__rospy_message_converter__ubuntu_bionic_amd64) | [![Build Status](http://build.ros.org/buildStatus/icon?job=Mdoc__rospy_message_converter__ubuntu_bionic_amd64)](http://build.ros.org/job/Mdoc__rospy_message_converter__ubuntu_bionic_amd64) |
| noetic | [![Build Status](http://build.ros.org/buildStatus/icon?job=Nbin_uF64__rospy_message_converter__ubuntu_focal_amd64__binary)](http://build.ros.org/job/Nbin_uF64__rospy_message_converter__ubuntu_focal_amd64__binary/) | [![Build Status](http://build.ros.org/buildStatus/icon?job=Nsrc_uF__rospy_message_converter__ubuntu_focal__source)](http://build.ros.org/job/Nsrc_uF__rospy_message_converter__ubuntu_focal__source/) | [![Build Status](http://build.ros.org/buildStatus/icon?job=Ndev__rospy_message_converter__ubuntu_focal_amd64)](http://build.ros.org/job/Ndev__rospy_message_converter__ubuntu_focal_amd64/) | [![Build Status](http://build.ros.org/buildStatus/icon?job=Ndoc__rospy_message_converter__ubuntu_focal_amd64)](http://build.ros.org/job/Ndoc__rospy_message_converter__ubuntu_focal_amd64/) |

View File

@@ -0,0 +1,2 @@
# array of arrays for testing purposes
Uint8ArrayTestMessage[] arrays

View File

@@ -0,0 +1 @@
float64[] data

View File

@@ -0,0 +1,2 @@
# Fixed size uint8 array for testing purposes
uint8[3] data

View File

@@ -0,0 +1,2 @@
# Size-agnostic uint8 array for testing purposes
uint8[] data

View File

@@ -0,0 +1,28 @@
<?xml version="1.0"?>
<package format="3">
<name>rospy_message_converter</name>
<version>0.5.9</version>
<description>Converts between Python dictionaries and JSON to rospy messages.</description>
<maintainer email="martin.guenther@dfki.de">Martin Günther</maintainer>
<license>BSD</license>
<url type="website">http://ros.org/wiki/rospy_message_converter</url>
<url type="repository">https://github.com/DFKI-NI/rospy_message_converter</url>
<url type="bugtracker">https://github.com/DFKI-NI/rospy_message_converter/issues</url>
<author email="baalexander@gmail.com">Brandon Alexander</author>
<buildtool_depend>catkin</buildtool_depend>
<build_depend>message_generation</build_depend>
<build_depend>std_msgs</build_depend>
<build_export_depend>std_msgs</build_export_depend>
<exec_depend>roslib</exec_depend>
<exec_depend>rospy</exec_depend>
<exec_depend>message_runtime</exec_depend>
<exec_depend>std_msgs</exec_depend>
<test_depend>rosunit</test_depend>
<test_depend>std_srvs</test_depend>
<test_depend condition="$ROS_PYTHON_VERSION == 2">python-numpy</test_depend>
<test_depend condition="$ROS_PYTHON_VERSION == 3">python3-numpy</test_depend>
</package>

View File

@@ -0,0 +1,5 @@
[tool.black]
# The line length here has to match the flake8 config in .flake8
line-length = 120
target-version = ['py38']
skip-string-normalization = true

View File

@@ -0,0 +1,11 @@
# ! DO NOT MANUALLY INVOKE THIS setup.py, USE CATKIN INSTEAD
from distutils.core import setup
from catkin_pkg.python_setup import generate_distutils_setup
d = generate_distutils_setup(
packages=['rospy_message_converter'],
package_dir={'': 'src'},
)
setup(**d)

View File

@@ -0,0 +1,97 @@
# -*- coding: utf-8 -*-
#
# Software License Agreement (BSD License)
#
# Copyright (c) 2019-2022, Martin Günther (DFKI GmbH) and others
# Copyright (c) 2013-2016, Brandon Alexander
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
# * Neither the name of this project nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
import json
from rospy_message_converter import message_converter
def convert_json_to_ros_message(message_type, json_message, strict_mode=True, log_level='error'):
"""
Takes in the message type and a JSON-formatted string and returns a ROS
message.
:param message_type: The desired ROS message type of the result
:type message_type: str
:param json_message: A JSON-formatted string
:type json_message: str
:param strict_mode: If strict_mode is set, an exception will be thrown if the json message contains extra fields.
:type strict_mode: bool, optional
:param log_level: The log level to be used. Available levels: debug, info, warning, error, critical
:type log_level: str, optional
:return: A ROS message
:rtype: class:`genpy.Message`
Example:
>>> msg_type = "std_msgs/String"
>>> json_msg = '{"data": "Hello, Robot"}'
>>> convert_json_to_ros_message(msg_type, json_msg)
data: "Hello, Robot"
"""
dictionary = json.loads(json_message)
return message_converter.convert_dictionary_to_ros_message(
message_type, dictionary, strict_mode=strict_mode, log_level=log_level
)
def convert_ros_message_to_json(message, binary_array_as_bytes=True):
"""
Takes in a ROS message and returns a JSON-formatted string.
:param message: A ROS message to convert
:type message: class:`genpy.Message`
:param binary_array_as_bytes: rospy treats `uint8[]` data as a `bytes`, which is the Python representation for byte
data. In Python 2, this is the same as `str`. If this parameter is `False`, all `uint8[]` fields will be
converted to `list(int)` instead.
:type binary_array_as_bytes: bool, optional
:return: A JSON-formatted string
:rtype: str
Example:
>>> import std_msgs.msg
>>> ros_message = std_msgs.msg.String(data="Hello, Robot")
>>> convert_ros_message_to_json(ros_message)
'{"data": "Hello, Robot"}'
"""
dictionary = message_converter.convert_ros_message_to_dictionary(message, binary_array_as_bytes)
json_message = json.dumps(dictionary)
return json_message
if __name__ == "__main__":
import doctest
doctest.testmod()

View File

@@ -0,0 +1,397 @@
# -*- coding: utf-8 -*-
#
# Software License Agreement (BSD License)
#
# Copyright (c) 2019-2022, Martin Günther (DFKI GmbH) and others
# Copyright (c) 2013-2016, Brandon Alexander
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
# * Neither the name of this project nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
import logging
import roslib.message
import rospy
import base64
import sys
import copy
import collections
python3 = sys.hexversion > 0x03000000
python_list_types = [list, tuple]
if python3:
python_string_types = [str, bytes]
python_int_types = [int]
else:
python_string_types = [str, unicode] # noqa
python_int_types = [int, long] # noqa
python_float_types = [float]
ros_to_python_type_map = {
'bool': [bool],
'float32': copy.deepcopy(python_float_types + python_int_types),
'float64': copy.deepcopy(python_float_types + python_int_types),
'int8': copy.deepcopy(python_int_types),
'int16': copy.deepcopy(python_int_types),
'int32': copy.deepcopy(python_int_types),
'int64': copy.deepcopy(python_int_types),
'uint8': copy.deepcopy(python_int_types),
'uint16': copy.deepcopy(python_int_types),
'uint32': copy.deepcopy(python_int_types),
'uint64': copy.deepcopy(python_int_types),
'byte': copy.deepcopy(python_int_types),
'char': copy.deepcopy(python_int_types),
'string': copy.deepcopy(python_string_types),
}
try:
import numpy as np
_ros_to_numpy_type_map = {
'float32': [np.float32, np.int8, np.int16, np.uint8, np.uint16],
# don't include int32, because conversion to float may change value:
# v = np.iinfo(np.int32).max; np.float32(v) != v
'float64': [np.float32, np.float64, np.int8, np.int16, np.int32, np.uint8, np.uint16, np.uint32],
'int8': [np.int8],
'int16': [np.int8, np.int16, np.uint8],
'int32': [np.int8, np.int16, np.int32, np.uint8, np.uint16],
'int64': [np.int8, np.int16, np.int32, np.int64, np.uint8, np.uint16, np.uint32],
'uint8': [np.uint8],
'uint16': [np.uint8, np.uint16],
'uint32': [np.uint8, np.uint16, np.uint32],
'uint64': [np.uint8, np.uint16, np.uint32, np.uint64],
'byte': [np.int8],
'char': [np.uint8],
}
# merge type_maps
merged = collections.defaultdict(list, ros_to_python_type_map)
for k, v in _ros_to_numpy_type_map.items():
merged[k].extend(v)
ros_to_python_type_map = dict(merged)
except ImportError:
pass
ros_time_types = ['time', 'duration']
ros_primitive_types = [
'bool',
'byte',
'char',
'int8',
'uint8',
'int16',
'uint16',
'int32',
'uint32',
'int64',
'uint64',
'float32',
'float64',
'string',
]
ros_header_types = ['Header', 'std_msgs/Header', 'roslib/Header']
def convert_dictionary_to_ros_message(
message_type,
dictionary,
kind='message',
strict_mode=True,
check_missing_fields=False,
check_types=True,
log_level='error',
):
"""
Takes in the message type and a Python dictionary and returns a ROS message.
Example:
>>> msg_type = "std_msgs/String"
>>> dict_msg = { "data": "Hello, Robot" }
>>> convert_dictionary_to_ros_message(msg_type, dict_msg)
data: "Hello, Robot"
>>> msg_type = "std_srvs/SetBool"
>>> dict_msg = { "data": True }
>>> kind = "request"
>>> convert_dictionary_to_ros_message(msg_type, dict_msg, kind)
data: True
"""
if kind == 'message':
message_class = roslib.message.get_message_class(message_type)
message = message_class()
elif kind == 'request':
service_class = roslib.message.get_service_class(message_type)
message = service_class._request_class()
elif kind == 'response':
service_class = roslib.message.get_service_class(message_type)
message = service_class._response_class()
else:
raise ValueError('Unknown kind "%s".' % kind)
message_fields = dict(_get_message_fields(message))
remaining_message_fields = copy.deepcopy(message_fields)
if dictionary is None:
dictionary = {}
for field_name, field_value in dictionary.items():
if field_name in message_fields:
field_type = message_fields[field_name]
if field_value is not None:
field_value = _convert_to_ros_type(
field_name, field_type, field_value, strict_mode, check_missing_fields, check_types, log_level
)
setattr(message, field_name, field_value)
del remaining_message_fields[field_name]
else:
error_message = 'ROS message type "{0}" has no field named "{1}"'.format(message_type, field_name)
if strict_mode:
raise ValueError(error_message)
else:
if log_level not in ["debug", "info", "warning", "error", "critical"]:
log_level = "error"
logger = logging.getLogger('rosout')
log_func = getattr(logger, log_level)
log_func('{}! It will be ignored.'.format(error_message))
if check_missing_fields and remaining_message_fields:
error_message = 'Missing fields "{0}"'.format(remaining_message_fields)
raise ValueError(error_message)
return message
def _convert_to_ros_type(
field_name,
field_type,
field_value,
strict_mode=True,
check_missing_fields=False,
check_types=True,
log_level='error',
):
if _is_ros_binary_type(field_type):
field_value = _convert_to_ros_binary(field_type, field_value)
elif field_type in ros_time_types:
field_value = _convert_to_ros_time(field_type, field_value)
elif field_type in ros_primitive_types:
# Note: one could also use genpy.message.check_type() here, but:
# 1. check_type is "not designed to run fast and is meant only for error diagnosis"
# 2. it doesn't check floats (see ros/genpy#130)
# 3. it rejects numpy types, although they can be serialized
if check_types and type(field_value) not in ros_to_python_type_map[field_type]:
raise TypeError(
"Field '{0}' has wrong type {1} (valid types: {2})".format(
field_name, type(field_value), ros_to_python_type_map[field_type]
)
)
field_value = _convert_to_ros_primitive(field_type, field_value)
elif _is_field_type_a_primitive_array(field_type):
field_value = field_value
elif _is_field_type_an_array(field_type):
field_value = _convert_to_ros_array(
field_name, field_type, field_value, strict_mode, check_missing_fields, check_types, log_level
)
else:
field_value = convert_dictionary_to_ros_message(
field_type,
field_value,
strict_mode=strict_mode,
check_missing_fields=check_missing_fields,
check_types=check_types,
log_level=log_level,
)
return field_value
def _convert_to_ros_binary(field_type, field_value):
if type(field_value) in python_string_types:
if python3:
# base64 in python3 added the `validate` arg:
# If field_value is not properly base64 encoded and there are non-base64-alphabet characters in the input,
# a binascii.Error will be raised.
binary_value_as_string = base64.b64decode(field_value, validate=True)
else:
# base64 in python2 doesn't have the `validate` arg: characters that are not in the base-64 alphabet are
# silently discarded, resulting in garbage output.
binary_value_as_string = base64.b64decode(field_value)
else:
binary_value_as_string = bytes(bytearray(field_value))
return binary_value_as_string
def _convert_to_ros_time(field_type, field_value):
time = None
if field_type == 'time' and field_value == 'now':
time = rospy.get_rostime()
else:
if field_type == 'time':
time = rospy.rostime.Time()
elif field_type == 'duration':
time = rospy.rostime.Duration()
if 'secs' in field_value and field_value['secs'] is not None:
setattr(time, 'secs', field_value['secs'])
if 'nsecs' in field_value and field_value['nsecs'] is not None:
setattr(time, 'nsecs', field_value['nsecs'])
return time
def _convert_to_ros_primitive(field_type, field_value):
# std_msgs/msg/_String.py always calls encode() on python3, so don't do it here
if field_type == "string" and not python3:
field_value = field_value.encode('utf-8')
return field_value
def _convert_to_ros_array(
field_name,
field_type,
list_value,
strict_mode=True,
check_missing_fields=False,
check_types=True,
log_level='error',
):
# use index to raise ValueError if '[' not present
list_type = field_type[: field_type.index('[')]
return [
_convert_to_ros_type(field_name, list_type, value, strict_mode, check_missing_fields, check_types, log_level)
for value in list_value
]
def convert_ros_message_to_dictionary(message, binary_array_as_bytes=True):
"""
Takes in a ROS message and returns a Python dictionary.
Example:
>>> import std_msgs.msg
>>> ros_message = std_msgs.msg.UInt32(data=42)
>>> convert_ros_message_to_dictionary(ros_message)
{'data': 42}
"""
dictionary = {}
message_fields = _get_message_fields(message)
for field_name, field_type in message_fields:
field_value = getattr(message, field_name)
dictionary[field_name] = _convert_from_ros_type(field_type, field_value, binary_array_as_bytes)
return dictionary
def _convert_from_ros_type(field_type, field_value, binary_array_as_bytes=True):
if field_type in ros_primitive_types:
field_value = _convert_from_ros_primitive(field_type, field_value)
elif field_type in ros_time_types:
field_value = _convert_from_ros_time(field_type, field_value)
elif _is_ros_binary_type(field_type):
if binary_array_as_bytes:
field_value = _convert_from_ros_binary(field_type, field_value)
elif type(field_value) == str:
field_value = [ord(v) for v in field_value]
else:
field_value = list(field_value)
elif _is_field_type_a_primitive_array(field_type):
field_value = list(field_value)
elif _is_field_type_an_array(field_type):
field_value = _convert_from_ros_array(field_type, field_value, binary_array_as_bytes)
else:
field_value = convert_ros_message_to_dictionary(field_value, binary_array_as_bytes)
return field_value
def _is_ros_binary_type(field_type):
"""Checks if the field is a binary array one, fixed size or not
>>> _is_ros_binary_type("uint8")
False
>>> _is_ros_binary_type("uint8[]")
True
>>> _is_ros_binary_type("uint8[3]")
True
>>> _is_ros_binary_type("char")
False
>>> _is_ros_binary_type("char[]")
True
>>> _is_ros_binary_type("char[3]")
True
"""
return field_type.startswith('uint8[') or field_type.startswith('char[')
def _convert_from_ros_binary(field_type, field_value):
field_value = base64.b64encode(field_value).decode('utf-8')
return field_value
def _convert_from_ros_time(field_type, field_value):
field_value = {'secs': field_value.secs, 'nsecs': field_value.nsecs}
return field_value
def _convert_from_ros_primitive(field_type, field_value):
# std_msgs/msg/_String.py always calls decode() on python3, so don't do it here
if field_type == "string" and not python3:
field_value = field_value.decode('utf-8')
return field_value
def _convert_from_ros_array(field_type, field_value, binary_array_as_bytes=True):
# use index to raise ValueError if '[' not present
list_type = field_type[: field_type.index('[')]
return [_convert_from_ros_type(list_type, value, binary_array_as_bytes) for value in field_value]
def _get_message_fields(message):
return zip(message.__slots__, message._slot_types)
def _is_field_type_an_array(field_type):
return field_type.find('[') >= 0
def _is_field_type_a_primitive_array(field_type):
bracket_index = field_type.find('[')
if bracket_index < 0:
return False
else:
list_type = field_type[:bracket_index]
return list_type in ros_primitive_types
if __name__ == "__main__":
import doctest
doctest.testmod()

View File

@@ -0,0 +1,6 @@
# service with nested types for testing purposes
NestedUint8ArrayTestMessage input
---
NestedUint8ArrayTestMessage output

View File

@@ -0,0 +1,172 @@
# -*- coding: utf-8 -*-
#
# Software License Agreement (BSD License)
#
# Copyright (c) 2019-2022, Martin Günther (DFKI GmbH) and others
# Copyright (c) 2013-2016, Brandon Alexander
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
# * Neither the name of this project nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
import unittest
import rospy
from rospy_message_converter import json_message_converter
class TestJsonMessageConverter(unittest.TestCase):
def test_ros_message_with_string(self):
from std_msgs.msg import String
expected_json = '{"data": "Hello"}'
message = String(data='Hello')
message = serialize_deserialize(message)
returned_json = json_message_converter.convert_ros_message_to_json(message)
self.assertEqual(returned_json, expected_json)
def test_ros_message_with_string_unicode(self):
from std_msgs.msg import String
expected_json = '{"data": "Hello \\u00dcnicode"}'
message = String(data=u'Hello \u00dcnicode')
message = serialize_deserialize(message)
returned_json = json_message_converter.convert_ros_message_to_json(message)
self.assertEqual(returned_json, expected_json)
def test_ros_message_with_header(self):
from std_msgs.msg import Header
from time import time
now_time = rospy.Time(time())
expected_json1 = '{{"stamp": {{"secs": {0}, "nsecs": {1}}}, "frame_id": "my_frame", "seq": 3}}'.format(
now_time.secs, now_time.nsecs
)
expected_json2 = '{{"seq": 3, "stamp": {{"secs": {0}, "nsecs": {1}}}, "frame_id": "my_frame"}}'.format(
now_time.secs, now_time.nsecs
)
expected_json3 = '{{"frame_id": "my_frame", "seq": 3, "stamp": {{"secs": {0}, "nsecs": {1}}}}}'.format(
now_time.secs, now_time.nsecs
)
message = Header(stamp=now_time, frame_id='my_frame', seq=3)
message = serialize_deserialize(message)
returned_json = json_message_converter.convert_ros_message_to_json(message)
self.assertTrue(
returned_json == expected_json1 or returned_json == expected_json2 or returned_json == expected_json3
)
def test_ros_message_with_uint8_array(self):
from rospy_message_converter.msg import Uint8ArrayTestMessage
input_data = [97, 98, 99, 100]
expected_json = '{"data": "YWJjZA=="}' # base64.b64encode("abcd") is "YWJjZA=="
message = Uint8ArrayTestMessage(data=input_data)
message = serialize_deserialize(message)
returned_json = json_message_converter.convert_ros_message_to_json(message)
self.assertEqual(returned_json, expected_json)
def test_ros_message_with_3uint8_array(self):
from rospy_message_converter.msg import Uint8Array3TestMessage
input_data = [97, 98, 99]
expected_json = '{"data": "YWJj"}' # base64.b64encode("abc") is "YWJj"
message = Uint8Array3TestMessage(data=input_data)
message = serialize_deserialize(message)
returned_json = json_message_converter.convert_ros_message_to_json(message)
self.assertEqual(returned_json, expected_json)
def test_json_with_string(self):
from std_msgs.msg import String
expected_message = String(data='Hello')
json_str = '{"data": "Hello"}'
message = json_message_converter.convert_json_to_ros_message('std_msgs/String', json_str)
expected_message = serialize_deserialize(expected_message)
self.assertEqual(message, expected_message)
def test_json_with_string_unicode(self):
from std_msgs.msg import String
expected_message = String(data=u'Hello \u00dcnicode')
json_str = '{"data": "Hello \\u00dcnicode"}'
message = json_message_converter.convert_json_to_ros_message('std_msgs/String', json_str)
expected_message = serialize_deserialize(expected_message)
self.assertEqual(message, expected_message)
def test_json_with_header(self):
from std_msgs.msg import Header
from time import time
now_time = rospy.Time(time())
expected_message = Header(stamp=now_time, frame_id='my_frame', seq=12)
json_str = '{{"stamp": {{"secs": {0}, "nsecs": {1}}}, "frame_id": "my_frame", "seq": 12}}'.format(
now_time.secs, now_time.nsecs
)
message = json_message_converter.convert_json_to_ros_message('std_msgs/Header', json_str)
expected_message = serialize_deserialize(expected_message)
self.assertEqual(message, expected_message)
def test_json_with_string_null(self):
from std_msgs.msg import String
expected_message = String(data='')
json_str = '{"data": null}'
message = json_message_converter.convert_json_to_ros_message('std_msgs/String', json_str)
expected_message = serialize_deserialize(expected_message)
self.assertEqual(message, expected_message)
def test_json_with_invalid_message_fields(self):
self.assertRaises(
ValueError, json_message_converter.convert_json_to_ros_message, 'std_msgs/String', '{"not_data": "Hello"}'
)
def serialize_deserialize(message):
"""
Serialize and then deserialize a message. This simulates sending a message
between ROS nodes and makes sure that the ROS messages being tested are
actually serializable, and are in the same format as they would be received
over the network. In rospy, it is possible to assign an illegal data type
to a message field (for example, `message = String(data=42)`), but trying
to publish this message will throw `SerializationError: field data must be
of type str`. This method will expose such bugs.
"""
from io import BytesIO
buff = BytesIO()
message.serialize(buff)
result = message.__class__() # create new instance of same class as message
result.deserialize(buff.getvalue())
return result
PKG = 'rospy_message_converter'
NAME = 'test_json_message_converter'
if __name__ == '__main__':
import rosunit
rosunit.unitrun(PKG, NAME, TestJsonMessageConverter)

File diff suppressed because it is too large Load Diff