├── .gitignore ├── CHANGELOG.rst ├── CMakeLists.txt ├── README.md ├── cmake └── genpy-extras.cmake.em ├── doc ├── Makefile ├── conf.py └── index.rst ├── package.xml ├── rosdoc.yaml ├── scripts ├── CMakeLists.txt ├── genmsg_py.py └── gensrv_py.py ├── setup.py ├── src └── genpy │ ├── __init__.py │ ├── base.py │ ├── dynamic.py │ ├── generate_initpy.py │ ├── generate_numpy.py │ ├── generate_struct.py │ ├── generator.py │ ├── genpy_main.py │ ├── message.py │ └── rostime.py └── test ├── __init__.py ├── files ├── array │ ├── Object.msg │ ├── ObjectArray.msg │ ├── bool_fixed_deser.txt │ ├── bool_fixed_ser.txt │ ├── bool_varlen_deser.txt │ ├── bool_varlen_ser.txt │ ├── int16_fixed_deser.txt │ ├── int16_fixed_deser_np.txt │ ├── int16_fixed_ser.txt │ ├── int16_fixed_ser_np.txt │ ├── int16_varlen_deser.txt │ ├── int16_varlen_deser_np.txt │ ├── int16_varlen_ser.txt │ ├── int16_varlen_ser_np.txt │ ├── object_fixed_deser.txt │ ├── object_fixed_ser.txt │ ├── object_varlen_deser.txt │ ├── object_varlen_ser.txt │ ├── object_varlen_ser_full.txt │ ├── string_fixed_deser.txt │ ├── string_fixed_ser.txt │ ├── string_varlen_deser.txt │ ├── string_varlen_ser.txt │ ├── uint8_fixed_deser.txt │ ├── uint8_fixed_deser_np.txt │ ├── uint8_fixed_ser.txt │ ├── uint8_fixed_ser_np.txt │ ├── uint8_varlen_deser.txt │ ├── uint8_varlen_deser_np.txt │ ├── uint8_varlen_ser.txt │ └── uint8_varlen_ser_np.txt └── complex │ ├── object_ser.txt │ └── object_ser_full.txt ├── msg ├── TestFillEmbedTime.msg ├── TestFillSimple.msg ├── TestManyFields.msg ├── TestMsgArray.msg ├── TestPrimitiveArray.msg ├── TestPythonSafe.msg ├── TestPythonSafeSubfields.msg ├── TestString.msg ├── TestStringFloat.msg └── generate_test_messages.py ├── test_genpy_base.py ├── test_genpy_dynamic.py ├── test_genpy_generate_numpy.py ├── test_genpy_generate_struct.py ├── test_genpy_generator.py ├── test_genpy_message.py ├── test_genpy_python_safe.py ├── test_genpy_rostime.py └── test_genpy_rostime_truediv.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | ._* 3 | *~ 4 | src/genpy/msg 5 | -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 2 | Changelog for package genpy 3 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 4 | 5 | 0.6.18 (2025-04-25) 6 | ------------------- 7 | * Use yaml.safe_load in test (`#150 `_) 8 | * Replaced deprecated unit test aliases `#145 `_ (`#156 `_) 9 | * Contributors: Atsushi Watanabe, Tanveer Brar 10 | 11 | 0.6.17 (2025-04-10) 12 | ------------------- 13 | * Fix bug in reduce_pattern (`#152 `_) 14 | * Update maintainers (`#144 `_) 15 | * Fix typos discovered by codespell (`#142 `_) 16 | * (docs) Add automodule (`#143 `_) 17 | * Contributors: Christian Clauss, Geoffrey Biggs, Matthijs van der Burgh, bigolol 18 | 19 | 0.6.16 (2021-05-01) 20 | ------------------- 21 | * Fix runtime incompatibility with messages generated with 0.6.14 (`#139 `_) 22 | * Contributors: Shane Loretz 23 | 24 | 0.6.15 (2021-04-12) 25 | ------------------- 26 | * Update maintainers (`#135 `_) 27 | * Check for Python 3 before looking up codec (`#134 `_) 28 | * Add check for float32 and float64 to check_type (`#131 `_) 29 | * Contributors: Dirk Thomas, Martin Günther, Shane Loretz 30 | 31 | 0.6.14 (2020-08-10) 32 | ------------------- 33 | * create Struct objects to save memory (`#129 `_) 34 | 35 | 0.6.13 (2020-07-20) 36 | ------------------- 37 | * don't raise exceptions on unicode decode error (`#127 `_) 38 | 39 | 0.6.12 (2020-05-28) 40 | ------------------- 41 | * fix check_type for uint8[] to accept bytes (`#123 `_) 42 | 43 | 0.6.11 (2020-05-12) 44 | ------------------- 45 | * support non-int integral time parameters (`#121 `_) 46 | 47 | 0.6.10 (2020-01-24) 48 | ------------------- 49 | * various code cleanup (`#117 `_) 50 | * update logic for newer PyYAML output for dump() (`#116 `_) 51 | * small optimization in dynamic.py (`#109 `_) 52 | * use setuptools instead of distutils (`#115 `_) 53 | * bump CMake version to avoid CMP0048 warning (`#114 `_) 54 | * serialization: always set _x var for correct exception msg (`#113 `_) 55 | * sort generated imports to make them reproducible (`#111 `_) 56 | * make the generated "struct" constructs reproducible (`#110 `_) 57 | 58 | 0.6.9 (2019-10-03) 59 | ------------------ 60 | * fix Python 3 buffer handling (`#106 `_) 61 | * Python 3 compatibility (`#104 `_) 62 | * fix usage of undefined variables in exception (`#105 `_) 63 | * convert map iterator to list (`#103 `_) 64 | * fix negative limit check for signed ints (`#102 `_) 65 | * failing test case for UTF-8 encoded characters in comments (`#95 `_) 66 | 67 | 0.6.8 (2019-03-04) 68 | ------------------ 69 | * check size of fixed sized arrays when serializing (`#92 `_) 70 | * allow returning derived types in overloaded operators (`#100 `_) 71 | * reload() was move into importlib in Python 3 (`#98 `_) 72 | * fix _convert_getattr for handling uint8[] message fields in Python 3 (`#96 `_) 73 | 74 | 0.6.7 (2017-10-26) 75 | ------------------ 76 | * use errno to detect existing dir (`#89 `_) 77 | * fix typo (`#84 `_) 78 | 79 | 0.6.6 (2017-07-27) 80 | ------------------ 81 | * add escaping for strings which is valid in YAML (`#79 `_) 82 | * fix inefficient canon method in rostime (`#77 `_) 83 | 84 | 0.6.5 (2017-03-06) 85 | ------------------ 86 | * expose spec for dynamically generated messages (`#75 `_) 87 | 88 | 0.6.4 (2017-02-27) 89 | ------------------ 90 | * fix default value for non-primitive fixed-size arrays, regression of 0.6.1 (`#74 `) 91 | 92 | 0.6.3 (2016-10-24) 93 | ------------------ 94 | * fix Python 3 regressions (`#71 `_) 95 | * use Python safe subfields for nested types (`#69 `_) 96 | 97 | 0.6.2 (2016-09-03) 98 | ------------------ 99 | * fix regression regarding lazy init introduced in 0.6.1 (`#67 `_) 100 | 101 | 0.6.1 (2016-09-02) 102 | ------------------ 103 | * lazy init struct (`#65 `_) 104 | * fix default value of lists to not expand to N items in the generated code (`#64 `_) 105 | * simpler and more canonical hash (`#55 `_) 106 | * various improvements to the time and duration classes (`#63 `_) 107 | 108 | 0.6.0 (2016-04-21) 109 | ------------------ 110 | * change semantic of integer division for duration (`#59 `_) 111 | 112 | 0.5.9 (2016-04-19) 113 | ------------------ 114 | * warn about using floor division of durations (`#58 `_) 115 | * allow durations to be divided by other durations (`#48 `_) 116 | * avoid adding newline in msg_generator (`#47 `_, `#51 `_) 117 | 118 | 0.5.8 (2016-03-09) 119 | ------------------ 120 | 121 | * right align nsec fields of timestamps (`#45 `_) 122 | * fix order of imports in generated init files deterministic (`#44 `_) 123 | * fix exception handling code using undefined variable (`#42 `_) 124 | * add test for expected exception when serializing wrong type 125 | 126 | 0.5.7 (2015-11-09) 127 | ------------------ 128 | * add line about encoding to generated Python files (`#41 `_) 129 | 130 | 0.5.6 (2015-10-12) 131 | ------------------ 132 | * fix handling of dynamic message classes with names containing other message classes as substrings (`#40 `_) 133 | 134 | 0.5.5 (2015-09-19) 135 | ------------------ 136 | * fix handling of dynamic message classes with the same name (`#37 `_) 137 | * fix Duration.abs() when sec is zero (`#35 `_) 138 | 139 | 0.5.4 (2014-12-22) 140 | ------------------ 141 | * add support for fixed-width floating-point and integer array values (`ros/ros_comm#400 `_) 142 | * add missing test dependency on yaml 143 | 144 | 0.5.3 (2014-06-02) 145 | ------------------ 146 | * make TVal more similar to generated messages for introspection (`ros/std_msgs#6 `_) 147 | 148 | 0.5.2 (2014-05-08) 149 | ------------------ 150 | * fix usage of load_manifest() introduced in 0.5.1 (`#28 `_) 151 | 152 | 0.5.1 (2014-05-07) 153 | ------------------ 154 | * resolve message classes from dry packages (`ros/ros_comm#293 `_) 155 | * add architecture_independent flag in package.xml (`#27 `_) 156 | 157 | 0.5.0 (2014-02-25) 158 | ------------------ 159 | * use catkin_install_python() to install Python scripts (`#25 `_) 160 | 161 | 0.4.15 (2014-01-07) 162 | ------------------- 163 | * python 3 compatibility (`#22 `_) 164 | * use PYTHON_EXECUTABLE when invoking scripts for better Windows support (`#23 `_) 165 | * improve exception message when message type does not match (`#21 `_) 166 | 167 | 0.4.14 (2013-08-21) 168 | ------------------- 169 | * make genpy relocatable (`ros/catkin#490 `_) 170 | * enable int/long values for list of time/duration (`#13 `_) 171 | * fix issue with time/duration message fields (without std_msgs prefix) when used as array (`ros/ros_comm#252 `_) 172 | * fix Time() for seconds being of type long on 32-bit systems (fix `#15 `_) 173 | * fix passing keys to _fill_message_args (`#20 `_) 174 | 175 | 0.4.13 (2013-07-03) 176 | ------------------- 177 | * check for CATKIN_ENABLE_TESTING to enable configure without tests 178 | 179 | 0.4.12 (2013-06-18) 180 | ------------------- 181 | * fix deserialize bytes in Python3 (`#10 `_) 182 | 183 | 0.4.11 (2013-03-08) 184 | ------------------- 185 | * fix handling spaces in folder names (`ros/catkin#375 `_) 186 | 187 | 0.4.10 (2012-12-21) 188 | ------------------- 189 | * first public release for Groovy 190 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0.2) 2 | project(genpy) 3 | find_package(catkin REQUIRED COMPONENTS genmsg) 4 | 5 | catkin_package( 6 | CATKIN_DEPENDS genmsg 7 | CFG_EXTRAS genpy-extras.cmake 8 | ) 9 | 10 | add_subdirectory(scripts) 11 | 12 | file(WRITE ${CATKIN_DEVEL_PREFIX}/${GENMSG_LANGS_DESTINATION}/genpy "Python") 13 | install(FILES ${CATKIN_DEVEL_PREFIX}/${GENMSG_LANGS_DESTINATION}/genpy 14 | DESTINATION ${GENMSG_LANGS_DESTINATION}) 15 | 16 | catkin_python_setup() 17 | 18 | if(CATKIN_ENABLE_TESTING) 19 | assert(CATKIN_ENV) 20 | add_custom_target(generate_test_messages 21 | COMMAND 22 | "${CATKIN_ENV}" "${PYTHON_EXECUTABLE}" 23 | "${CMAKE_CURRENT_SOURCE_DIR}/test/msg/generate_test_messages.py") 24 | if(TARGET tests) 25 | add_dependencies(tests generate_test_messages) 26 | endif() 27 | 28 | catkin_add_nosetests(test DEPENDENCIES generate_test_messages) 29 | endif() 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Archived - ROS 1 End-of-life 2 | 3 | This repository contains ROS 1 packages. 4 | The last supported ROS 1 release, ROS Noetic, [officially reached end of life on May 31st, 2025](https://bit.ly/NoeticEOL). 5 | 6 | # genpy 7 | 8 | The Python ROS message and service generator. 9 | 10 | ## Examples for generating messages with dependencies 11 | 12 | ```console 13 | ./scripts/genmsg_py.py -p std_msgs -Istd_msgs:`rospack find std_msgs`/msg -o gen `rospack find std_msgs`/msg/String.msg 14 | ./scripts/genmsg_py.py -p geometry_msgs -Istd_msgs:`rospack find std_msgs`/msg -Igeometry_msgs:`rospack find geometry_msgs`/msg -o gen `rospack find geometry_msgs`/msg/PoseStamped.msg 15 | ``` 16 | -------------------------------------------------------------------------------- /cmake/genpy-extras.cmake.em: -------------------------------------------------------------------------------- 1 | @[if DEVELSPACE]@ 2 | # location of scripts in develspace 3 | set(GENPY_BIN_DIR "@(CMAKE_CURRENT_SOURCE_DIR)/scripts") 4 | @[else]@ 5 | # location of scripts in installspace 6 | set(GENPY_BIN_DIR "${genpy_DIR}/../../../@(CATKIN_PACKAGE_BIN_DESTINATION)") 7 | @[end if]@ 8 | 9 | set(GENMSG_PY_BIN ${GENPY_BIN_DIR}/genmsg_py.py) 10 | set(GENSRV_PY_BIN ${GENPY_BIN_DIR}/gensrv_py.py) 11 | 12 | # Generate .msg->.h for py 13 | # The generated .h files should be added ALL_GEN_OUTPUT_FILES_py 14 | macro(_generate_msg_py ARG_PKG ARG_MSG ARG_IFLAGS ARG_MSG_DEPS ARG_GEN_OUTPUT_DIR) 15 | 16 | #Append msg to output dir 17 | set(GEN_OUTPUT_DIR "${ARG_GEN_OUTPUT_DIR}/msg") 18 | file(MAKE_DIRECTORY ${GEN_OUTPUT_DIR}) 19 | #Create input and output filenames 20 | get_filename_component(MSG_SHORT_NAME ${ARG_MSG} NAME_WE) 21 | 22 | set(MSG_GENERATED_NAME _${MSG_SHORT_NAME}.py) 23 | set(GEN_OUTPUT_FILE ${GEN_OUTPUT_DIR}/${MSG_GENERATED_NAME}) 24 | 25 | add_custom_command(OUTPUT ${GEN_OUTPUT_FILE} 26 | DEPENDS ${GENMSG_PY_BIN} ${ARG_MSG} ${ARG_MSG_DEPS} 27 | COMMAND ${CATKIN_ENV} ${PYTHON_EXECUTABLE} ${GENMSG_PY_BIN} ${ARG_MSG} 28 | ${ARG_IFLAGS} 29 | -p ${ARG_PKG} 30 | -o ${GEN_OUTPUT_DIR} 31 | COMMENT "Generating Python from MSG ${ARG_PKG}/${MSG_SHORT_NAME}" 32 | ) 33 | 34 | list(APPEND ALL_GEN_OUTPUT_FILES_py ${GEN_OUTPUT_FILE}) 35 | 36 | endmacro() 37 | 38 | #todo, these macros are practically equal. Check for input file extension instead 39 | macro(_generate_srv_py ARG_PKG ARG_SRV ARG_IFLAGS ARG_MSG_DEPS ARG_GEN_OUTPUT_DIR) 40 | 41 | #Append msg to output dir 42 | set(GEN_OUTPUT_DIR "${ARG_GEN_OUTPUT_DIR}/srv") 43 | file(MAKE_DIRECTORY ${GEN_OUTPUT_DIR}) 44 | 45 | #Create input and output filenames 46 | get_filename_component(SRV_SHORT_NAME ${ARG_SRV} NAME_WE) 47 | 48 | set(SRV_GENERATED_NAME _${SRV_SHORT_NAME}.py) 49 | set(GEN_OUTPUT_FILE ${GEN_OUTPUT_DIR}/${SRV_GENERATED_NAME}) 50 | 51 | add_custom_command(OUTPUT ${GEN_OUTPUT_FILE} 52 | DEPENDS ${GENSRV_PY_BIN} ${ARG_SRV} ${ARG_MSG_DEPS} 53 | COMMAND ${CATKIN_ENV} ${PYTHON_EXECUTABLE} ${GENSRV_PY_BIN} ${ARG_SRV} 54 | ${ARG_IFLAGS} 55 | -p ${ARG_PKG} 56 | -o ${GEN_OUTPUT_DIR} 57 | COMMENT "Generating Python code from SRV ${ARG_PKG}/${SRV_SHORT_NAME}" 58 | ) 59 | 60 | list(APPEND ALL_GEN_OUTPUT_FILES_py ${GEN_OUTPUT_FILE}) 61 | 62 | endmacro() 63 | 64 | macro(_generate_module_py ARG_PKG ARG_GEN_OUTPUT_DIR ARG_GENERATED_FILES) 65 | 66 | # generate empty __init__ to make parent folder of msg/srv a python module 67 | if(NOT EXISTS ${ARG_GEN_OUTPUT_DIR}/__init__.py) 68 | file(WRITE ${ARG_GEN_OUTPUT_DIR}/__init__.py "") 69 | endif() 70 | 71 | #Append msg to output dir 72 | foreach(type "msg" "srv") 73 | set(GEN_OUTPUT_DIR "${ARG_GEN_OUTPUT_DIR}/${type}") 74 | set(GEN_OUTPUT_FILE ${GEN_OUTPUT_DIR}/__init__.py) 75 | 76 | if(IS_DIRECTORY ${GEN_OUTPUT_DIR}) 77 | add_custom_command(OUTPUT ${GEN_OUTPUT_FILE} 78 | DEPENDS ${GENMSG_PY_BIN} ${ARG_GENERATED_FILES} 79 | COMMAND ${CATKIN_ENV} ${PYTHON_EXECUTABLE} ${GENMSG_PY_BIN} 80 | -o ${GEN_OUTPUT_DIR} 81 | --initpy 82 | COMMENT "Generating Python ${type} __init__.py for ${ARG_PKG}") 83 | list(APPEND ALL_GEN_OUTPUT_FILES_py ${GEN_OUTPUT_FILE}) 84 | endif() 85 | 86 | endforeach() 87 | 88 | endmacro() 89 | 90 | if(NOT EXISTS @(PROJECT_NAME)_SOURCE_DIR) 91 | set(genpy_INSTALL_DIR ${PYTHON_INSTALL_DIR}) 92 | endif() 93 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | 15 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest 16 | 17 | help: 18 | @echo "Please use \`make ' where is one of" 19 | @echo " html to make standalone HTML files" 20 | @echo " dirhtml to make HTML files named index.html in directories" 21 | @echo " singlehtml to make a single large HTML file" 22 | @echo " pickle to make pickle files" 23 | @echo " json to make JSON files" 24 | @echo " htmlhelp to make HTML files and a HTML help project" 25 | @echo " qthelp to make HTML files and a qthelp project" 26 | @echo " devhelp to make HTML files and a Devhelp project" 27 | @echo " epub to make an epub" 28 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 29 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 30 | @echo " text to make text files" 31 | @echo " man to make manual pages" 32 | @echo " changes to make an overview of all changed/added/deprecated items" 33 | @echo " linkcheck to check all external links for integrity" 34 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 35 | 36 | clean: 37 | -rm -rf $(BUILDDIR)/* 38 | 39 | html: 40 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 41 | @echo 42 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 43 | 44 | dirhtml: 45 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 48 | 49 | singlehtml: 50 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 51 | @echo 52 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 53 | 54 | pickle: 55 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 56 | @echo 57 | @echo "Build finished; now you can process the pickle files." 58 | 59 | json: 60 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 61 | @echo 62 | @echo "Build finished; now you can process the JSON files." 63 | 64 | htmlhelp: 65 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 66 | @echo 67 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 68 | ".hhp project file in $(BUILDDIR)/htmlhelp." 69 | 70 | qthelp: 71 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 72 | @echo 73 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 74 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 75 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/genpy.qhcp" 76 | @echo "To view the help file:" 77 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/genpy.qhc" 78 | 79 | devhelp: 80 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 81 | @echo 82 | @echo "Build finished." 83 | @echo "To view the help file:" 84 | @echo "# mkdir -p $$HOME/.local/share/devhelp/genpy" 85 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/genpy" 86 | @echo "# devhelp" 87 | 88 | epub: 89 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 90 | @echo 91 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 92 | 93 | latex: 94 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 95 | @echo 96 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 97 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 98 | "(use \`make latexpdf' here to do that automatically)." 99 | 100 | latexpdf: 101 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 102 | @echo "Running LaTeX files through pdflatex..." 103 | make -C $(BUILDDIR)/latex all-pdf 104 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 105 | 106 | text: 107 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 108 | @echo 109 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 110 | 111 | man: 112 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 113 | @echo 114 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 115 | 116 | changes: 117 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 118 | @echo 119 | @echo "The overview file is in $(BUILDDIR)/changes." 120 | 121 | linkcheck: 122 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 123 | @echo 124 | @echo "Link check complete; look for any errors in the above output " \ 125 | "or in $(BUILDDIR)/linkcheck/output.txt." 126 | 127 | doctest: 128 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 129 | @echo "Testing of doctests in the sources finished, look at the " \ 130 | "results in $(BUILDDIR)/doctest/output.txt." 131 | -------------------------------------------------------------------------------- /doc/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # genpy documentation build configuration file, created by 4 | # sphinx-quickstart on Thu Oct 6 14:10:15 2011. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | #sys.path.insert(0, os.path.abspath('.')) 20 | 21 | # -- General configuration ----------------------------------------------------- 22 | 23 | # If your documentation needs a minimal Sphinx version, state it here. 24 | #needs_sphinx = '1.0' 25 | 26 | # Add any Sphinx extension module names here, as strings. They can be extensions 27 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 28 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.viewcode'] 29 | 30 | # Add any paths that contain templates here, relative to this directory. 31 | templates_path = ['_templates'] 32 | 33 | # The suffix of source filenames. 34 | source_suffix = '.rst' 35 | 36 | # The encoding of source files. 37 | #source_encoding = 'utf-8-sig' 38 | 39 | # The master toctree document. 40 | master_doc = 'index' 41 | 42 | # General information about the project. 43 | project = u'genpy' 44 | copyright = u'2011, Ken Conley' 45 | 46 | # The version info for the project you're documenting, acts as replacement for 47 | # |version| and |release|, also used in various other places throughout the 48 | # built documents. 49 | # 50 | # The short X.Y version. 51 | version = '0.1.0' 52 | # The full version, including alpha/beta/rc tags. 53 | release = '0.1.0' 54 | 55 | # The language for content autogenerated by Sphinx. Refer to documentation 56 | # for a list of supported languages. 57 | #language = None 58 | 59 | # There are two options for replacing |today|: either, you set today to some 60 | # non-false value, then it is used: 61 | #today = '' 62 | # Else, today_fmt is used as the format for a strftime call. 63 | #today_fmt = '%B %d, %Y' 64 | 65 | # List of patterns, relative to source directory, that match files and 66 | # directories to ignore when looking for source files. 67 | exclude_patterns = ['_build'] 68 | 69 | # The reST default role (used for this markup: `text`) to use for all documents. 70 | #default_role = None 71 | 72 | # If true, '()' will be appended to :func: etc. cross-reference text. 73 | #add_function_parentheses = True 74 | 75 | # If true, the current module name will be prepended to all description 76 | # unit titles (such as .. function::). 77 | #add_module_names = True 78 | 79 | # If true, sectionauthor and moduleauthor directives will be shown in the 80 | # output. They are ignored by default. 81 | #show_authors = False 82 | 83 | # The name of the Pygments (syntax highlighting) style to use. 84 | pygments_style = 'sphinx' 85 | 86 | # A list of ignored prefixes for module index sorting. 87 | #modindex_common_prefix = [] 88 | 89 | 90 | # -- Options for HTML output --------------------------------------------------- 91 | 92 | # The theme to use for HTML and HTML Help pages. See the documentation for 93 | # a list of builtin themes. 94 | html_theme = 'default' 95 | 96 | # Theme options are theme-specific and customize the look and feel of a theme 97 | # further. For a list of options available for each theme, see the 98 | # documentation. 99 | #html_theme_options = {} 100 | 101 | # Add any paths that contain custom themes here, relative to this directory. 102 | #html_theme_path = [] 103 | 104 | # The name for this set of Sphinx documents. If None, it defaults to 105 | # " v documentation". 106 | #html_title = None 107 | 108 | # A shorter title for the navigation bar. Default is the same as html_title. 109 | #html_short_title = None 110 | 111 | # The name of an image file (relative to this directory) to place at the top 112 | # of the sidebar. 113 | #html_logo = None 114 | 115 | # The name of an image file (within the static path) to use as favicon of the 116 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 117 | # pixels large. 118 | #html_favicon = None 119 | 120 | # Add any paths that contain custom static files (such as style sheets) here, 121 | # relative to this directory. They are copied after the builtin static files, 122 | # so a file named "default.css" will overwrite the builtin "default.css". 123 | html_static_path = [] 124 | 125 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 126 | # using the given strftime format. 127 | #html_last_updated_fmt = '%b %d, %Y' 128 | 129 | # If true, SmartyPants will be used to convert quotes and dashes to 130 | # typographically correct entities. 131 | #html_use_smartypants = True 132 | 133 | # Custom sidebar templates, maps document names to template names. 134 | #html_sidebars = {} 135 | 136 | # Additional templates that should be rendered to pages, maps page names to 137 | # template names. 138 | #html_additional_pages = {} 139 | 140 | # If false, no module index is generated. 141 | #html_domain_indices = True 142 | 143 | # If false, no index is generated. 144 | #html_use_index = True 145 | 146 | # If true, the index is split into individual pages for each letter. 147 | #html_split_index = False 148 | 149 | # If true, links to the reST sources are added to the pages. 150 | #html_show_sourcelink = True 151 | 152 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 153 | #html_show_sphinx = True 154 | 155 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 156 | #html_show_copyright = True 157 | 158 | # If true, an OpenSearch description file will be output, and all pages will 159 | # contain a tag referring to it. The value of this option must be the 160 | # base URL from which the finished HTML is served. 161 | #html_use_opensearch = '' 162 | 163 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 164 | #html_file_suffix = None 165 | 166 | # Output file base name for HTML help builder. 167 | htmlhelp_basename = 'genpydoc' 168 | 169 | 170 | # -- Options for LaTeX output -------------------------------------------------- 171 | 172 | # The paper size ('letter' or 'a4'). 173 | #latex_paper_size = 'letter' 174 | 175 | # The font size ('10pt', '11pt' or '12pt'). 176 | #latex_font_size = '10pt' 177 | 178 | # Grouping the document tree into LaTeX files. List of tuples 179 | # (source start file, target name, title, author, documentclass [howto/manual]). 180 | latex_documents = [ 181 | ('index', 'genpy.tex', u'genpy Documentation', 182 | u'Ken Conley', 'manual'), 183 | ] 184 | 185 | # The name of an image file (relative to this directory) to place at the top of 186 | # the title page. 187 | #latex_logo = None 188 | 189 | # For "manual" documents, if this is true, then toplevel headings are parts, 190 | # not chapters. 191 | #latex_use_parts = False 192 | 193 | # If true, show page references after internal links. 194 | #latex_show_pagerefs = False 195 | 196 | # If true, show URL addresses after external links. 197 | #latex_show_urls = False 198 | 199 | # Additional stuff for the LaTeX preamble. 200 | #latex_preamble = '' 201 | 202 | # Documents to append as an appendix to all manuals. 203 | #latex_appendices = [] 204 | 205 | # If false, no module index is generated. 206 | #latex_domain_indices = True 207 | 208 | 209 | # -- Options for manual page output -------------------------------------------- 210 | 211 | # One entry per manual page. List of tuples 212 | # (source start file, name, description, authors, manual section). 213 | man_pages = [ 214 | ('index', 'genpy', u'genpy Documentation', 215 | [u'Ken Conley'], 1) 216 | ] 217 | 218 | 219 | # Example configuration for intersphinx: refer to the Python standard library. 220 | intersphinx_mapping = {'http://docs.python.org/': None} 221 | -------------------------------------------------------------------------------- /doc/index.rst: -------------------------------------------------------------------------------- 1 | .. genpy documentation master file, created by 2 | sphinx-quickstart on Thu Oct 6 14:10:15 2011. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to genpy's documentation! 7 | ================================= 8 | 9 | Python ROS message and service generators. 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | Indices and tables 15 | ================== 16 | 17 | * :ref:`genindex` 18 | * :ref:`modindex` 19 | * :ref:`search` 20 | 21 | .. automodule:: genpy 22 | :members: 23 | :undoc-members: 24 | 25 | -------------------------------------------------------------------------------- /package.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | genpy 7 | 0.6.18 8 | Python ROS message and service generators. 9 | Geoffrey Biggs 10 | BSD 11 | 12 | http://wiki.ros.org/genpy 13 | https://github.com/ros/genpy/issues 14 | https://github.com/ros/genpy 15 | 16 | Ken Conley 17 | Troy Straszheim 18 | Morten Kjaergaard 19 | Dirk Thomas 20 | Mabel Zhang 21 | Shane Loretz 22 | 23 | genmsg 24 | 25 | catkin 26 | python-setuptools 27 | python3-setuptools 28 | 29 | python-yaml 30 | python3-yaml 31 | 32 | python-numpy 33 | python3-numpy 34 | 35 | 36 | py 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /rosdoc.yaml: -------------------------------------------------------------------------------- 1 | - builder: sphinx 2 | sphinx_root_dir: doc 3 | -------------------------------------------------------------------------------- /scripts/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | catkin_install_python( 2 | PROGRAMS genmsg_py.py gensrv_py.py 3 | DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}) 4 | -------------------------------------------------------------------------------- /scripts/genmsg_py.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Software License Agreement (BSD License) 3 | # 4 | # Copyright (c) 2008, Willow Garage, Inc. 5 | # All rights reserved. 6 | # 7 | # Redistribution and use in source and binary forms, with or without 8 | # modification, are permitted provided that the following conditions 9 | # are met: 10 | # 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above 14 | # copyright notice, this list of conditions and the following 15 | # disclaimer in the documentation and/or other materials provided 16 | # with the distribution. 17 | # * Neither the name of Willow Garage, Inc. nor the names of its 18 | # contributors may be used to endorse or promote products derived 19 | # from this software without specific prior written permission. 20 | # 21 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 24 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 25 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 26 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 27 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 28 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 29 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 31 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 32 | # POSSIBILITY OF SUCH DAMAGE. 33 | # 34 | # Revision $Id: genmsg_py.py 9002 2010-04-09 01:08:47Z kwc $ 35 | 36 | """ 37 | ROS message source code generation for Python. 38 | 39 | Converts ROS .msg files in a package into Python source code implementations. 40 | """ 41 | import sys 42 | 43 | import genpy.generator 44 | import genpy.genpy_main 45 | 46 | if __name__ == '__main__': 47 | genpy.genpy_main.genmain(sys.argv, 'genmsg_py.py', genpy.generator.MsgGenerator()) 48 | -------------------------------------------------------------------------------- /scripts/gensrv_py.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Software License Agreement (BSD License) 3 | # 4 | # Copyright (c) 2008, Willow Garage, Inc. 5 | # All rights reserved. 6 | # 7 | # Redistribution and use in source and binary forms, with or without 8 | # modification, are permitted provided that the following conditions 9 | # are met: 10 | # 11 | # * Redistributions of source code must retain the above copyright 12 | # notice, this list of conditions and the following disclaimer. 13 | # * Redistributions in binary form must reproduce the above 14 | # copyright notice, this list of conditions and the following 15 | # disclaimer in the documentation and/or other materials provided 16 | # with the distribution. 17 | # * Neither the name of Willow Garage, Inc. nor the names of its 18 | # contributors may be used to endorse or promote products derived 19 | # from this software without specific prior written permission. 20 | # 21 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 24 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 25 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 26 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 27 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 28 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 29 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 31 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 32 | # POSSIBILITY OF SUCH DAMAGE. 33 | 34 | """ 35 | ROS service source code generation for Python. 36 | 37 | Converts ROS .srv files into Python source code implementations. 38 | """ 39 | 40 | import os 41 | import sys 42 | 43 | import genpy.generator 44 | import genpy.genpy_main 45 | 46 | if __name__ == "__main__": 47 | genpy.genpy_main.genmain(sys.argv, 'gensrv_py.py', genpy.generator.SrvGenerator()) 48 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from catkin_pkg.python_setup import generate_distutils_setup 2 | 3 | from setuptools import setup 4 | 5 | d = generate_distutils_setup( 6 | packages=['genpy'], 7 | package_dir={'': 'src'}, 8 | requires=['genmsg'] 9 | ) 10 | 11 | setup(**d) 12 | -------------------------------------------------------------------------------- /src/genpy/__init__.py: -------------------------------------------------------------------------------- 1 | # Software License Agreement (BSD License) 2 | # 3 | # Copyright (c) 2011, Willow Garage, Inc. 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions 8 | # are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # * Redistributions in binary form must reproduce the above 13 | # copyright notice, this list of conditions and the following 14 | # disclaimer in the documentation and/or other materials provided 15 | # with the distribution. 16 | # * Neither the name of Willow Garage, Inc. nor the names of its 17 | # contributors may be used to endorse or promote products derived 18 | # from this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 23 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 24 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 25 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 26 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 30 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 31 | # POSSIBILITY OF SUCH DAMAGE. 32 | 33 | from . rostime import Time, Duration, TVal 34 | from . message import Message, SerializationError, DeserializationError, MessageException, struct_I 35 | 36 | __all__ = [ 37 | 'Time', 'Duration', 'TVal', 38 | 'Message', 'SerializationError', 'DeserializationError', 'MessageException', 'struct_I'] 39 | -------------------------------------------------------------------------------- /src/genpy/base.py: -------------------------------------------------------------------------------- 1 | # Software License Agreement (BSD License) 2 | # 3 | # Copyright (c) 2008, Willow Garage, Inc. 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions 8 | # are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # * Redistributions in binary form must reproduce the above 13 | # copyright notice, this list of conditions and the following 14 | # disclaimer in the documentation and/or other materials provided 15 | # with the distribution. 16 | # * Neither the name of Willow Garage, Inc. nor the names of its 17 | # contributors may be used to endorse or promote products derived 18 | # from this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 23 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 24 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 25 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 26 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 30 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 31 | # POSSIBILITY OF SUCH DAMAGE. 32 | 33 | ################################################################################ 34 | # Primitive type handling for ROS builtin types 35 | 36 | SIMPLE_TYPES_DICT = { # see python module struct 37 | 'int8': 'b', 38 | 'uint8': 'B', 39 | # Python 2.6 adds in '?' for C99 _Bool, which appears equivalent to an uint8, 40 | # thus, we use uint8 41 | 'bool': 'B', 42 | 'int16': 'h', 43 | 'uint16': 'H', 44 | 'int32': 'i', 45 | 'uint32': 'I', 46 | 'int64': 'q', 47 | 'uint64': 'Q', 48 | 'float32': 'f', 49 | 'float64': 'd', 50 | # deprecated 51 | 'char': 'B', # unsigned 52 | 'byte': 'b', # signed 53 | } 54 | 55 | # Simple types are primitives with fixed-serialization length 56 | SIMPLE_TYPES = list(SIMPLE_TYPES_DICT.keys()) # py3k 57 | 58 | 59 | def is_simple(type_): 60 | """ 61 | Check if a type is a 'simple' type. 62 | 63 | :returns: ``True`` if type is a 'simple' type, i.e. is of 64 | fixed/known serialization length. This is effectively all primitive 65 | types except for string, ``bool`` 66 | """ 67 | return type_ in SIMPLE_TYPES 68 | -------------------------------------------------------------------------------- /src/genpy/dynamic.py: -------------------------------------------------------------------------------- 1 | # Software License Agreement (BSD License) 2 | # 3 | # Copyright (c) 2008, Willow Garage, Inc. 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions 8 | # are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # * Redistributions in binary form must reproduce the above 13 | # copyright notice, this list of conditions and the following 14 | # disclaimer in the documentation and/or other materials provided 15 | # with the distribution. 16 | # * Neither the name of Willow Garage, Inc. nor the names of its 17 | # contributors may be used to endorse or promote products derived 18 | # from this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 23 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 24 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 25 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 26 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 30 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 31 | # POSSIBILITY OF SUCH DAMAGE. 32 | 33 | """Dynamic generation of deserializer.""" 34 | 35 | from __future__ import print_function 36 | 37 | try: 38 | from cStringIO import StringIO # Python 2.x 39 | except ImportError: 40 | from io import StringIO # Python 3.x 41 | 42 | import atexit 43 | import os 44 | import re 45 | import shutil 46 | import sys 47 | import tempfile 48 | 49 | import genmsg 50 | import genmsg.msg_loader 51 | from genmsg import MsgContext, MsgGenerationException 52 | 53 | from . generator import msg_generator 54 | 55 | 56 | def _generate_dynamic_specs(msg_context, specs, dep_msg): 57 | """ 58 | Dynamically generate message specificition. 59 | 60 | :param dep_msg: text of dependent .msg definition, ``str`` 61 | :returns: type name, message spec, ``str, MsgSpec`` 62 | :raises: MsgGenerationException If dep_msg is improperly formatted 63 | """ 64 | line1 = dep_msg.find('\n') 65 | msg_line = dep_msg[:line1] 66 | if not msg_line.startswith('MSG: '): 67 | raise MsgGenerationException("invalid input to generate_dynamic: dependent type is missing 'MSG:' type declaration header") 68 | dep_type = msg_line[5:].strip() 69 | dep_pkg, dep_base_type = genmsg.package_resource_name(dep_type) 70 | dep_spec = genmsg.msg_loader.load_msg_from_string(msg_context, dep_msg[line1+1:], dep_type) 71 | return dep_type, dep_spec 72 | 73 | 74 | def _gen_dyn_name(pkg, base_type): 75 | """ 76 | Modify pkg/base_type name so that it can safely co-exist with statically generated files. 77 | 78 | :returns: name to use for pkg/base_type for dynamically generated message class. 79 | @rtype: str 80 | """ 81 | return '_%s__%s' % (pkg, base_type) 82 | 83 | 84 | def _gen_dyn_modify_references(py_text, current_type, types): 85 | """ 86 | Modify the generated code to rewrite names such that the code can safely co-exist with messages of the same name. 87 | 88 | :param py_text: genmsg_py-generated Python source code, ``str`` 89 | :returns: updated text, ``str`` 90 | """ 91 | for t in types: 92 | pkg, base_type = genmsg.package_resource_name(t) 93 | gen_name = _gen_dyn_name(pkg, base_type) 94 | 95 | # Several things we have to rewrite: 96 | # - remove any import statements 97 | py_text = py_text.replace('import %s.msg' % pkg, '') 98 | # - rewrite any references to class 99 | if '%s.msg.%s' % (pkg, base_type) in py_text: 100 | # only call expensive re.sub if the class name is in the string 101 | py_text = re.sub(r'(? 1: 86 | new_pattern = new_pattern + str(count) + prev 87 | else: 88 | new_pattern = new_pattern + prev 89 | prev = c 90 | count = 1 91 | if count > 1: 92 | new_pattern = new_pattern + str(count) + c 93 | else: 94 | new_pattern = new_pattern + prev 95 | return new_pattern 96 | 97 | 98 | def serialize(expr): 99 | """ 100 | Return code to write expression to buffer. 101 | 102 | :param expr str: string python expression that is evaluated for serialization 103 | :returns str: python call to write value returned by expr to serialization buffer 104 | """ 105 | return 'buff.write(%s)' % expr 106 | 107 | 108 | # int32 is very common due to length serialization, so it is special cased 109 | def int32_pack(var): 110 | """ 111 | Pack int32. 112 | 113 | :param var: variable name, ``str`` 114 | :returns: struct packing code for an int32 115 | """ 116 | return serialize('_struct_I.pack(%s)' % var) 117 | 118 | 119 | # int32 is very common due to length serialization, so it is special cased 120 | def int32_unpack(var, buff): 121 | """ 122 | Unpack int32. 123 | 124 | :param var: variable name, ``str`` 125 | :returns: struct unpacking code for an int32 126 | """ 127 | return '(%s,) = _struct_I.unpack(%s)' % (var, buff) 128 | 129 | 130 | # NOTE: '<' = little endian 131 | def pack(pattern, vars_): 132 | """ 133 | Create struct.pack call for when pattern is a string pattern. 134 | 135 | :param pattern: pattern for pack, ``str`` 136 | :param vars_: name of variables to pack, ``str`` 137 | """ 138 | # - store pattern in context 139 | pattern = reduce_pattern(pattern) 140 | add_pattern(pattern) 141 | return serialize('_get_struct_%s().pack(%s)' % (pattern, vars_)) 142 | 143 | 144 | def pack2(pattern, vars_): 145 | """ 146 | Create struct.pack call for when pattern is the name of a variable. 147 | 148 | :param pattern: name of variable storing string pattern, ``struct`` 149 | :param vars_: name of variables to pack, ``str`` 150 | """ 151 | return serialize('struct.Struct(%s).pack(%s)' % (pattern, vars_)) 152 | 153 | 154 | def unpack(var, pattern, buff): 155 | """ 156 | Create struct.unpack call for when pattern is a string pattern. 157 | 158 | :param var: name of variable to unpack, ``str`` 159 | :param pattern: pattern for pack, ``str`` 160 | :param buff: buffer to unpack from, ``str`` 161 | """ 162 | # - store pattern in context 163 | pattern = reduce_pattern(pattern) 164 | add_pattern(pattern) 165 | return var + ' = _get_struct_%s().unpack(%s)' % (pattern, buff) 166 | 167 | 168 | def unpack2(var, pattern, buff): 169 | """ 170 | Create struct.unpack call for when pattern refers to variable. 171 | 172 | :param var: variable the stores the result of unpack call, ``str`` 173 | :param pattern: name of variable that unpack will read from, ``str`` 174 | :param buff: buffer that the unpack reads from, ``StringIO`` 175 | """ 176 | return '%s = struct.unpack(%s, %s)' % (var, pattern, buff) 177 | 178 | 179 | def unpack3(var, struct_var, buff): 180 | """ 181 | Create an unpack call on the ``struct.Struct`` object with the name 182 | ``struct_var``. 183 | 184 | :param var: variable the stores the result of unpack call, ``str`` 185 | :param str struct_var: name of the struct variable used to unpack ``buff`` 186 | :param buff: buffer that the unpack reads from, ``StringIO`` 187 | """ 188 | return '%s = %s.unpack(%s)' % (var, struct_var, buff) 189 | -------------------------------------------------------------------------------- /src/genpy/genpy_main.py: -------------------------------------------------------------------------------- 1 | # Software License Agreement (BSD License) 2 | # 3 | # Copyright (c) 2008, Willow Garage, Inc. 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions 8 | # are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # * Redistributions in binary form must reproduce the above 13 | # copyright notice, this list of conditions and the following 14 | # disclaimer in the documentation and/or other materials provided 15 | # with the distribution. 16 | # * Neither the name of Willow Garage, Inc. nor the names of its 17 | # contributors may be used to endorse or promote products derived 18 | # from this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 23 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 24 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 25 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 26 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 30 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 31 | # POSSIBILITY OF SUCH DAMAGE. 32 | 33 | from __future__ import print_function 34 | 35 | 36 | import os 37 | import sys 38 | import traceback 39 | from optparse import OptionParser 40 | 41 | import genmsg 42 | import genmsg.command_line 43 | from genmsg import MsgGenerationException 44 | 45 | from . generate_initpy import write_modules 46 | 47 | 48 | def usage(progname): 49 | print('%(progname)s file(s)' % vars()) 50 | 51 | 52 | def genmain(argv, progname, gen): 53 | parser = OptionParser('%s file' % (progname)) 54 | parser.add_option('--initpy', dest='initpy', action='store_true', 55 | default=False) 56 | parser.add_option('-p', dest='package') 57 | parser.add_option('-o', dest='outdir') 58 | parser.add_option('-I', dest='includepath', action='append') 59 | options, args = parser.parse_args(argv) 60 | try: 61 | if options.initpy: 62 | if options.outdir: 63 | retcode = write_modules(options.outdir) 64 | else: 65 | parser.error('Missing args') 66 | else: 67 | if len(args) < 2: 68 | parser.error('please specify args') 69 | if not os.path.exists(options.outdir): 70 | # This script can be run multiple times in parallel. We 71 | # don't mind if the makedirs call fails because somebody 72 | # else snuck in and created the directory before us. 73 | try: 74 | os.makedirs(options.outdir) 75 | except OSError: 76 | if not os.path.exists(options.outdir): 77 | raise 78 | search_path = genmsg.command_line.includepath_to_dict(options.includepath) 79 | retcode = gen.generate_messages(options.package, args[1:], options.outdir, search_path) 80 | except genmsg.InvalidMsgSpec as e: 81 | print('ERROR: ', e, file=sys.stderr) 82 | retcode = 1 83 | except MsgGenerationException as e: 84 | print('ERROR: ', e, file=sys.stderr) 85 | retcode = 2 86 | except Exception as e: 87 | traceback.print_exc() 88 | print('ERROR: ', e) 89 | retcode = 3 90 | sys.exit(retcode or 0) 91 | -------------------------------------------------------------------------------- /src/genpy/message.py: -------------------------------------------------------------------------------- 1 | # Software License Agreement (BSD License) 2 | # 3 | # Copyright (c) 2008, Willow Garage, Inc. 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions 8 | # are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # * Redistributions in binary form must reproduce the above 13 | # copyright notice, this list of conditions and the following 14 | # disclaimer in the documentation and/or other materials provided 15 | # with the distribution. 16 | # * Neither the name of Willow Garage, Inc. nor the names of its 17 | # contributors may be used to endorse or promote products derived 18 | # from this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 23 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 24 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 25 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 26 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 30 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 31 | # POSSIBILITY OF SUCH DAMAGE. 32 | 33 | """ 34 | Support library for Python autogenerated message files. 35 | 36 | This defines the Message base class used by genpy as well as support 37 | libraries for type checking and retrieving message classes by type name. 38 | """ 39 | 40 | import codecs 41 | import itertools 42 | import math 43 | import struct 44 | import sys 45 | 46 | import genmsg 47 | 48 | import yaml 49 | 50 | from .base import is_simple 51 | from .rostime import Duration 52 | from .rostime import TVal 53 | from .rostime import Time 54 | 55 | try: 56 | reload # Python 2 57 | except NameError: # Python 3 58 | from importlib import reload 59 | 60 | if sys.version > '3': 61 | long = int 62 | 63 | try: 64 | import numpy as np 65 | _valid_float_types = [float, int, long, np.float32, np.float64, np.int8, np.int16, np.int32, np.int64, np.uint8, 66 | np.uint16, np.uint32, np.uint64] 67 | except ImportError: 68 | _valid_float_types = [float, int, long] 69 | 70 | # common struct pattern singletons for msgs to use. Although this 71 | # would better placed in a generator-specific module, we don't want to 72 | # add another import to messages (which incurs higher import cost) 73 | struct_I = struct.Struct('iter(str)`` 137 | :returns: string (YAML) representation of message, ``str`` 138 | """ 139 | type_ = type(val) 140 | if type_ in (int, long, float) and fixed_numeric_width is not None: 141 | if type_ is float: 142 | num_str = ('%.' + str(fixed_numeric_width) + 'f') % val 143 | return num_str[:max(num_str.find('.'), fixed_numeric_width)] 144 | else: 145 | return ('%' + str(fixed_numeric_width) + 'd') % val 146 | elif type_ in (int, long, float, bool): 147 | return str(val) 148 | elif isstring(val): 149 | if not val: 150 | return "''" 151 | # escape strings for use in yaml file using yaml dump with default style to avoid trailing "...\n" 152 | return yaml.dump(val, default_style='"').rstrip('\n') 153 | elif isinstance(val, TVal): 154 | 155 | if time_offset is not None and isinstance(val, Time): 156 | val = val-time_offset 157 | 158 | if fixed_numeric_width is not None: 159 | format_str = '%' + str(fixed_numeric_width) + 'd' 160 | sec_str = '\n%ssecs: ' % indent + (format_str % val.secs) 161 | nsec_str = '\n%snsecs: ' % indent + (format_str % val.nsecs) 162 | return sec_str + nsec_str 163 | else: 164 | return '\n%ssecs: %s\n%snsecs: %9d' % (indent, val.secs, indent, val.nsecs) 165 | 166 | elif type_ in (list, tuple): 167 | if len(val) == 0: 168 | return '[]' 169 | val0 = val[0] 170 | if type(val0) in (int, float) and fixed_numeric_width is not None: 171 | list_str = '[' + ''.join(strify_message(v, indent, time_offset, current_time, field_filter, fixed_numeric_width) + ', ' for v in val).rstrip(', ') + ']' 172 | return list_str 173 | elif isstring(val0): 174 | # escape list of strings for use in yaml file using yaml dump 175 | yaml_str = yaml.dump(val).rstrip('\n') 176 | # earlier versions of PyYAML return: ['', '']\n 177 | # newer versions of PyYaml return: - ''\n- ''\n 178 | assert yaml_str[0] in ('[', '-') 179 | if yaml_str[0] == '[': 180 | return yaml_str 181 | return '\n' + '\n'.join(indent + line for line in yaml_str.splitlines()) 182 | elif type(val0) in (int, float, bool): 183 | return str(list(val)) 184 | else: 185 | pref = indent + '- ' 186 | indent = indent + ' ' 187 | return '\n'+'\n'.join([pref+strify_message(v, indent, time_offset, current_time, field_filter, fixed_numeric_width) for v in val]) 188 | elif isinstance(val, Message): 189 | # allow caller to select which fields of message are strified 190 | if field_filter is not None: 191 | fields = list(field_filter(val)) 192 | else: 193 | fields = val.__slots__ 194 | 195 | p = '%s%%s: %%s' % (indent) 196 | ni = ' '+indent 197 | if sys.hexversion > 0x03000000: # Python3 198 | vals = '\n'.join([ 199 | p % (f, strify_message(_convert_getattr(val, f, t), ni, time_offset, current_time, field_filter, fixed_numeric_width)) 200 | for f, t in zip(val.__slots__, val._slot_types) if f in fields]) 201 | else: # Python2 202 | vals = '\n'.join([ 203 | p % (f, strify_message(_convert_getattr(val, f, t), ni, time_offset, current_time, field_filter, fixed_numeric_width)) 204 | for f, t in itertools.izip(val.__slots__, val._slot_types) if f in fields]) 205 | if indent: 206 | return '\n'+vals 207 | else: 208 | return vals 209 | 210 | else: 211 | return str(val) # punt 212 | 213 | 214 | def _convert_getattr(val, f, t): 215 | """ 216 | Convert attribute types on the fly, if necessary. 217 | 218 | This is mainly to convert uint8[] fields back to an array type. 219 | """ 220 | attr = getattr(val, f) 221 | if isstring(attr) and 'uint8[' in t: 222 | return [ord(x) for x in attr] 223 | elif isinstance(attr, bytes) and 'uint8[' in t: 224 | return list(attr) 225 | else: 226 | return attr 227 | 228 | 229 | # check_type mildly violates some abstraction boundaries between .msg 230 | # representation and the python Message representation. The 231 | # alternative is to have the message generator map .msg types to 232 | # python types beforehand, but that would make it harder to do 233 | # width/signed checks. 234 | 235 | _widths = { 236 | 'byte': 8, 'char': 8, 'int8': 8, 'uint8': 8, 237 | 'int16': 16, 'uint16': 16, 238 | 'int32': 32, 'uint32': 32, 239 | 'int64': 64, 'uint64': 64, 240 | } 241 | 242 | 243 | def check_type(field_name, field_type, field_val): 244 | """ 245 | Dynamic type checker that maps ROS .msg types to python types and verifies the python value. 246 | 247 | check_type() is not designed to be fast and is targeted at error diagnosis. 248 | This type checker is not designed to run fast and is meant only for error 249 | diagnosis. 250 | 251 | :param field_name: ROS .msg field name, ``str`` 252 | :param field_type: ROS .msg field type, ``str`` 253 | :param field_val: field value, ``Any`` 254 | :raises: :exc:`SerializationError` If typecheck fails 255 | """ 256 | if is_simple(field_type): 257 | # check sign and width 258 | if field_type in ['byte', 'int8', 'int16', 'int32', 'int64']: 259 | if type(field_val) not in [long, int]: 260 | raise SerializationError('field %s must be an integer type' % field_name) 261 | maxval = int(math.pow(2, _widths[field_type]-1)) 262 | if field_val >= maxval or field_val < -maxval: 263 | raise SerializationError('field %s exceeds specified width [%s]' % (field_name, field_type)) 264 | elif field_type in ['char', 'uint8', 'uint16', 'uint32', 'uint64']: 265 | if type(field_val) not in [long, int] or field_val < 0: 266 | raise SerializationError('field %s must be unsigned integer type' % field_name) 267 | maxval = int(math.pow(2, _widths[field_type])) 268 | if field_val >= maxval: 269 | raise SerializationError('field %s exceeds specified width [%s]' % (field_name, field_type)) 270 | elif field_type in ['float32', 'float64']: 271 | if type(field_val) not in _valid_float_types: 272 | raise SerializationError('field %s must be float type' % field_name) 273 | elif field_type == 'bool': 274 | if field_val not in [True, False, 0, 1]: 275 | raise SerializationError('field %s is not a bool' % (field_name)) 276 | elif field_type == 'string': 277 | if sys.hexversion > 0x03000000: 278 | if type(field_val) == str: 279 | try: 280 | field_val.encode('ascii') 281 | except UnicodeEncodeError: 282 | raise SerializationError('field %s is a non-ascii string' % field_name) 283 | elif not type(field_val) == bytes: 284 | raise SerializationError('field %s must be of type bytes or an ascii string' % field_name) 285 | else: 286 | if type(field_val) == unicode: # noqa: F821 287 | raise SerializationError('field %s is a unicode string instead of an ascii string' % field_name) 288 | elif not isstring(field_val): 289 | raise SerializationError('field %s must be of type str' % field_name) 290 | elif field_type == 'time': 291 | if not isinstance(field_val, Time): 292 | raise SerializationError('field %s must be of type Time' % field_name) 293 | elif field_type == 'duration': 294 | if not isinstance(field_val, Duration): 295 | raise SerializationError('field %s must be of type Duration' % field_name) 296 | 297 | elif field_type.endswith(']'): # array type 298 | # use index to generate error if '[' not present 299 | base_type = field_type[:field_type.index('[')] 300 | 301 | if type(field_val) in (bytes, str): 302 | if base_type not in ['char', 'uint8']: 303 | raise SerializationError('field %s must be a list or tuple type. Only uint8[] can be a string' % field_name) 304 | else: 305 | # It's a string so its already in byte format and we 306 | # don't need to check the individual bytes in the 307 | # string. 308 | return 309 | 310 | if not type(field_val) in [list, tuple]: 311 | raise SerializationError('field %s must be a list or tuple type' % field_name) 312 | for v in field_val: 313 | check_type(field_name + '[]', base_type, v) 314 | else: 315 | if isinstance(field_val, Message): 316 | # roslib/Header is the old location of Header. We check it for backwards compat 317 | if field_val._type in ['std_msgs/Header', 'roslib/Header']: 318 | if field_type not in ['Header', 'std_msgs/Header', 'roslib/Header']: 319 | raise SerializationError('field %s must be a Header instead of a %s' % (field_name, field_val._type)) 320 | elif field_val._type != field_type: 321 | raise SerializationError('field %s must be of type %s instead of %s' % (field_name, field_type, field_val._type)) 322 | for n, t in zip(field_val.__slots__, field_val._get_types()): 323 | check_type('%s.%s' % (field_name, n), t, getattr(field_val, n)) 324 | else: 325 | raise SerializationError('field %s must be of type [%s]' % (field_name, field_type)) 326 | 327 | # TODO: dynamically load message class and do instance compare 328 | 329 | 330 | class Message(object): 331 | """Base class of Message data classes auto-generated from msg files.""" 332 | 333 | # slots is explicitly both for data representation and 334 | # performance. Higher-level code assumes that there is a 1-to-1 335 | # mapping between __slots__ and message fields. In terms of 336 | # performance, explicitly settings slots eliminates dictionary for 337 | # new-style object. 338 | __slots__ = ['_connection_header'] 339 | 340 | def __init__(self, *args, **kwds): 341 | """ 342 | Create a new Message instance. 343 | 344 | There are multiple ways of initializing Message instances, either using 345 | a 1-to-1 correspondence between constructor arguments and message 346 | fields (*args), or using Python "keyword" arguments (**kwds) to 347 | initialize named field and leave the rest with default values. 348 | """ 349 | if args and kwds: 350 | raise TypeError('Message constructor may only use args OR keywords, not both') 351 | if args: 352 | if len(args) != len(self.__slots__): 353 | raise TypeError('Invalid number of arguments, args should be %s' % str(self.__slots__) + ' args are' + str(args)) 354 | for i, k in enumerate(self.__slots__): 355 | setattr(self, k, args[i]) 356 | else: 357 | # validate kwds 358 | for k, v in kwds.items(): 359 | if k not in self.__slots__: 360 | raise AttributeError('%s is not an attribute of %s' % (k, self.__class__.__name__)) 361 | # iterate through slots so all fields are initialized. 362 | # this is important so that subclasses don't reference an 363 | # uninitialized field and raise an AttributeError. 364 | for k in self.__slots__: 365 | if k in kwds: 366 | setattr(self, k, kwds[k]) 367 | else: 368 | setattr(self, k, None) 369 | 370 | def __getstate__(self): 371 | """Support for Python pickling.""" 372 | return [getattr(self, x) for x in self.__slots__] 373 | 374 | def __setstate__(self, state): 375 | """Support for Python pickling.""" 376 | for x, val in zip(self.__slots__, state): 377 | setattr(self, x, val) 378 | 379 | def _get_types(self): 380 | raise Exception('must be overriden') 381 | 382 | def _check_types(self, exc=None): 383 | """ 384 | Perform dynamic type-checking of Message fields. 385 | 386 | This is performance intensive 387 | and is meant for post-error diagnosis 388 | :param exc: underlying exception that gave cause for type check, ``Exception`` 389 | :raises: exc:`genpy.SerializationError` If typecheck fails 390 | """ 391 | for n, t in zip(self.__slots__, self._get_types()): 392 | check_type(n, t, getattr(self, n)) 393 | if exc: # if exc is set and check_type could not diagnose, raise wrapped error 394 | raise SerializationError(str(exc)) 395 | 396 | def serialize(self, buff): 397 | """ 398 | Serialize data into buffer. 399 | 400 | :param buff: buffer, ``StringIO`` 401 | """ 402 | pass 403 | 404 | def deserialize(self, str_): 405 | """ 406 | Deserialize data in str into this instance. 407 | 408 | :param str_: serialized data, ``str`` 409 | """ 410 | pass 411 | 412 | def __repr__(self): 413 | return strify_message(self) 414 | 415 | def __str__(self): 416 | return strify_message(self) 417 | 418 | # TODO: unit test 419 | def __eq__(self, other): 420 | if not isinstance(other, self.__class__): 421 | return False 422 | for f in self.__slots__: 423 | try: 424 | v1 = getattr(self, f) 425 | v2 = getattr(other, f) 426 | if type(v1) in (list, tuple) and type(v2) in (list, tuple): 427 | # we treat tuples and lists as equivalent 428 | if tuple(v1) != tuple(v2): 429 | return False 430 | elif not v1 == v2: 431 | return False 432 | except AttributeError: 433 | return False 434 | return True 435 | 436 | def __ne__(self, other): 437 | return not self == other 438 | 439 | 440 | def get_printable_message_args(msg, buff=None, prefix=''): 441 | """ 442 | Get string representation of msg arguments. 443 | 444 | :param msg: msg message to fill, ``Message`` 445 | :param prefix: field name prefix (for verbose printing), ``str`` 446 | :returns: printable representation of msg args, ``str`` 447 | """ 448 | try: 449 | from cStringIO import StringIO # Python 2.x 450 | except ImportError: 451 | from io import StringIO # Python 3.x 452 | 453 | if buff is None: 454 | buff = StringIO() 455 | for f in msg.__slots__: 456 | if isinstance(getattr(msg, f), Message): 457 | get_printable_message_args(getattr(msg, f), buff=buff, prefix=(prefix+f+'.')) 458 | else: 459 | buff.write(prefix+f+' ') 460 | return buff.getvalue().rstrip() 461 | 462 | 463 | def _fill_val(msg, f, v, keys, prefix): 464 | """ 465 | Subroutine of L{_fill_message_args()}. 466 | 467 | Sets a particular field on a message 468 | :param f: field name, ``str`` 469 | :param v: field value 470 | :param keys: keys to use as substitute values for messages and timestamps, ``dict`` 471 | :raises: exc:`MessageException` 472 | """ 473 | if f not in msg.__slots__: 474 | raise MessageException('No field name [%s%s]' % (prefix, f)) 475 | def_val = getattr(msg, f) 476 | if isinstance(def_val, Message) or isinstance(def_val, TVal): 477 | # check for substitution key, e.g. 'now' 478 | if type(v) == str: 479 | if v in keys: 480 | setattr(msg, f, keys[v]) 481 | else: 482 | raise MessageException('No key named [%s]' % (v)) 483 | elif isinstance(def_val, TVal) and type(v) in (int, long): 484 | # special case to handle time value represented as a single number 485 | # TODO: this is a lossy conversion 486 | if isinstance(def_val, Time): 487 | setattr(msg, f, Time.from_sec(v/1e9)) 488 | elif isinstance(def_val, Duration): 489 | setattr(msg, f, Duration.from_sec(v/1e9)) 490 | else: 491 | raise MessageException('Cannot create time values of type [%s]' % (type(def_val))) 492 | else: 493 | _fill_message_args(def_val, v, keys, prefix=(prefix+f+'.')) 494 | elif type(def_val) == list: 495 | if not type(v) in [list, tuple]: 496 | raise MessageException('Field [%s%s] must be a list or tuple instead of: %s' % (prefix, f, type(v).__name__)) 497 | # determine base_type of field by looking at _slot_types 498 | idx = msg.__slots__.index(f) 499 | t = msg._slot_types[idx] 500 | base_type, is_array, length = genmsg.msgs.parse_type(t) 501 | # - for primitives, we just directly set (we don't 502 | # type-check. we rely on serialization type checker) 503 | if base_type in genmsg.msgs.PRIMITIVE_TYPES: 504 | # 3785 505 | if length is not None and len(v) != length: 506 | raise MessageException('Field [%s%s] has incorrect number of elements: %s != %s' % (prefix, f, len(v), length)) 507 | setattr(msg, f, v) 508 | 509 | # - for complex types, we have to iteratively append to def_val 510 | else: 511 | # 3785 512 | if length is not None and len(v) != length: 513 | raise MessageException('Field [%s%s] has incorrect number of elements: %s != %s' % (prefix, f, len(v), length)) 514 | list_msg_class = get_message_class(base_type) 515 | if list_msg_class is None: 516 | raise MessageException('Cannot instantiate messages for field [%s%s] : cannot load class %s' % (prefix, f, base_type)) 517 | del def_val[:] 518 | for el in v: 519 | inner_msg = list_msg_class() 520 | if isinstance(inner_msg, TVal) and type(el) in (int, long): 521 | # special case to handle time value represented as a single number 522 | # TODO: this is a lossy conversion 523 | if isinstance(inner_msg, Time): 524 | inner_msg = Time.from_sec(el/1e9) 525 | elif isinstance(inner_msg, Duration): 526 | inner_msg = Duration.from_sec(el/1e9) 527 | else: 528 | raise MessageException('Cannot create time values of type [%s]' % (type(inner_msg))) 529 | else: 530 | _fill_message_args(inner_msg, el, keys, prefix) 531 | def_val.append(inner_msg) 532 | else: 533 | setattr(msg, f, v) 534 | 535 | 536 | def _fill_message_args(msg, msg_args, keys, prefix=''): 537 | """ 538 | Populate message with specified args. 539 | 540 | :param msg: message to fill, ``Message`` 541 | :param msg_args: list of arguments to set fields to, ``[args]`` 542 | :param keys: keys to use as substitute values for messages and timestamps. ``dict`` 543 | :param prefix: field name prefix (for verbose printing), ``str`` 544 | :returns: unused/leftover message arguments. ``[args]`` 545 | :raise :exc:`MessageException` If not enough message arguments to fill message 546 | :raises: :exc:`ValueError` If msg or msg_args is not of correct type 547 | """ 548 | if not isinstance(msg, (Message, TVal)): 549 | raise ValueError('msg must be a Message instance: %s' % msg) 550 | 551 | if type(msg_args) == dict: 552 | 553 | # print "DICT ARGS", msg_args 554 | # print "ACTIVE SLOTS",msg.__slots__ 555 | 556 | for f, v in msg_args.items(): 557 | # assume that an empty key is actually an empty string 558 | if v is None: 559 | v = '' 560 | _fill_val(msg, f, v, keys, prefix) 561 | elif type(msg_args) == list: 562 | 563 | # print "LIST ARGS", msg_args 564 | # print "ACTIVE SLOTS",msg.__slots__ 565 | 566 | if len(msg_args) > len(msg.__slots__): 567 | raise MessageException('Too many arguments:\n * Given: %s\n * Expected: %s' % (msg_args, msg.__slots__)) 568 | elif len(msg_args) < len(msg.__slots__): 569 | raise MessageException('Not enough arguments:\n * Given: %s\n * Expected: %s' % (msg_args, msg.__slots__)) 570 | 571 | for f, v in zip(msg.__slots__, msg_args): 572 | _fill_val(msg, f, v, keys, prefix) 573 | else: 574 | raise ValueError('invalid msg_args type: %s' % str(msg_args)) 575 | 576 | 577 | def fill_message_args(msg, msg_args, keys={}): 578 | """ 579 | Populate message with specified args. 580 | 581 | Args are assumed to be a 582 | list of arguments from a command-line YAML parser. See 583 | http://www.ros.org/wiki/ROS/YAMLCommandLine for specification on 584 | how messages are filled. 585 | 586 | fill_message_args also takes in an optional 'keys' dictionary 587 | which contain substitute values for message and time types. These 588 | values must be of the correct instance type, i.e. a Message, Time, 589 | or Duration. In a string key is encountered with these types, the 590 | value from the keys dictionary will be used instead. This is 591 | mainly used to provide values for the 'now' timestamp. 592 | 593 | :param msg: message to fill, ``Message`` 594 | :param msg_args: list of arguments to set fields to, or 595 | If None, msg_args will be made an empty list., ``[args]`` 596 | :param keys: keys to use as substitute values for messages and timestamps, ``dict`` 597 | :raises: :exc:`MessageException` If not enough/too many message arguments to fill message 598 | """ 599 | # a list of arguments is similar to python's 600 | # *args, whereas dictionaries are like **kwds. 601 | 602 | # empty messages serialize as a None, which we make equivalent to 603 | # an empty message 604 | if msg_args is None: 605 | msg_args = [] 606 | 607 | # msg_args is always a list, due to the fact it is parsed from a 608 | # command-line argument list. We have to special-case handle a 609 | # list with a single dictionary, which has precedence over the 610 | # general list representation. We offer this precedence as there 611 | # is no other way to do kwd assignments into the outer message. 612 | if len(msg_args) == 1 and type(msg_args[0]) == dict: 613 | # according to spec, if we only get one msg_arg and it's a dictionary, we 614 | # use it directly 615 | _fill_message_args(msg, msg_args[0], keys, '') 616 | else: 617 | _fill_message_args(msg, msg_args, keys, '') 618 | 619 | 620 | def _get_message_or_service_class(type_str, message_type, reload_on_error=False): 621 | """ 622 | Retrieve message/service class instances. 623 | 624 | Used by get_message_class and get_service_class. 625 | :param type_str: 'msg' or 'srv', ``str`` 626 | :param message_type: type name of message/service, ``str`` 627 | :returns: Message/Service for message/service type or None, ``class`` 628 | :raises: :exc:`ValueError` If message_type is invalidly specified 629 | """ 630 | if message_type == 'time': 631 | return Time 632 | if message_type == 'duration': 633 | return Duration 634 | # parse package and local type name for import 635 | package, base_type = genmsg.package_resource_name(message_type) 636 | if not package: 637 | if base_type == 'Header': 638 | package = 'std_msgs' 639 | else: 640 | raise ValueError('message type is missing package name: %s' % str(message_type)) 641 | pypkg = val = None 642 | try: 643 | # import the package 644 | pypkg = __import__('%s.%s' % (package, type_str)) 645 | except ImportError: 646 | # try importing from dry package if available 647 | try: 648 | from roslib import load_manifest 649 | from rospkg import ResourceNotFound 650 | try: 651 | load_manifest(package) 652 | try: 653 | pypkg = __import__('%s.%s' % (package, type_str)) 654 | except ImportError: 655 | pass 656 | except ResourceNotFound: 657 | pass 658 | except ImportError: 659 | pass 660 | if pypkg: 661 | try: 662 | val = getattr(getattr(pypkg, type_str), base_type) 663 | except AttributeError: 664 | pass 665 | 666 | # this logic is mainly to support rosh, so that a user doesn't 667 | # have to exit a shell just because a message wasn't built yet 668 | if val is None and reload_on_error: 669 | try: 670 | if pypkg: 671 | reload(pypkg) 672 | val = getattr(getattr(pypkg, type_str), base_type) 673 | except Exception: 674 | val = None 675 | return val 676 | 677 | 678 | # cache for get_message_class 679 | _message_class_cache = {} 680 | 681 | 682 | def get_message_class(message_type, reload_on_error=False): 683 | """ 684 | Get the message class. 685 | 686 | NOTE: this function maintains a local cache of results to improve 687 | performance. 688 | :param message_type: type name of message, ``str`` 689 | :param reload_on_error: (optional). Attempt to reload the Python 690 | module if unable to load message the first time. Defaults to 691 | False. This is necessary if messages are built after the first load. 692 | :returns: Message class for message/service type, ``Message class`` 693 | :raises :exc:`ValueError`: if message_type is invalidly specified 694 | """ 695 | if message_type in _message_class_cache: 696 | return _message_class_cache[message_type] 697 | cls = _get_message_or_service_class('msg', message_type, reload_on_error=reload_on_error) 698 | if cls: 699 | _message_class_cache[message_type] = cls 700 | return cls 701 | 702 | 703 | # cache for get_service_class 704 | _service_class_cache = {} 705 | 706 | 707 | def get_service_class(service_type, reload_on_error=False): 708 | """ 709 | Get the service class. 710 | 711 | NOTE: this function maintains a local cache of results to improve 712 | performance. 713 | :param service_type: type name of service, ``str`` 714 | :param reload_on_error: (optional). Attempt to reload the Python 715 | module if unable to load message the first time. Defaults to 716 | False. This is necessary if messages are built after the first load. 717 | :returns: Service class for service type, ``Service class`` 718 | :raises :exc:`Exception` If service_type is invalidly specified 719 | """ 720 | if service_type in _service_class_cache: 721 | return _service_class_cache[service_type] 722 | cls = _get_message_or_service_class('srv', service_type, reload_on_error=reload_on_error) 723 | _service_class_cache[service_type] = cls 724 | return cls 725 | -------------------------------------------------------------------------------- /src/genpy/rostime.py: -------------------------------------------------------------------------------- 1 | # Software License Agreement (BSD License) 2 | # 3 | # Copyright (c) 2008, Willow Garage, Inc. 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions 8 | # are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # * Redistributions in binary form must reproduce the above 13 | # copyright notice, this list of conditions and the following 14 | # disclaimer in the documentation and/or other materials provided 15 | # with the distribution. 16 | # * Neither the name of Willow Garage, Inc. nor the names of its 17 | # contributors may be used to endorse or promote products derived 18 | # from this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 23 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 24 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 25 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 26 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 30 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 31 | # POSSIBILITY OF SUCH DAMAGE. 32 | 33 | """ROS Time representation, including Duration.""" 34 | from __future__ import division 35 | 36 | import numbers 37 | 38 | 39 | def _canon(secs, nsecs): 40 | # canonical form: nsecs is always positive, nsecs < 1 second 41 | secs_over = nsecs // 1000000000 42 | secs += secs_over 43 | nsecs -= secs_over * 1000000000 44 | return secs, nsecs 45 | 46 | 47 | class TVal(object): 48 | """ 49 | Base class of :class:`Time` and :class:`Duration` representations. 50 | 51 | Representation is secs+nanoseconds since epoch. 52 | """ 53 | 54 | __slots__ = ['secs', 'nsecs'] 55 | 56 | # mimic same API as messages when being introspected 57 | _slot_types = ['int32', 'int32'] 58 | 59 | def __init__(self, secs=0, nsecs=0): # noqa: D205, D400 60 | """ 61 | :param secs: seconds. If secs is a float, then nsecs must not be set or 0, 62 | larger seconds will be of type long on 32-bit systems, ``int/long/float`` 63 | :param nsecs: nanoseconds, ``int`` 64 | """ 65 | if not isinstance(secs, numbers.Integral): 66 | # float secs constructor 67 | if nsecs != 0: 68 | raise ValueError('if secs is a float, nsecs cannot be set') 69 | float_secs = secs 70 | secs = int(float_secs) 71 | nsecs = int((float_secs - secs) * 1000000000) 72 | else: 73 | secs = int(secs) 74 | nsecs = int(nsecs) 75 | 76 | self.secs, self.nsecs = _canon(secs, nsecs) 77 | 78 | @classmethod 79 | def from_sec(cls, float_secs): 80 | """ 81 | Create new TVal instance using time.time() value (float seconds). 82 | 83 | :param float_secs: time value in time.time() format, ``float`` 84 | :returns: :class:`TVal` instance for specified time 85 | """ 86 | secs = int(float_secs) 87 | nsecs = int((float_secs - secs) * 1000000000) 88 | return cls(secs, nsecs) 89 | 90 | def is_zero(self): # noqa: D200, D400, D401 91 | """ 92 | :returns: ``True`` if time is zero (secs and nsecs are zero), ``bool`` 93 | """ 94 | return self.secs == 0 and self.nsecs == 0 95 | 96 | def set(self, secs, nsecs): 97 | """ 98 | Set time using separate secs and nsecs values. 99 | 100 | :param secs: seconds since epoch, ``int`` 101 | :param nsecs: nanoseconds since seconds, ``int`` 102 | """ 103 | self.secs = secs 104 | self.nsecs = nsecs 105 | 106 | def canon(self): 107 | """ 108 | Canonicalize the field representation in this instance. 109 | 110 | Should only be used when manually setting secs/nsecs slot values, as 111 | in deserialization. 112 | """ 113 | self.secs, self.nsecs = _canon(self.secs, self.nsecs) 114 | 115 | def to_sec(self): # noqa: D200, D400, D401 116 | """ 117 | :returns: time as float seconds (same as time.time() representation), ``float`` 118 | """ 119 | return float(self.secs) + float(self.nsecs) / 1e9 120 | 121 | def to_nsec(self): # noqa: D200, D400, D401 122 | """ 123 | :returns: time as nanoseconds, ``long`` 124 | """ 125 | return self.secs * int(1e9) + self.nsecs 126 | 127 | def __hash__(self): 128 | """ 129 | Time values are hashable. 130 | 131 | Time values with identical fields have the same hash. 132 | """ 133 | return hash((self.secs, self.nsecs)) 134 | 135 | def __str__(self): 136 | return str(self.to_nsec()) 137 | 138 | def __repr__(self): 139 | return 'genpy.TVal[%d]' % self.to_nsec() 140 | 141 | def __nonzero__(self): 142 | """Return if time value is not zero.""" 143 | return self.secs != 0 or self.nsecs != 0 144 | __bool__ = __nonzero__ 145 | 146 | def __lt__(self, other): 147 | """< test for time values.""" 148 | try: 149 | return self.__cmp__(other) < 0 150 | except TypeError: 151 | return NotImplemented 152 | 153 | def __le__(self, other): 154 | """<= test for time values.""" 155 | try: 156 | return self.__cmp__(other) <= 0 157 | except TypeError: 158 | return NotImplemented 159 | 160 | def __gt__(self, other): 161 | """> test for time values.""" 162 | try: 163 | return self.__cmp__(other) > 0 164 | except TypeError: 165 | return NotImplemented 166 | 167 | def __ge__(self, other): 168 | """>= test for time values.""" 169 | try: 170 | return self.__cmp__(other) >= 0 171 | except TypeError: 172 | return NotImplemented 173 | 174 | def __ne__(self, other): 175 | return not self.__eq__(other) 176 | 177 | def __cmp__(self, other): 178 | if not isinstance(other, TVal): 179 | raise TypeError('Cannot compare to non-TVal') 180 | a = self.to_nsec() 181 | b = other.to_nsec() 182 | return (a > b) - (a < b) 183 | 184 | def __eq__(self, other): 185 | if not isinstance(other, TVal): 186 | return False 187 | return self.to_nsec() == other.to_nsec() 188 | 189 | 190 | class Time(TVal): 191 | """ 192 | Time contains the ROS-wide 'time' primitive representation. 193 | 194 | It consists of two integers: seconds since epoch and nanoseconds since 195 | seconds. Time instances are mutable. 196 | """ 197 | 198 | __slots__ = ['secs', 'nsecs'] 199 | 200 | def __init__(self, secs=0, nsecs=0): 201 | """ 202 | Construct time where secs and nsecs are integers. 203 | 204 | You may prefer to use the static L{from_sec()} factory method instead. 205 | 206 | :param secs: seconds since epoch, ``int`` 207 | :param nsecs: nanoseconds since seconds (since epoch), ``int`` 208 | """ 209 | super(Time, self).__init__(secs, nsecs) 210 | if self.secs < 0: 211 | raise TypeError('time values must be positive') 212 | 213 | def __getstate__(self): 214 | """Support for Python pickling.""" 215 | return [self.secs, self.nsecs] 216 | 217 | def __setstate__(self, state): 218 | """Support for Python pickling.""" 219 | self.secs, self.nsecs = state 220 | 221 | def to_time(self): 222 | """ 223 | Get Time in time.time() format. alias of L{to_sec()}. 224 | 225 | :returns: time in floating point secs (time.time() format), ``float`` 226 | """ 227 | return self.to_sec() 228 | 229 | def __hash__(self): 230 | return super(Time, self).__hash__() 231 | 232 | def __repr__(self): 233 | return 'genpy.Time[%d]' % self.to_nsec() 234 | 235 | def __add__(self, other): 236 | """ 237 | Add duration to this time. 238 | 239 | :param other: :class:`Duration` 240 | """ 241 | if not isinstance(other, Duration): 242 | return NotImplemented 243 | return self.__class__(self.secs + other.secs, self.nsecs + other.nsecs) 244 | 245 | __radd__ = __add__ 246 | 247 | def __sub__(self, other): 248 | """ 249 | Subtract time or duration from this time. 250 | 251 | :param other: :class:`Duration`/:class:`Time` 252 | :returns: :class:`Duration` if other is a :class:`Time`, :class:`Time` if other is a :class:`Duration` 253 | """ 254 | if isinstance(other, Time): 255 | return Duration(self.secs - other.secs, self.nsecs - other.nsecs) 256 | elif isinstance(other, Duration): 257 | return self.__class__(self.secs - other.secs, self.nsecs - other.nsecs) 258 | else: 259 | return NotImplemented 260 | 261 | def __cmp__(self, other): 262 | """ 263 | Compare to another time. 264 | 265 | :param other: :class:`Time` 266 | """ 267 | if not isinstance(other, Time): 268 | raise TypeError('cannot compare to non-Time') 269 | a = self.to_nsec() 270 | b = other.to_nsec() 271 | return (a > b) - (a < b) 272 | 273 | def __eq__(self, other): 274 | """ 275 | Equal test for Time. 276 | 277 | Comparison assumes that both time instances are in canonical 278 | representation; only compares fields. 279 | 280 | :param other: :class:`Time` 281 | """ 282 | if not isinstance(other, Time): 283 | return False 284 | return self.secs == other.secs and self.nsecs == other.nsecs 285 | 286 | 287 | class Duration(TVal): 288 | """ 289 | Duration represents the ROS 'duration' primitive. 290 | 291 | It consists of two integers: seconds and nanoseconds. 292 | The Duration class allows you to add and subtract Duration instances, 293 | including adding and subtracting from :class:`Time` instances. 294 | """ 295 | 296 | __slots__ = ['secs', 'nsecs'] 297 | 298 | def __init__(self, secs=0, nsecs=0): 299 | """ 300 | Create new Duration instance. secs and nsecs are integers and correspond to the ROS 'duration' primitive. 301 | 302 | :param secs: seconds, ``int`` 303 | :param nsecs: nanoseconds, ``int`` 304 | """ 305 | super(Duration, self).__init__(secs, nsecs) 306 | 307 | def __getstate__(self): 308 | """Support for Python pickling.""" 309 | return [self.secs, self.nsecs] 310 | 311 | def __setstate__(self, state): 312 | """Support for Python pickling.""" 313 | self.secs, self.nsecs = state 314 | 315 | def __hash__(self): 316 | return super(Duration, self).__hash__() 317 | 318 | def __repr__(self): 319 | return 'genpy.Duration[%d]' % self.to_nsec() 320 | 321 | def __neg__(self): # noqa: D400, D401 322 | """:returns: Negative value of this :class:`Duration`""" 323 | return self.__class__(-self.secs, -self.nsecs) 324 | 325 | def __abs__(self): 326 | """ 327 | Absolute value of this duration. 328 | 329 | :returns: positive :class:`Duration` 330 | """ 331 | if self.secs >= 0: 332 | return self 333 | return self.__neg__() 334 | 335 | def __add__(self, other): 336 | """ 337 | Add duration to this duration, or this duration to a time, creating a new time value as a result. 338 | 339 | :param other: duration or time, ``Duration``/``Time`` 340 | :returns: :class:`Duration` if other is a :class:`Duration` 341 | instance, :class:`Time` if other is a :class:`Time` 342 | """ 343 | if isinstance(other, Duration): 344 | return self.__class__(self.secs+other.secs, self.nsecs+other.nsecs) 345 | else: 346 | return NotImplemented 347 | 348 | __radd__ = __add__ 349 | 350 | def __sub__(self, other): 351 | """ 352 | Subtract duration from this duration, returning a new duration. 353 | 354 | :param other: duration 355 | :returns: :class:`Duration` 356 | """ 357 | if not isinstance(other, Duration): 358 | return NotImplemented 359 | return self.__class__(self.secs-other.secs, self.nsecs-other.nsecs) 360 | 361 | def __mul__(self, val): 362 | """ 363 | Multiply this duration by an integer or float. 364 | 365 | :param val: multiplication factor, ``int/float`` 366 | :returns: :class:`Duration` multiplied by val 367 | """ 368 | if isinstance(val, numbers.Integral): 369 | return self.__class__(self.secs * val, self.nsecs * val) 370 | elif isinstance(val, numbers.Real): 371 | return self.__class__.from_sec(self.to_sec() * val) 372 | else: 373 | return NotImplemented 374 | 375 | __rmul__ = __mul__ 376 | 377 | def __floordiv__(self, val): 378 | """ 379 | Floor divide this duration by an integer or float. 380 | 381 | :param val: division factor ``int/float``, or :class:`Duration` to divide by 382 | :returns: :class:`Duration` divided by val - a :class:`Duration` if divided by a number, or a number if divided by a duration 383 | """ 384 | if isinstance(val, numbers.Integral) or isinstance(val, numbers.Real): 385 | return self.__class__.from_sec(self.to_sec() // val) 386 | elif isinstance(val, Duration): 387 | return int(self.to_sec() // val.to_sec()) 388 | else: 389 | return NotImplemented 390 | 391 | def __div__(self, val): 392 | """ 393 | Divide this duration by an integer or float. 394 | 395 | :param val: division factor ``int/float``, or :class:`Duration` to divide by 396 | :returns: :class:`Duration` divided by val - a :class:`Duration` if divided by a number, or a number if divided by a duration 397 | """ 398 | if isinstance(val, numbers.Integral) or isinstance(val, numbers.Real): 399 | return self.__class__.from_sec(self.to_sec() / val) 400 | elif isinstance(val, Duration): 401 | return self.to_sec() / val.to_sec() 402 | else: 403 | return NotImplemented 404 | 405 | def __truediv__(self, val): 406 | """ 407 | Divide this duration by an integer or float. 408 | 409 | :param val: division factor ``int/float``, or :class:`Duration` to divide by 410 | :returns: :class:`Duration` divided by val - a :class:`Duration` if divided by a number, or a number if divided by a duration 411 | """ 412 | if isinstance(val, numbers.Real): 413 | return self.__class__.from_sec(self.to_sec() / val) 414 | elif isinstance(val, Duration): 415 | return self.to_sec() / val.to_sec() 416 | else: 417 | return NotImplemented 418 | 419 | def __mod__(self, val): 420 | """ 421 | Find the remainder when dividing this Duration by another Duration. 422 | 423 | :returns: :class:`Duration` The remaining time after the division 424 | """ 425 | if isinstance(val, Duration): 426 | return self.__class__.from_sec(self.to_sec() % val.to_sec()) 427 | else: 428 | return NotImplemented 429 | 430 | def __divmod__(self, val): 431 | """ 432 | Implement the builtin divmod for a pair of Durations. 433 | 434 | :returns: ``int`` The floored result of the division 435 | :returns: :class:`Duration` The remaining time after the division 436 | """ 437 | if isinstance(val, Duration): 438 | quotient, remainder = divmod(self.to_sec(), val.to_sec()) 439 | return int(quotient), self.__class__.from_sec(remainder) 440 | else: 441 | return NotImplemented 442 | 443 | def __cmp__(self, other): 444 | if not isinstance(other, Duration): 445 | raise TypeError('Cannot compare to non-Duration') 446 | a = self.to_nsec() 447 | b = other.to_nsec() 448 | return (a > b) - (a < b) 449 | 450 | def __eq__(self, other): 451 | if not isinstance(other, Duration): 452 | return False 453 | return self.secs == other.secs and self.nsecs == other.nsecs 454 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ros/genpy/9e32c6a06364d271ac18625718a6349afb8ee210/test/__init__.py -------------------------------------------------------------------------------- /test/files/array/Object.msg: -------------------------------------------------------------------------------- 1 | int32 data 2 | -------------------------------------------------------------------------------- /test/files/array/ObjectArray.msg: -------------------------------------------------------------------------------- 1 | Object[] array 2 | -------------------------------------------------------------------------------- /test/files/array/bool_fixed_deser.txt: -------------------------------------------------------------------------------- 1 | start = end 2 | end += 3 3 | data = _get_struct_3B().unpack(str[start:end]) 4 | data = list(map(bool, data)) 5 | -------------------------------------------------------------------------------- /test/files/array/bool_fixed_ser.txt: -------------------------------------------------------------------------------- 1 | buff.write(_get_struct_3B().pack(*data)) 2 | -------------------------------------------------------------------------------- /test/files/array/bool_varlen_deser.txt: -------------------------------------------------------------------------------- 1 | start = end 2 | end += 4 3 | (length,) = _struct_I.unpack(str[start:end]) 4 | pattern = '<%sB'%length 5 | start = end 6 | s = struct.Struct(pattern) 7 | end += s.size 8 | data = s.unpack(str[start:end]) 9 | data = list(map(bool, data)) 10 | -------------------------------------------------------------------------------- /test/files/array/bool_varlen_ser.txt: -------------------------------------------------------------------------------- 1 | length = len(data) 2 | buff.write(_struct_I.pack(length)) 3 | pattern = '<%sB'%length 4 | buff.write(struct.Struct(pattern).pack(*data)) 5 | -------------------------------------------------------------------------------- /test/files/array/int16_fixed_deser.txt: -------------------------------------------------------------------------------- 1 | start = end 2 | end += 20 3 | data = _get_struct_10h().unpack(str[start:end]) 4 | -------------------------------------------------------------------------------- /test/files/array/int16_fixed_deser_np.txt: -------------------------------------------------------------------------------- 1 | start = end 2 | end += 20 3 | data = numpy.frombuffer(str[start:end], dtype=numpy.int16, count=10) 4 | -------------------------------------------------------------------------------- /test/files/array/int16_fixed_ser.txt: -------------------------------------------------------------------------------- 1 | buff.write(_get_struct_10h().pack(*data)) 2 | -------------------------------------------------------------------------------- /test/files/array/int16_fixed_ser_np.txt: -------------------------------------------------------------------------------- 1 | buff.write(data.tostring()) 2 | -------------------------------------------------------------------------------- /test/files/array/int16_varlen_deser.txt: -------------------------------------------------------------------------------- 1 | start = end 2 | end += 4 3 | (length,) = _struct_I.unpack(str[start:end]) 4 | pattern = '<%sh'%length 5 | start = end 6 | s = struct.Struct(pattern) 7 | end += s.size 8 | data = s.unpack(str[start:end]) 9 | -------------------------------------------------------------------------------- /test/files/array/int16_varlen_deser_np.txt: -------------------------------------------------------------------------------- 1 | start = end 2 | end += 4 3 | (length,) = _struct_I.unpack(str[start:end]) 4 | pattern = '<%sh'%length 5 | start = end 6 | s = struct.Struct(pattern) 7 | end += s.size 8 | data = numpy.frombuffer(str[start:end], dtype=numpy.int16, count=length) 9 | -------------------------------------------------------------------------------- /test/files/array/int16_varlen_ser.txt: -------------------------------------------------------------------------------- 1 | length = len(data) 2 | buff.write(_struct_I.pack(length)) 3 | pattern = '<%sh'%length 4 | buff.write(struct.Struct(pattern).pack(*data)) 5 | -------------------------------------------------------------------------------- /test/files/array/int16_varlen_ser_np.txt: -------------------------------------------------------------------------------- 1 | length = len(data) 2 | buff.write(_struct_I.pack(length)) 3 | pattern = '<%sh'%length 4 | buff.write(data.tostring()) 5 | -------------------------------------------------------------------------------- /test/files/array/object_fixed_deser.txt: -------------------------------------------------------------------------------- 1 | data = [] 2 | for i in range(0, 3): 3 | val0 = foo.msg.Object() 4 | start = end 5 | end += 4 6 | (val0.data,) = _get_struct_i().unpack(str[start:end]) 7 | data.append(val0) 8 | -------------------------------------------------------------------------------- /test/files/array/object_fixed_ser.txt: -------------------------------------------------------------------------------- 1 | if len(data) != 3: 2 | self._check_types(ValueError("Expecting %s items but found %s when writing '%s'" % (3, len(data), 'data'))) 3 | for val0 in data: 4 | _x = val0.data 5 | buff.write(_get_struct_i().pack(_x)) 6 | -------------------------------------------------------------------------------- /test/files/array/object_varlen_deser.txt: -------------------------------------------------------------------------------- 1 | start = end 2 | end += 4 3 | (length,) = _struct_I.unpack(str[start:end]) 4 | data = [] 5 | for i in range(0, length): 6 | val0 = foo.msg.Object() 7 | start = end 8 | end += 4 9 | (val0.data,) = _get_struct_i().unpack(str[start:end]) 10 | data.append(val0) 11 | -------------------------------------------------------------------------------- /test/files/array/object_varlen_ser.txt: -------------------------------------------------------------------------------- 1 | length = len(data) 2 | buff.write(_struct_I.pack(length)) 3 | for val0 in data: 4 | _x = val0.data 5 | buff.write(_get_struct_i().pack(_x)) 6 | -------------------------------------------------------------------------------- /test/files/array/object_varlen_ser_full.txt: -------------------------------------------------------------------------------- 1 | try: 2 | length = len(self.array) 3 | buff.write(_struct_I.pack(length)) 4 | for val1 in self.array: 5 | _x = val1.data 6 | buff.write(_get_struct_i().pack(_x)) 7 | except struct.error as se: self._check_types(struct.error("%s: '%s' when writing '%s'" % (type(se), str(se), str(locals().get('_x', self))))) 8 | except TypeError as te: self._check_types(ValueError("%s: '%s' when writing '%s'" % (type(te), str(te), str(locals().get('_x', self))))) 9 | -------------------------------------------------------------------------------- /test/files/array/string_fixed_deser.txt: -------------------------------------------------------------------------------- 1 | data = [] 2 | for i in range(0, 2): 3 | start = end 4 | end += 4 5 | (length,) = _struct_I.unpack(str[start:end]) 6 | start = end 7 | end += length 8 | if python3: 9 | val0 = str[start:end].decode('utf-8', 'rosmsg') 10 | else: 11 | val0 = str[start:end] 12 | data.append(val0) 13 | -------------------------------------------------------------------------------- /test/files/array/string_fixed_ser.txt: -------------------------------------------------------------------------------- 1 | if len(data) != 2: 2 | self._check_types(ValueError("Expecting %s items but found %s when writing '%s'" % (2, len(data), 'data'))) 3 | for val0 in data: 4 | length = len(val0) 5 | if python3 or type(val0) == unicode: 6 | val0 = val0.encode('utf-8') 7 | length = len(val0) 8 | buff.write(struct.Struct('= TVal()) 66 | self.assert_(v <= TVal()) 67 | self.assert_(v < TVal(0,1)) 68 | self.assert_(TVal(0,1) > v) 69 | v.set(0, 0) 70 | self.assertEqual(0, v.secs) 71 | self.assertEqual(0, v.nsecs) 72 | v.set(1, 0) 73 | self.assertEqual(1, v.secs) 74 | self.assertEqual(0, v.nsecs) 75 | v.set(0, 1) 76 | self.assertEqual(0, v.secs) 77 | self.assertEqual(1, v.nsecs) 78 | # - set does _not_ canonicalize 79 | v.set(0, 1000000000) 80 | self.assertEqual(0, v.secs) 81 | self.assertEqual(1000000000, v.nsecs) 82 | v.canon() 83 | self.assertEqual(1, v.secs) 84 | self.assertEqual(0, v.nsecs) 85 | 86 | # - test seconds 87 | v = TVal(1) 88 | self.assertEqual(1, v.secs) 89 | self.assertEqual(0, v.nsecs) 90 | self.assert_(v) # test __zero__ 91 | self.assertFalse(v.is_zero()) 92 | self.assertEqual('1000000000', str(v)) 93 | self.assertEqual(1000000000, v.to_nsec()) 94 | self.assertEqual(v, v) 95 | self.assertEqual(v, TVal(1)) 96 | self.assertEqual(v, TVal(1, 0)) 97 | self.assertEqual(v, TVal(0,1000000000)) 98 | self.assertEqual(v.__hash__(), TVal(0,1000000000).__hash__()) 99 | self.assertNotEquals(v, TVal(0, 0)) 100 | self.assertNotEquals(v.__hash__(), TVal(0, 0).__hash__()) 101 | self.assertEqual(NotImplemented, v.__ge__(0)) 102 | class Foo(object): pass 103 | self.assertEqual(NotImplemented, v.__gt__(Foo())) 104 | self.assertEqual(NotImplemented, v.__ge__(Foo())) 105 | self.assertEqual(NotImplemented, v.__le__(Foo())) 106 | self.assertEqual(NotImplemented, v.__lt__(Foo())) 107 | self.assertFalse(v.__eq__(Foo())) 108 | self.assert_(v.__ne__(Foo())) 109 | self.assert_(v >= TVal()) 110 | self.assert_(v <= TVal(1)) 111 | self.assert_(v <= TVal(1,0)) 112 | self.assert_(v <= TVal(2,0)) 113 | self.assert_(v < TVal(2)) 114 | self.assert_(v < TVal(1,1)) 115 | self.assert_(TVal(1,1) > v) 116 | self.assert_(TVal(2) > v) 117 | # - test ns 118 | v = TVal(0, 1) 119 | self.assertEqual(0, v.secs) 120 | self.assertEqual(1, v.nsecs) 121 | self.assert_(v) # test __zero__ 122 | self.assertFalse(v.is_zero()) 123 | self.assertEqual('1', str(v)) 124 | self.assertEqual(1, v.to_nsec()) 125 | self.assertEqual(v, v) 126 | self.assertEqual(v, TVal(0,1)) 127 | self.assertNotEquals(v, TVal(0, 0)) 128 | self.assert_(v >= TVal()) 129 | self.assert_(v <= TVal(1)) 130 | self.assert_(v <= TVal(0,1)) 131 | self.assert_(v <= TVal(2,0)) 132 | self.assert_(v < TVal(0,2)) 133 | self.assert_(v < TVal(1)) 134 | self.assert_(TVal(1) > v) 135 | self.assert_(TVal(0,2) > v) 136 | # - test canon 137 | v = TVal(1, 1000000000) 138 | self.assertEqual(2, v.secs) 139 | self.assertEqual(0, v.nsecs) 140 | self.assertEqual(2, v.to_sec()) 141 | self.assertEqual(2000000000, v.to_nsec()) 142 | 143 | v = TVal(1, 1000000001) 144 | self.assertEqual(2, v.secs) 145 | self.assertEqual(1, v.nsecs) 146 | self.assertEqual(2.000000001, v.to_sec()) 147 | self.assertEqual(2000000001, v.to_nsec()) 148 | 149 | v = TVal(1, -1000000000) 150 | self.assertEqual(0, v.secs) 151 | self.assertEqual(0, v.nsecs) 152 | v = TVal(1, -999999999) 153 | self.assertEqual(0, v.secs) 154 | self.assertEqual(1, v.nsecs) 155 | self.assertEqual(0.000000001, v.to_sec()) 156 | self.assertEqual(1, v.to_nsec()) 157 | 158 | if test_neg: 159 | v = TVal(-1, -1000000000) 160 | self.assertEqual(-2, v.secs) 161 | self.assertEqual(0, v.nsecs) 162 | self.assertEqual(-2, v.to_sec()) 163 | self.assertEqual(-2000000000, v.to_nsec()) 164 | 165 | v = TVal(-2, 1000000000) 166 | self.assertEqual(-1, v.secs) 167 | self.assertEqual(0, v.nsecs) 168 | self.assertEqual(-1, v.to_sec()) 169 | self.assertEqual(-1000000000, v.to_nsec()) 170 | 171 | 172 | # test some more hashes 173 | self.assertEqual(TVal(1).__hash__(), TVal(1).__hash__()) 174 | self.assertEqual(TVal(1,1).__hash__(), TVal(1,1).__hash__()) 175 | self.assertNotEquals(TVal(1).__hash__(), TVal(2).__hash__()) 176 | self.assertNotEquals(TVal(1,1).__hash__(), TVal(1,2).__hash__()) 177 | self.assertNotEquals(TVal(1,1).__hash__(), TVal(2,1).__hash__()) 178 | 179 | def test_Time(self): 180 | from genpy.rostime import Time, Duration 181 | self.test_TVal(TVal=Time, test_neg=False) 182 | 183 | # #1600 Duration > Time should fail 184 | failed = False 185 | try: 186 | v = Duration.from_sec(0.1) > Time.from_sec(0.5) 187 | failed = True 188 | except: pass 189 | self.assertFalse(failed, "should have failed to compare") 190 | try: 191 | v = Time.from_sec(0.4) > Duration.from_sec(0.1) 192 | failed = True 193 | except: pass 194 | self.assertFalse(failed, "should have failed to compare") 195 | 196 | # TODO: sub 197 | 198 | # neg time fails 199 | try: 200 | Time(-1) 201 | failed = True 202 | except: pass 203 | self.assertFalse(failed, "negative time not allowed") 204 | try: 205 | Time(1, -1000000001) 206 | failed = True 207 | except: pass 208 | self.assertFalse(failed, "negative time not allowed") 209 | 210 | # test Time.now() is within 10 seconds of actual time (really generous) 211 | import time 212 | t = time.time() 213 | v = Time.from_sec(t) 214 | self.assertEqual(v.to_sec(), t) 215 | # test from_sec() 216 | self.assertEqual(Time.from_sec(0), Time()) 217 | self.assertEqual(Time.from_sec(1.), Time(1)) 218 | self.assertEqual(Time.from_sec(v.to_sec()), v) 219 | self.assertEqual(v.from_sec(v.to_sec()), v) 220 | # test to_time() 221 | self.assertEqual(v.to_sec(), v.to_time()) 222 | 223 | # test addition 224 | # - time + time fails 225 | try: 226 | v = Time(1,0) + Time(1, 0) 227 | failed = True 228 | except: pass 229 | self.assertFalse(failed, "Time + Time must fail") 230 | 231 | # - time + duration 232 | v = Time(1,0) + Duration(1, 0) 233 | self.assertEqual(Time(2, 0), v) 234 | v = Duration(1, 0) + Time(1,0) 235 | self.assertEqual(Time(2, 0), v) 236 | v = Time(1,1) + Duration(1, 1) 237 | self.assertEqual(Time(2, 2), v) 238 | v = Duration(1, 1) + Time(1,1) 239 | self.assertEqual(Time(2, 2), v) 240 | 241 | v = Time(1) + Duration(0, 1000000000) 242 | self.assertEqual(Time(2), v) 243 | v = Duration(1) + Time(0, 1000000000) 244 | self.assertEqual(Time(2), v) 245 | 246 | v = Time(100, 100) + Duration(300) 247 | self.assertEqual(Time(400, 100), v) 248 | v = Duration(300) + Time(100, 100) 249 | self.assertEqual(Time(400, 100), v) 250 | 251 | v = Time(100, 100) + Duration(300, 300) 252 | self.assertEqual(Time(400, 400), v) 253 | v = Duration(300, 300) + Time(100, 100) 254 | self.assertEqual(Time(400, 400), v) 255 | 256 | v = Time(100, 100) + Duration(300, -101) 257 | self.assertEqual(Time(399, 999999999), v) 258 | v = Duration(300, -101) + Time(100, 100) 259 | self.assertEqual(Time(399, 999999999), v) 260 | 261 | # test subtraction 262 | try: 263 | v = Time(1,0) - 1 264 | failed = True 265 | except: pass 266 | self.assertFalse(failed, "Time - non Duration must fail") 267 | class Foob(object): pass 268 | try: 269 | v = Time(1,0) - Foob() 270 | failed = True 271 | except: pass 272 | self.assertFalse(failed, "Time - non TVal must fail") 273 | 274 | # - Time - Duration 275 | v = Time(1,0) - Duration(1, 0) 276 | self.assertEqual(Time(), v) 277 | 278 | v = Time(1,1) - Duration(-1, -1) 279 | self.assertEqual(Time(2, 2), v) 280 | v = Time(1) - Duration(0, 1000000000) 281 | self.assertEqual(Time(), v) 282 | v = Time(2) - Duration(0, 1000000000) 283 | self.assertEqual(Time(1), v) 284 | v = Time(400, 100) - Duration(300) 285 | self.assertEqual(Time(100, 100), v) 286 | v = Time(100, 100) - Duration(0, 101) 287 | self.assertEqual(Time(99, 999999999), v) 288 | 289 | # - Time - Time = Duration 290 | v = Time(100, 100) - Time(100, 100) 291 | self.assertEqual(Duration(), v) 292 | v = Time(100, 100) - Time(100) 293 | self.assertEqual(Duration(0,100), v) 294 | v = Time(100) - Time(200) 295 | self.assertEqual(Duration(-100), v) 296 | 297 | # Time (float secs) vs. Time(int, int) 298 | self.assertEqual(Time.from_sec(0.5), Time(0.5)) 299 | t = Time(0.5) 300 | self.assert_(type(t.secs) == int) 301 | self.assertEqual(0, t.secs) 302 | self.assertEqual(500000000, t.nsecs) 303 | 304 | try: 305 | Time(0.5, 0.5) 306 | self.fail("should have thrown value error") 307 | except ValueError: pass 308 | 309 | def test_Duration(self): 310 | from genpy.rostime import Time, Duration 311 | 312 | self.test_TVal(TVal=Duration, test_neg=True) 313 | 314 | # test from_sec 315 | v = Duration(1000) 316 | self.assertEqual(v, Duration.from_sec(v.to_sec())) 317 | self.assertEqual(v, v.from_sec(v.to_sec())) 318 | v = Duration(0,1000) 319 | self.assertEqual(v, Duration.from_sec(v.to_sec())) 320 | self.assertEqual(v, v.from_sec(v.to_sec())) 321 | 322 | # test neg 323 | v = -Duration(1, -1) 324 | self.assertEqual(-1, v.secs) 325 | self.assertEqual(1, v.nsecs) 326 | v = -Duration(-1, -1) 327 | self.assertEqual(1, v.secs) 328 | self.assertEqual(1, v.nsecs) 329 | v = -Duration(-1, 1) 330 | self.assertEqual(0, v.secs) 331 | self.assertEqual(999999999, v.nsecs) 332 | 333 | # test addition 334 | self.assertEqual(Duration(1,0) + Time(1, 0), Time(2, 0)) 335 | failed = False 336 | try: 337 | v = Duration(1,0) + 1 338 | failed = True 339 | except: pass 340 | self.assertFalse(failed, "Duration + int must fail") 341 | 342 | v = Duration(1,0) + Duration(1, 0) 343 | self.assertEqual(2, v.secs) 344 | self.assertEqual(0, v.nsecs) 345 | self.assertEqual(Duration(2, 0), v) 346 | v = Duration(-1,-1) + Duration(1, 1) 347 | self.assertEqual(0, v.secs) 348 | self.assertEqual(0, v.nsecs) 349 | self.assertEqual(Duration(), v) 350 | v = Duration(1) + Duration(0, 1000000000) 351 | self.assertEqual(2, v.secs) 352 | self.assertEqual(0, v.nsecs) 353 | self.assertEqual(Duration(2), v) 354 | v = Duration(100, 100) + Duration(300) 355 | self.assertEqual(Duration(400, 100), v) 356 | v = Duration(100, 100) + Duration(300, 300) 357 | self.assertEqual(Duration(400, 400), v) 358 | v = Duration(100, 100) + Duration(300, -101) 359 | self.assertEqual(Duration(399, 999999999), v) 360 | 361 | # test subtraction 362 | try: 363 | v = Duration(1,0) - 1 364 | failed = True 365 | except: pass 366 | self.assertFalse(failed, "Duration - non duration must fail") 367 | try: 368 | v = Duration(1, 0) - Time(1,0) 369 | failed = True 370 | except: pass 371 | self.assertFalse(failed, "Duration - Time must fail") 372 | 373 | v = Duration(1,0) - Duration(1, 0) 374 | self.assertEqual(Duration(), v) 375 | v = Duration(-1,-1) - Duration(1, 1) 376 | self.assertEqual(Duration(-3, 999999998), v) 377 | v = Duration(1) - Duration(0, 1000000000) 378 | self.assertEqual(Duration(), v) 379 | v = Duration(2) - Duration(0, 1000000000) 380 | self.assertEqual(Duration(1), v) 381 | v = Duration(100, 100) - Duration(300) 382 | self.assertEqual(Duration(-200, 100), v) 383 | v = Duration(100, 100) - Duration(300, 101) 384 | self.assertEqual(Duration(-201, 999999999), v) 385 | 386 | # test abs 387 | self.assertEqual(abs(Duration()), Duration()) 388 | self.assertEqual(abs(Duration(1)), Duration(1)) 389 | self.assertEqual(abs(Duration(-1)), Duration(1)) 390 | self.assertEqual(abs(Duration(0,-1)), Duration(0,1)) 391 | self.assertEqual(abs(Duration(-1,-1)), Duration(1,1)) 392 | self.assertEqual(abs(Duration(0,1)), Duration(0,1)) 393 | 394 | # Duration (float secs) vs. Duration(int, int) 395 | self.assertEqual(Duration.from_sec(0.5), Duration(0.5)) 396 | t = Duration(0.5) 397 | self.assert_(type(t.secs) == int) 398 | self.assertEqual(0, t.secs) 399 | self.assertEqual(500000000, t.nsecs) 400 | 401 | try: 402 | Duration(0.5, 0.5) 403 | self.fail("should have thrown value error") 404 | except ValueError: pass 405 | 406 | # Test mul 407 | self.assertEqual(Duration(4), Duration(2) * 2) 408 | self.assertEqual(Duration(4), Duration(2) * 2.) 409 | self.assertEqual(Duration(4), 2 * Duration(2)) 410 | self.assertEqual(Duration(4), 2. * Duration(2)) 411 | self.assertEqual(Duration(10), Duration(4) * 2.5) 412 | self.assertEqual(Duration(4, 8), Duration(2, 4) * 2) 413 | v = Duration(4, 8) - (Duration(2, 4) * 2.) 414 | self.assert_(abs(v.to_nsec()) < 100) 415 | v = Duration(5, 10) - (Duration(2, 4) * 2.5) 416 | self.assert_(abs(v.to_nsec()) < 100) 417 | 418 | # Test div 419 | self.assertEqual(Duration(4), Duration(8) / 2) 420 | self.assertEqual(Duration(4), Duration(8) / 2.) 421 | with warnings.catch_warnings(record=True) as w: 422 | warnings.simplefilter('always') 423 | self.assertEqual(Duration(4), Duration(8) // 2) 424 | self.assertEqual(Duration(4), Duration(8) // 2.) 425 | self.assertEqual(Duration(4), Duration(9) // 2) 426 | self.assertEqual(Duration(4), Duration(9) // 2.) 427 | self.assertEqual(len(w), 0) 428 | self.assertEqual(Duration(4, 2), Duration(8, 4) / 2) 429 | v = Duration(4, 2) - (Duration(8, 4) / 2.) 430 | self.assert_(abs(v.to_nsec()) < 100) 431 | 432 | with warnings.catch_warnings(record=True) as w: 433 | warnings.simplefilter('always') 434 | self.assertEqual(Duration(4, 0), Duration(8, 4) // 2) 435 | self.assertEqual(Duration(4, 0), Duration(9, 5) // 2) 436 | v = Duration(4, 2) - (Duration(9, 5) // 2.) 437 | self.assertTrue(abs(v.to_nsec()) < 100) 438 | self.assertEqual(len(w), 0) 439 | -------------------------------------------------------------------------------- /test/test_genpy_rostime_truediv.py: -------------------------------------------------------------------------------- 1 | # Software License Agreement (BSD License) 2 | # 3 | # Copyright (c) 2009, Willow Garage, Inc. 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions 8 | # are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright 11 | # notice, this list of conditions and the following disclaimer. 12 | # * Redistributions in binary form must reproduce the above 13 | # copyright notice, this list of conditions and the following 14 | # disclaimer in the documentation and/or other materials provided 15 | # with the distribution. 16 | # * Neither the name of Willow Garage, Inc. nor the names of its 17 | # contributors may be used to endorse or promote products derived 18 | # from this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 23 | # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 24 | # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 25 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 26 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 28 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 | # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 30 | # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 31 | # POSSIBILITY OF SUCH DAMAGE. 32 | 33 | from __future__ import division 34 | 35 | import unittest 36 | 37 | 38 | class RostimeTruedivTest(unittest.TestCase): 39 | 40 | def test_Duration(self): 41 | from genpy.rostime import Duration 42 | 43 | # See #3667 as well as PEP 238 44 | d = Duration(13, 500000000) 45 | to_sec = d.to_sec() 46 | self.assertEqual(Duration(to_sec / 2.), d/2) 47 | 48 | # Test div 49 | self.assertEqual(Duration(4), Duration(8) / 2) 50 | self.assertEqual(Duration(4), Duration(8) / 2.) 51 | self.assertEqual(Duration(4), Duration(8) // 2) 52 | self.assertEqual(Duration(4), Duration(8) // 2.) 53 | self.assertEqual(Duration(4), Duration(9) // 2) 54 | self.assertEqual(Duration(4), Duration(9) // 2.) 55 | self.assertEqual(Duration(4, 2), Duration(8, 4) / 2) 56 | v = Duration(4, 2) - (Duration(8, 4) / 2.) 57 | self.assertTrue(abs(v.to_nsec()) < 100) 58 | 59 | self.assertEqual(Duration(4, 0), Duration(8, 4) // 2) 60 | self.assertEqual(Duration(4, 0), Duration(9, 5) // 2) 61 | v = Duration(4, 2) - (Duration(9, 5) // 2.) 62 | self.assertTrue(abs(v.to_nsec()) < 100) 63 | --------------------------------------------------------------------------------