├── .gitchangelog.rc ├── .gitignore ├── .pyup.yml ├── .travis.yml ├── CHANGELOG.rst ├── LICENSE ├── MANIFEST.in ├── README.rst ├── doc ├── changelog_link.rst ├── conf.py ├── index.rst ├── overview.rst ├── pyros_internals.rst ├── readme_link.rst ├── roadmap.rst ├── weblinks.rst └── zmp_internals.rst ├── examples ├── README.md └── ROS │ └── README.md ├── pyros ├── __init__.py ├── __main__.py ├── _version.py ├── client │ ├── __init__.py │ └── client.py ├── config.py └── server │ ├── __init__.py │ └── ctx_server.py ├── requirements.txt ├── requirements ├── ROS │ ├── indigo.txt │ └── kinetic.txt ├── dev.txt ├── tests.txt └── tools.txt ├── setup.cfg ├── setup.py ├── tests ├── __init__.py ├── test_pyros │ ├── __init__.py │ ├── profile_pyros_ros.py │ └── test_ros_ctx_server.py ├── test_pyros_client │ ├── __init__.py │ └── test_client.py └── test_pyros_server │ ├── __init__.py │ └── test_mock_ctx_server.py └── tox.ini /.gitchangelog.rc: -------------------------------------------------------------------------------- 1 | ## 2 | ## Format 3 | ## 4 | ## ACTION: [AUDIENCE:] COMMIT_MSG [!TAG ...] 5 | ## 6 | ## Description 7 | ## 8 | ## ACTION is one of 'chg', 'fix', 'new' 9 | ## 10 | ## Is WHAT the change is about. 11 | ## 12 | ## 'chg' is for refactor, small improvement, cosmetic changes... 13 | ## 'fix' is for bug fixes 14 | ## 'new' is for new features, big improvement 15 | ## 16 | ## AUDIENCE is optional and one of 'dev', 'usr', 'pkg', 'test', 'doc' 17 | ## 18 | ## Is WHO is concerned by the change. 19 | ## 20 | ## 'dev' is for developpers (API changes, refactors...) 21 | ## 'usr' is for final users (UI changes) 22 | ## 'pkg' is for packagers (packaging changes) 23 | ## 'test' is for testers (test only related changes) 24 | ## 'doc' is for doc guys (doc only changes) 25 | ## 26 | ## COMMIT_MSG is ... well ... the commit message itself. 27 | ## 28 | ## TAGs are additionnal adjective as 'refactor' 'minor' 'cosmetic' 29 | ## 30 | ## They are preceded with a '!' or a '@' (prefer the former, as the 31 | ## latter is wrongly interpreted in github.) Commonly used tags are: 32 | ## 33 | ## 'refactor' is obviously for refactoring code only 34 | ## 'minor' is for a very meaningless change (a typo, adding a comment) 35 | ## 'cosmetic' is for cosmetic driven change (re-indentation, 80-col...) 36 | ## 'wip' is for partial functionality but complete subfunctionality. 37 | ## 38 | ## Example: 39 | ## 40 | ## new: usr: support of bazaar implemented 41 | ## chg: re-indentend some lines !cosmetic 42 | ## new: dev: updated code to be compatible with last version of killer lib. 43 | ## fix: pkg: updated year of licence coverage. 44 | ## new: test: added a bunch of test around user usability of feature X. 45 | ## fix: typo in spelling my name in comment. !minor 46 | ## 47 | ## Please note that multi-line commit message are supported, and only the 48 | ## first line will be considered as the "summary" of the commit message. So 49 | ## tags, and other rules only applies to the summary. The body of the commit 50 | ## message will be displayed in the changelog without reformatting. 51 | 52 | 53 | ## 54 | ## ``ignore_regexps`` is a line of regexps 55 | ## 56 | ## Any commit having its full commit message matching any regexp listed here 57 | ## will be ignored and won't be reported in the changelog. 58 | ## 59 | ignore_regexps = [ 60 | r'@minor', r'!minor', 61 | r'@cosmetic', r'!cosmetic', 62 | r'@refactor', r'!refactor', 63 | r'@wip', r'!wip', 64 | r'^([cC]hg|[fF]ix|[nN]ew)\s*:\s*[p|P]kg:', 65 | r'^([cC]hg|[fF]ix|[nN]ew)\s*:\s*[d|D]ev:', 66 | r'^(.{3,3}\s*:)?\s*[fF]irst commit.?\s*$', 67 | ] 68 | 69 | 70 | ## ``section_regexps`` is a list of 2-tuples associating a string label and a 71 | ## list of regexp 72 | ## 73 | ## Commit messages will be classified in sections thanks to this. Section 74 | ## titles are the label, and a commit is classified under this section if any 75 | ## of the regexps associated is matching. 76 | ## 77 | section_regexps = [ 78 | ('New', [ 79 | r'^[nN]ew\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$', 80 | ]), 81 | ('Changes', [ 82 | r'^[cC]hg\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$', 83 | ]), 84 | ('Fix', [ 85 | r'^[fF]ix\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n]*)$', 86 | ]), 87 | 88 | ('Other', None ## Match all lines 89 | ), 90 | 91 | ] 92 | 93 | 94 | ## ``body_process`` is a callable 95 | ## 96 | ## This callable will be given the original body and result will 97 | ## be used in the changelog. 98 | ## 99 | ## Available constructs are: 100 | ## 101 | ## - any python callable that take one txt argument and return txt argument. 102 | ## 103 | ## - ReSub(pattern, replacement): will apply regexp substitution. 104 | ## 105 | ## - Indent(chars=" "): will indent the text with the prefix 106 | ## Please remember that template engines gets also to modify the text and 107 | ## will usually indent themselves the text if needed. 108 | ## 109 | ## - Wrap(regexp=r"\n\n"): re-wrap text in separate paragraph to fill 80-Columns 110 | ## 111 | ## - noop: do nothing 112 | ## 113 | ## - ucfirst: ensure the first letter is uppercase. 114 | ## (usually used in the ``subject_process`` pipeline) 115 | ## 116 | ## - final_dot: ensure text finishes with a dot 117 | ## (usually used in the ``subject_process`` pipeline) 118 | ## 119 | ## - strip: remove any spaces before or after the content of the string 120 | ## 121 | ## Additionally, you can `pipe` the provided filters, for instance: 122 | #body_process = Wrap(regexp=r'\n(?=\w+\s*:)') | Indent(chars=" ") 123 | #body_process = Wrap(regexp=r'\n(?=\w+\s*:)') 124 | #body_process = noop 125 | body_process = ReSub(r'((^|\n)[A-Z]\w+(-\w+)*: .*(\n\s+.*)*)+$', r'') | strip 126 | 127 | 128 | ## ``subject_process`` is a callable 129 | ## 130 | ## This callable will be given the original subject and result will 131 | ## be used in the changelog. 132 | ## 133 | ## Available constructs are those listed in ``body_process`` doc. 134 | subject_process = (strip | 135 | ReSub(r'^([cC]hg|[fF]ix|[nN]ew)\s*:\s*((dev|use?r|pkg|test|doc)\s*:\s*)?([^\n@]*)(@[a-z]+\s+)*$', r'\4') | 136 | ucfirst | final_dot) 137 | 138 | 139 | ## ``tag_filter_regexp`` is a regexp 140 | ## 141 | ## Tags that will be used for the changelog must match this regexp. 142 | ## 143 | tag_filter_regexp = r'^[0-9]+\.[0-9]+(\.[0-9]+)?$' 144 | 145 | 146 | ## ``unreleased_version_label`` is a string 147 | ## 148 | ## This label will be used as the changelog Title of the last set of changes 149 | ## between last valid tag and HEAD if any. 150 | unreleased_version_label = "%%version%% (unreleased)" 151 | 152 | 153 | ## ``output_engine`` is a callable 154 | ## 155 | ## This will change the output format of the generated changelog file 156 | ## 157 | ## Available choices are: 158 | ## 159 | ## - rest_py 160 | ## 161 | ## Legacy pure python engine, outputs ReSTructured text. 162 | ## This is the default. 163 | ## 164 | ## - mustache() 165 | ## 166 | ## Template name could be any of the available templates in 167 | ## ``templates/mustache/*.tpl``. 168 | ## Requires python package ``pystache``. 169 | ## Examples: 170 | ## - mustache("markdown") 171 | ## - mustache("restructuredtext") 172 | ## 173 | ## - makotemplate() 174 | ## 175 | ## Template name could be any of the available templates in 176 | ## ``templates/mako/*.tpl``. 177 | ## Requires python package ``mako``. 178 | ## Examples: 179 | ## - makotemplate("restructuredtext") 180 | ## 181 | output_engine = rest_py 182 | #output_engine = mustache("restructuredtext") 183 | #output_engine = mustache("markdown") 184 | #output_engine = makotemplate("restructuredtext") 185 | 186 | 187 | ## ``include_merge`` is a boolean 188 | ## 189 | ## This option tells git-log whether to include merge commits in the log. 190 | ## The default is to include them. 191 | include_merge = False 192 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .cache/ 3 | .idea/ 4 | .noseids 5 | build/ 6 | dist/ 7 | pyros.egg-info/ 8 | .tox/ 9 | -------------------------------------------------------------------------------- /.pyup.yml: -------------------------------------------------------------------------------- 1 | # configure updates globally 2 | # default: all 3 | # allowed: all, insecure, False 4 | update: all 5 | 6 | # configure dependency pinning globally 7 | # default: True 8 | # allowed: True, False 9 | pin: True 10 | 11 | # set the default branch 12 | # default: empty, the default branch on GitHub 13 | branch: master 14 | 15 | # update schedule 16 | # default: empty 17 | # allowed: "every day", "every week", .. 18 | schedule: "every week" 19 | 20 | # search for requirement files 21 | # default: True 22 | # allowed: True, False 23 | search: False 24 | 25 | # Specify requirement files by hand, default is empty 26 | # default: empty 27 | # allowed: list 28 | requirements: 29 | # These need to be manually pinned to match ROS distros versions 30 | - requirements/ROS/indigo.txt: 31 | # don't update dependencies, don't try to auto pin 32 | update: False 33 | pin: False 34 | - requirements/ROS/kinetic.txt: 35 | # don't update dependencies, don't try to auto pin 36 | update: False 37 | pin: False 38 | 39 | - requirements/dev.txt: 40 | # update all dependencies, use global 'pin' default 41 | update: all 42 | 43 | - requirements/tests.txt: 44 | # update all dependencies, use global 'pin' default 45 | update: all 46 | 47 | - requirements/tools.txt: 48 | # update all dependencies, never pin (dev will use whatever is latest for them) 49 | update: all 50 | pin: False 51 | 52 | 53 | # TODO : review tests nd default values after ROS things are out of the python repo 54 | 55 | # add a label to pull requests, default is not set 56 | # requires private repo permissions, even on public repos 57 | # default: empty 58 | label_prs: update 59 | 60 | # assign users to pull requests, default is not set 61 | # requires private repo permissions, even on public repos 62 | # default: empty 63 | assignees: 64 | - asmodehn 65 | 66 | # configure the branch prefix the bot is using 67 | # default: pyup- 68 | branch_prefix: pyup/ 69 | 70 | # set a global prefix for PRs 71 | # default: empty 72 | #pr_prefix: "Bug #12345" 73 | 74 | # allow to close stale PRs 75 | # default: True 76 | #close_prs: True -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: python 3 | 4 | branches: 5 | except: 6 | - gh-pages 7 | 8 | env: 9 | # These will be used to determine the proper version of our dependencies 10 | # We will NOT rely on a full ROS installation. 11 | - ROS_DISTRO=indigo 12 | - ROS_DISTRO=kinetic #TOX_PARAMS="--hypothesis-profile travis" 13 | 14 | # to get latest dependencies (not released in a ROS distro yet) 15 | - ROS_DISTRO=latest #TOX_PARAMS="--hypothesis-profile travis" 16 | 17 | python: 18 | - 2.7 19 | # Tests hang ! 20 | #- 3.4 21 | #- 3.5 22 | #- 3.6 23 | 24 | #- pypy 25 | #- pypy3 26 | 27 | install: 28 | - pip install tox tox-travis 29 | 30 | script: 31 | - tox 32 | 33 | notifications: 34 | email: false -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | 5 | 0.4.3 (2018-04-18) 6 | ------------------ 7 | - Disabling integration tests in python for now. [AlexV] 8 | - Removing ros interface as test requirement as there is no python 9 | package for it just yet. [AlexV] 10 | - Adding ros interface to testing requirements on ROS. [AlexV] 11 | - Moving tests outside of the package, to ease test dependency 12 | maintenance. [AlexV] 13 | - Adding pyup config. [AlexV] 14 | - Fixing tox config. [AlexV] 15 | - Improving testing by adding requirements matching rosdistro packages 16 | versions. [AlexV] 17 | - Fixing badge links. [AlexV] 18 | 19 | 20 | 0.4.2 (2018-04-17) 21 | ------------------ 22 | - V0.4.2. [AlexV] 23 | - Fixing pyros_common exceptions. [AlexV] 24 | - Removing QC badge. [AlexV] 25 | 26 | 27 | 0.4.1 (2017-05-08) 28 | ------------------ 29 | - V0.4.1. [alexv] 30 | - Adding pyros.server package to setup.py. [alexv] 31 | - Improving imports to rely on pyros_setup only for ros server test. 32 | [alexv] 33 | 34 | 35 | 0.4.0 (2017-05-08) 36 | ------------------ 37 | - V0.4.0. [alexv] 38 | - Removing catkin build files (should be in release repo). [alexv] 39 | - Package.xml -> 0.3.2. [alexv] 40 | - Fixing setup and examples using upcoming version of dependencies. 41 | [AlexV] 42 | - Added dynamic setup API on client. [AlexV] 43 | - Fixing usage of deprecated logging.warn. [AlexV] 44 | - Adding examples doc. [AlexV] 45 | - Fixes package names for tests to pass locally. [AlexV] 46 | - Moving context server in pyros main package from pyros-common. [AlexV] 47 | - Fixing package dependency names for new namespaced interface ROS 48 | packages. [AlexV] 49 | - Moving pyros.interfaces to pyros_interfaces in pyros_common repo. now 50 | using namespace pyros_interfaces as dependency. [AlexV] 51 | - WIP restructuring config and readding ctx_server here... [alexv] 52 | - Fixing tests with new structure. [alexv] 53 | - Moved a lot of things to pyros-common. [alexv] 54 | - Fixing tests. [alexv] 55 | - WIP : main with click. [alexv] 56 | - Extracting ros dependent part and restructure. [alexv] 57 | - Fixing doc to build without catkin. [alexv] 58 | - Fixing import of BaseInterface. [AlexV] 59 | - Fixing interface inheritance on the way to splitting mock and ros... 60 | [alexv] 61 | - Moving mock interface outside of ROS, to not depend on ROS for mocks. 62 | [alexv] 63 | - Adding rospublish command. [alexv] 64 | - Mutating into a pure python package. adding rosdevelop setup.py 65 | command. [alexv] 66 | 67 | 68 | 0.3.2 (2017-03-21) 69 | ------------------ 70 | - V0.3.2. [alexv] 71 | - Changing api for catkin_pip 0.2. [AlexV] 72 | - Now relying on catkin_pip >0.2. [alexv] 73 | 74 | 75 | 0.3.1 (2017-01-13) 76 | ------------------ 77 | - V0.3.1. [alexv] 78 | - Disabling cache test on jade since rocon_python_comms not available 79 | yet. [alexv] 80 | - Being less strict about mock depend. [alexv] 81 | - Fixing optional dependency for travis tests. [alexv] 82 | - Small comments for tests. [alexv] 83 | - Skipping travis test of rpm & debian branches. [alexv] 84 | - Removing old gone six submodule. [alexv] 85 | - Changed dependency to tblib third party released package to allow 86 | build for any ROS platform. [alexv] 87 | - Making python-mock a full dependency (used in package, for 88 | transitivity). commenting tblib, might not be needed. [alexv] 89 | 90 | 91 | 0.3.0 (2016-11-14) 92 | ------------------ 93 | - Fixed rospkg version. [alexv] 94 | - V0.3.0. [alexv] 95 | - Changing branch to master for all readme badges. [alexv] 96 | - Small file rename to match new name from pub and sub. [alexv] 97 | - Adding api subpackage to setup.py. [AlexV] 98 | - Moving all ros api calls into subpackage to make patching easier. 99 | cleaned up imports. [AlexV] 100 | - First version of rospy safe API. [AlexV] 101 | - Fixing node interface after pub/sub mixup. simplified stringpub and 102 | string sub tests. [alexv] 103 | - Fixing tests filenames. cleanup debug prints. [alexv] 104 | - Fixing cross logic between pubs ans subs. [alexv] 105 | - Improving subscriber / publisher inter interfacing... added timeout to 106 | connect a pub/sub. [alexv] 107 | - Now properly inverting (publisher_if is subscriber and vice versa) 108 | [alexv] 109 | - Fixing broken update loop of ros_interface. [alexv] 110 | - Skipping failing rosinterface test for now. passing fine 111 | independently, more investigation needed... [alexv] 112 | - Separating pubs and subs. needs pyros_test 0.0.6 for tests. [alexv] 113 | - Disabling cache builds on kinetic for now. [AlexV] 114 | 115 | rocon_python_comms is not available on kinetic yet. 116 | - Forcing install of debs for travis script. [alexv] 117 | - Now running same tests for python flow or installed catkin build. 118 | [alexv] 119 | - Improving travis build to test with cache as well... change version_eq 120 | to version_gte since buidfarm doesnt handle version_eq properly 121 | (yet?). [alexv] 122 | - Importing contextmanager from contextlib instead of decorator. [alexv] 123 | - Moving mockinterface into rosinterface.mock since design follows ROS 124 | concepts. fixed all tests. bumped pyros minor version to 0.3.0 because 125 | of all the changes... [alexv] 126 | - Merged testRosInterfaceNoCache and testRosInterfaceCache. fixed all 127 | issues. [alexv] 128 | - Fixed tests without cache. [alexv] 129 | - Basic usecase now working again with cache. needs lots of cleanup... 130 | [alexv] 131 | - Continuing changes in rosinterface, splitting service, topics and 132 | params interface pools now rosinterface tests all passing. [alexv] 133 | - Splitting baseinterface to simplify things. fixed mockinterface and 134 | tests. [alexv] 135 | - Various cleanups. [alexv] 136 | - Improved profiling script. [alexv] 137 | - Comments. [alexv] 138 | - Fixing bwcompat issues. dropping shutdown behavior fix for now. 139 | [alexv] 140 | - Improved management of interface topics and reference counting. still 141 | broken for multiprocess because shutdown is not working properly. 142 | [alexv] 143 | - Fixing params and services removal with cache diff input. improved 144 | topics interface creation and cleanup. [alexv] 145 | - Speeding up topic interfacing. [alexv] 146 | - Fixed logic for removing transients on difference update. now 147 | forwarding exception if param type not found small test improvements. 148 | [alexv] 149 | - Fixing param behavior in ros_interface and added unit tests. [alexv] 150 | - Fixing hybrid usecase of devel catkin workspace without ROS setup. 151 | [alexv] 152 | - Adding python-tblib as a ros dependency. [alexv] 153 | - Now using ros-shadow-fixed for testing with latest dependencies. 154 | [AlexV] 155 | - Making the travis_checks script switch to his own dir on startup. 156 | [alexv] 157 | - Fixing envvars checks for travis. made travis_checks.bash script 158 | executable. [alexv] 159 | - Fixing typos. [alexv] 160 | - Now travis tests with docker and on kinetic. [alexv] 161 | - Improving first dynamic ROS import to ros_interface. improved logging. 162 | some test clean up since we use python testing framework now. [alexv] 163 | - Fixing rospkg version. fixing setup.py commands for release flow. 164 | [alexv] 165 | 166 | 167 | 0.2.0 (2016-09-01) 168 | ------------------ 169 | - V0.2.0. [alexv] 170 | - Preparing release flow. cosmetics. [alexv] 171 | - Now fails with explanation if ConnectionCacheProxy not available in 172 | rocon_python_comms. [alexv] 173 | - Moving on with step by step rostesting and partial python testing, 174 | because of process conflicts. [alexv] 175 | - Making travis nose tests more verbose. [alexv] 176 | - Increased dependent version of pyros_setup. attempt fixing travis. 177 | [alexv] 178 | - Changed config behavior. now using pyros-setup default config. getting 179 | rid of complex default+override behavior for import config. improved 180 | logger. improved setup.py commands. [alexv] 181 | - Importing pyros_setup only when imports from ros_interface failed. 182 | [alexv] 183 | - Created deprecated decorator as util in pyros until we find better 184 | solution. [alexv] 185 | - Fixing dependency on pyzmp with strict version. removed useless env 186 | values for travis. [alexv] 187 | - Improved main init to import dependencies from python or from ROS 188 | packages. fixed check for unicode strings. started implementing 189 | CATKIN_PIP_NO_DEPS for testing. reviewing dependencies version. 190 | [alexv] 191 | - Improved travis test scripts from pyros-setup scripts. improved 192 | setup.py with publish method fixed python3 issues on pyros_client. 193 | [alexv] 194 | - Moved some dependencies out of pyros_setup, to not require pyros_setup 195 | if using ROS environment as usual. [alexv] 196 | - Describing improved repository structure. [alexv] 197 | - Improving release script. [AlexV] 198 | 199 | 200 | 0.0.9 (2016-08-25) 201 | ------------------ 202 | - Disabled pyrosROS test hanging on jenkins sometimes. [alexv] 203 | - Releasing 0.0.9 for gopher benevolent. [alexv] 204 | - Removing old gone six submodule. [alexv] 205 | 206 | 207 | 0.1.0 (2016-07-08) 208 | ------------------ 209 | - Regenerating full changelog. [AlexV] 210 | - Rosdep dependency is likely redundant with pypi package. [AlexV] 211 | - Fixing python mock version to be compatibel with trusty. [AlexV] 212 | - Revert "dropping installspace build. no ros-indigo deb package will be 213 | created. requirements are too high for trusty : six >= 1.9" [AlexV] 214 | 215 | This reverts commit 64a0688e6706424c3c9a3742f776fcb73e833fff. 216 | - Revert "downloading six >=1.9 for tests, ignoring system version" 217 | [AlexV] 218 | 219 | This reverts commit 946bf8df10ae50fcef8b77114521fcb861b31a56. 220 | - Dropping installspace build. no ros-indigo deb package will be 221 | created. requirements are too high for trusty : six >= 1.9. [AlexV] 222 | - Downloading six >=1.9 for tests, ignoring system version. [AlexV] 223 | - Adding pypi mock dependency. [AlexV] 224 | - Generating changelog in preparation for version 0.1.0. [alexv] 225 | - Reducing ros python dependencies since we now rely on catkin_pip. 226 | [alexv] 227 | - Cleanup debug log. [alexv] 228 | - Moved debug logging to special logdebug file to reduce terminal 229 | logspam. [alexv] 230 | - Fixing tests. [alexv] 231 | - Disabling some test to prevent catkin test hanging... but test pass 232 | when run without --with-xunit. probably a nose issue. [alexv] 233 | - Fix adding available services. quick fix on early topics detection to 234 | avoid dropping topic interface just after creation. now comparing 235 | local topic connection counter with global topic connection counter 236 | instead of always assuming 1. improved logging. [alexv] 237 | - Fixed checking for available transients. now doesnt have to be a dict, 238 | just an iterable. [alexv] 239 | - Now storing endpoints for topics in order to accurately detect lost 240 | topics when we get only endpoints diff from cache. WIP. some tests 241 | breaking now. [alexv] 242 | - WIP. attempting to fix diff behavior with cache in corner cases when 243 | things changing fast on the system. [alexv] 244 | - Changing static method used from class to class method used from self. 245 | [alexv] 246 | - Now using diff optimisation in connection_cache. [alexv] 247 | - Getting pyzmp 0.0.11 via dependencies to hopefully fix travis. not 248 | using requirements any longer since we dont have extra dependencies 249 | and catkin_pip_setup does install the package in dev mode. [alexv] 250 | - Fixing node behaviors with recent pyzmp. [alexv] 251 | - Reviewing how we use zmp nodes and improving tests... WIP. [alexv] 252 | - Fix adding available services. improved logging. [alexv] 253 | 254 | Conflicts: 255 | pyros/baseinterface/baseinterface.py 256 | pyros/rosinterface/ros_interface.py 257 | - Fixed checking for available transients. now doesnt have to be a dict, 258 | just an iterable. [alexv] 259 | 260 | Conflicts: 261 | pyros/rosinterface/ros_interface.py 262 | - Next TODO. first step to simplification. [alexv] 263 | - Removed useless None in get(smthg, None) [alexv] 264 | - Added interface cache tests to run by default. reverted debug long 265 | timeouts. [alexv] 266 | - Finished manual merging of connection_cache_diff_callback. fixed all 267 | RosInterfaceCache tests, but code really need refactoring... [alexv] 268 | - More changes from connection_cache_diff_callback branch. only 269 | ros_interface.py changes are left todo. [alexv] 270 | - Starting manual merge of connection_cache_diff_callback branch. 271 | [alexv] 272 | - Fixes for connection cache with diff optimisation. added pubsub wait 273 | for confirm from cache, but deleted pubsub report deleted before 274 | confirmation from cache. Not sure if it is the right choice, but extra 275 | care is needed when deleting... [alexv] 276 | - Fix tests for RosInterface especially with cache (but no diff optim) 277 | [alexv] 278 | - Adding yujin underlay as we need it for connectioncache message 279 | format. [alexv] 280 | - Fixing path to current workspace. [alexv] 281 | - Renaming catkin_pure_python to catkin_pip. [alexv] 282 | - Updating for catkin_pure_python 0.1.0. [AlexV] 283 | - Fixing various minor python issues. [AlexV] 284 | - Fixed service and topic type introspection. [alexv] 285 | - Fixing definitions to match new topic class structure. [alexv] 286 | - Fixing rostest call of testService.py. [alexv] 287 | - Locking version numbers for pyros-setup and pyros-test dependencies. 288 | [alexv] 289 | - Todo comments. py3 compat. [alexv] 290 | - Removed duplicated import. [AlexV] 291 | - Not installing pyros-setup from ROS package. pyros-setup should be 292 | useful only if run without ROS (directly from pip). [AlexV] 293 | - Fixing self tests. now using pyros_setup pip package. [alexv] 294 | - Adding nosemain for self test. [alexv] 295 | - Now using pyzmp package dependency instead of internal zmp sources. 296 | removed submodules. [alexv] 297 | - Now travis check python and ros workflows. [AlexV] 298 | - Moving to package v2. [alexv] 299 | - Replacing obsolete navi/semantic_locations by new 300 | /rocon/semantics/locations. [alexv] 301 | - Moved pyros and zmp sources, otherwise pyros was not find through egg 302 | link. [alexv] 303 | - Added version. fixed tests in cmakelists. added default config file, 304 | removed useless testing config. added entry point for selftests. added 305 | requirements devel dependency to pyros-setup. [alexv] 306 | - Cleaning up rosinterface __init__. now doing ros setup only in child 307 | node process, dynamically. parent process is isolated. [alexv] 308 | - Cleaning up imports and fixing tests. [alexv] 309 | - Refactored to add configuration at module, package and user levels. 310 | implified pyros-setup configuration from rosinterface. reviewed 311 | separation between node and interface to isolate all ros setup in 312 | child process. now doing ROS message conversion internally in 313 | rosinterface service and topic classes. fixed most tests. now uses six 314 | to improve python3 compatibility. [alexv] 315 | - Starting to adapt to new configuration from pyros-setup. [alexv] 316 | - Now using catkin_pure_python. [alexv] 317 | - Add Gitter badge. [The Gitter Badger] 318 | - Cosmetics, comments and small fixes... [alexv] 319 | - Readme regarding IoT. [alexv] 320 | - Cosmetics. [alexv] 321 | - Changing reinit method to a setup service. now reinitialize 322 | rosinterface everytime the list of services or topic passed by the 323 | user changes. refactor the base interface to hold local copy of system 324 | state. fix all tests. [alexv] 325 | - Added missing rosservice dependency. [alexv] 326 | - Fixing package dependencies for catkin. [alexv] 327 | - Fixing catkin build. [alexv] 328 | - Removing unused ROS service specifications. [alexv] 329 | - Improved exception handling. adding mock client to make unittests 330 | easy. cosmetics. [alexv] 331 | - Improved Readme. [AlexV] 332 | - Removing dynamic_reconfigure. [alexv] 333 | - Removed rocon feature. cleanup. [alexv] 334 | - Exposing servicecall timeout exception. cosmetics. [alexv] 335 | - Warn -> info when it's not meant to be alarming to the users. [Daniel 336 | Stonier] 337 | - Fixing log warn -> info for startup args. [alexv] 338 | - Fixme comments. [alexv] 339 | - Adding simple test to assert rospy potentially strange behaviors. 340 | separating cache and non cache tests. catching connection_cache proxy 341 | init timeout, showing error and disabling. [alexv] 342 | - Adding custom manager argument in basenode, and making shutdown 343 | possible override more obvious. [alexv] 344 | - ZMP : services and node advertisement now done in context managers. 345 | Node now support using custom context manager when starting in another 346 | process. cosmetics. [alexv] 347 | - Improving base support to pass diff instead of query full state 348 | everytime. now with callback called from connection cache proxy to 349 | only process list if change happens. [alexv] 350 | - Fixing reinit to be delayed if ros interface not ready yet. [alexv] 351 | - Fixing pyrosROS test with latest pyros_test. [alexv] 352 | - Adding pyrosRos test to catkin tests. [alexv] 353 | - Reiniting connection cache if dynamic_reconfigure disable/enable it. 354 | [alexv] 355 | - Using enable_cache in dynamic_reconfigure to be able to dynamically 356 | switch if needed. [alexv] 357 | - Fixed populating empty message instance. comments. [alexv] 358 | - Adding missing rosnode as test dependency. [AlexV] 359 | - Disabling roconinterface dynamic import. [AlexV] 360 | - Moving more nodes to pyros-test. [AlexV] 361 | - Moving nodes to pyros-test. skipping tests if connection_cache not 362 | found. [AlexV] 363 | - Better error message if tests are run from python without pyros-test 364 | installed in ROS env. [AlexV] 365 | - Using pyros_cfg and fix import in rocont interface, to run nosetests 366 | from python venv. [AlexV] 367 | - Added generated code for dynamic_reconfigure. [AlexV] 368 | - Adding requirements, fixing setup.py for setuptools. [AlexV] 369 | - Now allowing to delay the import of rosinterface subpackage and 370 | passing base_path to find ROS environment dynamically. [alexv] 371 | - Using ros-shadow-fixed for travis. [AlexV] 372 | - Cleaning up comments. [alexv] 373 | - Adding option to enable cache or not from rosparams. [alexv] 374 | - Ros_interface now using topics and service types from cacche if 375 | available, otherwise query one by one when needed. making sure cache 376 | process is started and stopped during the test to avoid scary harmless 377 | warnings. [alexv] 378 | - Improving tests. [alexv] 379 | - Using silent fallback for connectioncache proxy. [alexv] 380 | - Fixing dependencies in package.xml. [alexv] 381 | - Pyros now dependein on pyros_setup and pyros_test for tests. [alexv] 382 | - Pyros now depending on pyros_setup. [alexv] 383 | - Expose_transients_regex now relying on _transient_change_detect 384 | directly. small refactor to allow transient updates only with ROS 385 | system state differences. fixing mockinterface to call reinit only 386 | after setting up mock Added first connection_cache subscriber 387 | implementation to avoid pinging the master too often. WIP. [alexv] 388 | 389 | 390 | 0.0.8 (2016-01-25) 391 | ------------------ 392 | - Doing zmp tests one by one to workaround nose hanging bug with option 393 | --with-xunit. [alexv] 394 | - Making service and param new style classes. [alexv] 395 | - Fixing throttling to reinitialize last_update in basenode. [alexv] 396 | - Fixing a few quantifiedcode issues... [alexv] 397 | - ZMP node now passing timedelta to update. Pyros nodes now have a 398 | throttled_update method to control when heavy computation will be 399 | executed ( potentially not every update) [alexv] 400 | - Displaying name of ROS node in log when starting up. [alexv] 401 | - Mentioning dropping actions support in changelog. [alexv] 402 | - Overhauled documentation. [alexv] 403 | - Cosmetics. [alexv] 404 | - Exposing pyros service exceptions for import. [alexv] 405 | - Adding node with mute publisher for tests. [alexv] 406 | - Fixing basic test nodes return message type. cosmetics. [alexv] 407 | - Reviewing README. [alexv] 408 | - Changelog for 0.1.0. cosmetics. [alexv] 409 | - Migrated `%` string formating. [Cody] 410 | - Fixing badges after rename. [alexv] 411 | - Avoid mutable default arguments. [Cody] 412 | - Made namedtuple fields optional like for protobuf protocol. [alexv] 413 | - Fixing zmp tests with namedtuple protocol. [alexv] 414 | - Fixing catkin cmakelists after test rename. [alexv] 415 | - Making client exceptions also PyrosExceptions. [alexv] 416 | - Begining of implementation of slowservice node for test. not included 417 | in tests yet. [alexv] 418 | - Removed useless hack in travis cmds, fixed typo. [alexv] 419 | - Trying quick hack to fix travis build. [alexv] 420 | - Adding status message when creating linksto access catkin generated 421 | python modules. [alexv] 422 | - Adding zmp tests to catkin cmakelists. [alexv] 423 | - Added dummy file to fix catkin install. [alexv] 424 | - Small install and deps fixes. [alexv] 425 | - Simplifying traceback response code in node. [alexv] 426 | - Fixing unusable traceback usecase in zmp. [alexv] 427 | - Cosmetics. adding basemsg unused yet. [alexv] 428 | - Moving exception to base package, as they should be usable by the 429 | client of this package. [alexv] 430 | - Making pyros exceptions pickleable. minor fixes to ensure exception 431 | propagation. [alexv] 432 | - Comments. [alexv] 433 | - Ros_setup now use of install workspace optional. fixes problems 434 | running nodes ( which needs message types ) from nosetests. [alexv] 435 | - Added cleanup methods for transients. it comes in handy sometime ( for 436 | ROS topics for example ). [alexv] 437 | - Pretty print dynamic reconfigure request. [alexv] 438 | - Cleanup debug logging. [alexv] 439 | - Adding logic on name was not a good idea. breaks underlying systems 440 | relaying on node name like params for ROS. [alexv] 441 | - Removing name from argv, catching keyboard interrupt from pyros ros 442 | node. cosmetics. [alexv] 443 | - Increasing default timeouts for listing services call form pyros 444 | client. [alexv] 445 | - Fixed multiprocess mutli pyros conflict issues with topics with well 446 | known rosparam. now enforcing first part of node name. cosmetics. 447 | [alexv] 448 | - Removed useless logging. [alexv] 449 | - Adding basetopic and fixed topic detection in rosinterface. zmp 450 | service now excepting on timeout. [alexv] 451 | - Fixed exceptions handling and transfer. fixed serialization of 452 | services and topic classes for ROSinterface. [alexv] 453 | - Now reraise when transient type resolving or transient instance 454 | building fails. added reinit methods to list of node service to be 455 | able to change configuration without restarting the node ( usecase : 456 | dynamic reconfigure ) added option to PyrosROS node to start without 457 | dynamic reconfigure (useful for tests and explicit reinit) added some 458 | PyrosROS tests to check dynamic exposing of topics. cleaned up old 459 | rostful definitions. cosmetics. [alexv] 460 | - Cleaning up old action-related code. fixed mores tests. [alexv] 461 | - Fixing how to get topics and services list. commented some useless 462 | services ( interactions, ationcs, etc. ). [alexv] 463 | - Changing version number to 0.1.0. preparing for minor release. [alexv] 464 | - Refactoring ros emulated setup. [alexv] 465 | - Improving and fixing rosinterface tests. still too many failures with 466 | rostest. [alexv] 467 | - Fixing tests for Pyros client, and fixed Pyros client discovery logic. 468 | cosmetics. [alexv] 469 | - Making RosInterface a child of BaseInterface and getting all Topic and 470 | test services to pass. cosmetics. [alexv] 471 | - Improved test structure for rostest and nose to collaborate... [alexv] 472 | - WIP. reorganising tests, moved inside package, nose import makes it 473 | easy. still having problems with rostest. [alexv] 474 | - Fixing testTopic for rostest and nose. cosmetics. [alexv] 475 | - Finishing python package rename. [alexv] 476 | - Separated rospy / py trick from test. [alexv] 477 | - Fixing testRosInterface rostest to be runnable from python directly, 478 | and debuggable in IDE, by emulating ROS setup in testfile. [alexv] 479 | - Implemented functional API, abstract base interface class, 480 | mockinterface tests. [alexv] 481 | - Moving and fixing tests. [alexv] 482 | - Changing ros package name after repository rename. [alexv] 483 | - Fixing setup.py for recent catkin. [alexv] 484 | - Protecting rospy from unicode args list. [alexv] 485 | - Implemented transferring exception information via protobuf msg. 486 | readding tblib as dependency required for trusty. [alexv] 487 | - WIP. starting to change message to be able to just not send the 488 | traceback if tblib not found. [alexv] 489 | - Restructuring code and fixing all tests to run with new zmp-based 490 | implementation. [alexv] 491 | - Now able to use bound methods as services. [alexv] 492 | - Adding python-tblib as catkin dependency. [alexv] 493 | - Useful todo comments. [alexv] 494 | - Now using pickle is enough for serialization. getting rid of extra 495 | dill and funcsig dependencies. [alexv] 496 | - Not transmitting function signature anymore. not needed for python 497 | style function matching. [alexv] 498 | - Added cloudpickle in possible serializer comments. [alexv] 499 | - Now forwarding all exceptions in service call on node fixed all zmp 500 | tests. [alexv] 501 | - Fixing all zmp tests since we changed request into args and kwargs. 502 | [alexv] 503 | - Starting to use dill for serializing functions and params. [alexv] 504 | - Adding comments with more serialization lib candidates... [alexv] 505 | - WIP. looking for a way to enforce arguments type when calling a 506 | service, and parsing properly when returning an error upon exception. 507 | [alexv] 508 | - Getting message to work for both protobuf and pickle. Now we need to 509 | choose between tblib and dill for exception serialization. [alexv] 510 | - Adding dill as dependency. [alexv] 511 | - Multiprocess simple framework as separate zmp package. [alexv] 512 | - Comments. [alexv] 513 | - Transferring exceptions between processes. [alexv] 514 | - Fixing all service tests and deadlock gone. [alexv] 515 | - Improved service and node tests. still deadlock sometimes... [alexv] 516 | - Multiprocess service testing okay for discover. [alexv] 517 | - WIP. starting to use zmq for messaging. simpler than other 518 | alternatives. [alexv] 519 | - WIP implementing service. [alexv] 520 | - WIP adding mockframework a multiprocess communication framework. 521 | [alexv] 522 | - Adding mockparam. [alexv] 523 | - Adding code health badge. [alexv] 524 | - Adding requirements badge. [alexv] 525 | - Adding code quality badge. [alexv] 526 | - Adding echo tests for mocktopic and mockservice. [alexv] 527 | - Renaming populate / extract commands. [alexv] 528 | - Setting up custom message type and tests for mock interface. [alexv] 529 | - Fixing mockmessage and test. [alexv] 530 | - Improving mockmessage and tests. [alexv] 531 | - Started to build a mock interface, using python types as messages. 532 | This should help more accurate testing with mock. [alexv] 533 | - Adding six submodule. tblib might need it. otherwise it might come in 534 | useful anyway. [alexv] 535 | - Adding tblib to be able to transfer exception between processes. 536 | [alexv] 537 | - Fixing travis badge. [alexv] 538 | - Adding travis badge. [alexv] 539 | - Starting travis integration for autotest. [alexv] 540 | - Adding rostopic as a test_depend. [alexv] 541 | - Fixes to make this node work again with rostful cosmetics and 542 | cleanups. [alexv] 543 | - First implementation to expose params to python the same way as we do 544 | for topics and services. [alexv] 545 | 546 | 547 | 0.0.7 (2015-10-12) 548 | ------------------ 549 | - 0.0.7. [alexv] 550 | - Adding log to show rostful node process finishing. [alexv] 551 | - Change message content check to accept empty dicts. [Michal 552 | Staniaszek] 553 | - Fixing corner cases when passing None as message content. invalid and 554 | should not work. [alexv] 555 | - Fixing tests. and changed api a little. [alexv] 556 | - Removing useless fancy checks to force disabling rocon when set to 557 | false. updated rapp_watcher not working anymore. [AlexV] 558 | - Rocon_std_msgs changed from PlatformInfo.uri to MasterInfo.rocon_uri. 559 | [AlexV] 560 | - Send empty dicts instead of none from client. [Michal Staniaszek] 561 | - Service and topic exceptions caught and messages displayed. [Michal 562 | Staniaszek] 563 | - Fleshed out topic and service info tuples. [Michal Staniaszek] 564 | - Can check for rocon interface, get interactions. [Michal Staniaszek] 565 | - Listing functions for client, corresponding mock and node functions. 566 | [Michal Staniaszek] 567 | - Now passing stop_event as an argument to the spinner. cosmetics. 568 | [alexv] 569 | - Fix when running actual rostfulnode. [alexv] 570 | - Now running rostful_node in an separate process to avoid problems 571 | because of rospy.init_node tricks. [alexv] 572 | - Cosmetics. [alexv] 573 | - Improving how to launch rostest test. fixed hanging nosetest. hooking 574 | up new test to catkin. [alexv] 575 | - Force-delete for services, test for removal crash on expose. [Michal 576 | Staniaszek] 577 | 578 | Test service nodes added 579 | - Fix crash when reconfigure removes topics, started on unit tests. 580 | [Michal Staniaszek] 581 | - Fixing removing from dictionary topic_args. [alexv] 582 | - Stopped removal of slashes from front of topics. [Michal Staniaszek] 583 | - Fixed regex and add/remove issues with topics and services. [Michal 584 | Staniaszek] 585 | - Fixed topic deletion, multiple calls to add. [Michal Staniaszek] 586 | 587 | The interface now tracks how many calls have been made to the add function and 588 | ensures that topics are not prematurely deleted from the list. Actions also have 589 | a similar thing going on, but not sure if it works since they are unused. 590 | Services are unchanged. 591 | 592 | Ensured uniqueness of topics and services being passed into the system using sets. 593 | 594 | Removed unnecessary ws_name code. 595 | 596 | Issue #27. 597 | - Fix *_waiting list usage, service loss no longer permanent. [Michal 598 | Staniaszek] 599 | 600 | The lists *_waiting now contain topics, services or actions which we are 601 | expecting, but do not currently exist. Once it comes into existence, we remove 602 | it from this list. 603 | 604 | When services disconnect, their loss is no longer permanent. This had to do with 605 | the services being removed and not added to the waiting list. 606 | 607 | Fixes issue #21. 608 | - Full regex, fixed reconfigure crash. [Michal Staniaszek] 609 | 610 | Can now use full regex in topic or service strings to match incoming strings. 611 | 612 | Fixed crash when dynamic reconfigure receives an invalid string 613 | - Strings with no match characters don't add unwanted topics. [Michal 614 | Staniaszek] 615 | 616 | Regex fixed with beginning and end of line expected, previously would allow a 617 | match anywhere in the string. 618 | 619 | Issue #17. 620 | - Removed separate lists for match strings. [Michal Staniaszek] 621 | - Remove printing, unnecessary adding to _args arrays. [Michal 622 | Staniaszek] 623 | - Adding wildcard * for exposing topics or services. [Michal Staniaszek] 624 | 625 | Implementation should be such that other match characters can be easily added if 626 | necessary. 627 | 628 | Fixes issue #17. 629 | - Added TODO. [alexv] 630 | - Added exception catching for when rocon interface is not available. 631 | [Michal Staniaszek] 632 | - Added important technical TODO. [alexv] 633 | - Fixing bad merge. [alexv] 634 | - Fixing unitests after merge. [AlexV] 635 | - Quick fix to keep disappeared topics around, waiting, in case they 636 | come back up... [alexv] 637 | - Turning off consume/noloss behavior. should not be the default. should 638 | be in parameter another way to expose topics. [AlexV] 639 | - Allowing to call a service without any request. same as empty request. 640 | [AlexV] 641 | - Keeping topics alive even after they disappear, until all messages 642 | have been read... WIP. [AlexV] 643 | - Preparing for release 0.0.6. setup also possible without catkin. 644 | [AlexV] 645 | - Changing rostful node design to match mock design. [AlexV] 646 | - Fixing RostfulCtx with new Mock design. added unittest file. [AlexV] 647 | - Improved interface of rostful client. added unit tests for 648 | rostfulClient. [AlexV] 649 | - Improved interface of rostful mock, now async_spin return the pipe 650 | connection. added more unit tests for rostful mock. [AlexV] 651 | - Added rostful mock object ( useful if no ROS found ). improved 652 | structure and added small unit test. [AlexV] 653 | - Changing cfg file name to fix install. [AlexV] 654 | - Comments TODO to remember to fix hack. [AlexV] 655 | - Tentative fix of cfg... comments. [AlexV] 656 | - Adding python futures as dependency. [AlexV] 657 | - Commenting out icon image. no cache home on robot. need to find a new 658 | strategy. [AlexV] 659 | - Removed useless broken services. [AlexV] 660 | - Fixing catkin_make install with dynamic reconfigure. [AlexV] 661 | - Adding bloom release in release process to sync with pypi release. 662 | [AlexV] 663 | - Fixes for release and cosmetics. [AlexV] 664 | - Preparing pypi release. [AlexV] 665 | - Improving rostful node API. Adding rostful pipe client and python pipe 666 | protocol. removed redundant ros services. [AlexV] 667 | - Simplifying rapp start and stop by using rapp_watcher methods. [AlexV] 668 | - Now starting and stopping rapp. still ugly. [AlexV] 669 | - Fixes to get rocon features to work again. [AlexV] 670 | 671 | 672 | 0.0.3 (2015-07-01) 673 | ------------------ 674 | - Preparing pypi release. small fix. [AlexV] 675 | - Adding helper services to access Rosful node from a different process. 676 | Hacky, working around a limitation of rospy ( cannot publish on a 677 | topic created in a different process for some reason...). Proper 678 | design would be to call directly the python method ( work with 679 | services - node_init not needed ) [AlexV] 680 | - Small cleanup. [AlexV] 681 | - Adding context manager for rospy.init_node and rospy.signal_shutdown. 682 | No ROS signal handlers anymore. Cleanup properly done when program 683 | interrupted. [AlexV] 684 | - Playing with signal handlers... [AlexV] 685 | - Improved test. but topic interface not symmetric. needs to deeply test 686 | message conversion. [AlexV] 687 | - Small fixes and first working test to plug on existing topic. [AlexV] 688 | - Adding first copy from rostful. splitting repo in 2. [AlexV] 689 | - Initial commit. [AlexV] 690 | 691 | 692 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, AlexV 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of rostful-node nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include requirements.txt 2 | include package.xml -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Pyros 2 | ===== 3 | 4 | .. image:: https://badges.gitter.im/asmodehn/pyros.svg 5 | :alt: Join the chat at https://gitter.im/asmodehn/pyros 6 | :target: https://gitter.im/asmodehn/pyros?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge 7 | 8 | .. image:: https://travis-ci.org/pyros-dev/pyros.svg?branch=master 9 | :target: https://travis-ci.org/pyros-dev/pyros 10 | 11 | .. image:: https://requires.io/github/pyros-dev/pyros/requirements.svg?branch=master 12 | :target: https://requires.io/github/pyros-dev/pyros/requirements/?branch=master 13 | :alt: Requirements Status 14 | 15 | .. image:: https://landscape.io/github/pyros-dev/pyros/master/landscape.svg?style=flat 16 | :target: https://landscape.io/github/pyros-dev/pyros/master 17 | :alt: Code Health 18 | 19 | 20 | - If you are a `ROS`_ developer and want to use python inside `ROS`_, you can stop reading and go have a look there instead https://github.com/ros/ros_comm 21 | 22 | - If you are a Python developer and are curious about how you can plug a robot in your existing System using python, then keep on reading. 23 | 24 | Pyros is a multiprocess interface python package. 25 | 26 | It is not meant to run by itself, instead it should be used as a client library on one side, 27 | connecting to a specific process (`ROS node`_) it spawns inside your multiprocess system (`ROS`_) on the other side, in order to interface with it. 28 | 29 | `ROS`_ is the main application of pyros, however its abstract base class design aim at developing complete abstraction of such distributed systems and interface with other kinds of multiprocess systems. 30 | 31 | This package ultimately doesn't need `ROS`_ to be able to run and perform basic selftests on it. 32 | When using the ros interface however, your `ROS`_ configuration will be dynamically detected by `pyros-setup`_ and setup under the hood. 33 | 34 | Do not hesitate to get in touch by leaving an issue, if you think Pyros can help interfacing the multiprocess system you need to communicate with. 35 | 36 | Repository structure 37 | -------------------- 38 | 39 | This repository has a few main branches: 40 | 41 | - master : main branch, python dev workflow, releasing version tags into a pip package. 42 | - indigo-devel : current indigo-based ongoing development. catkin dev workflow. 43 | - indigo : current indigo-based release (ROS pkg - tags attempting to match) 44 | - : current -based release (ROS pkg) 45 | 46 | Apart from these we follow a `feature branching workflow `_ 47 | 48 | WARNING: This repository structure is currently being implemented... 49 | 50 | 51 | ROS usage 52 | --------- 53 | Pyros is a blanket under which all the ROS tricks can remain hidden, and not break your usual python workflows. 54 | 55 | ROS needs to be installed on your machine for the rosinterface to work. 56 | All system & ROS dependencies need to match pyros expectations when launching Pyros from Python. 57 | Pyros currently cannot verify your current system and certify compatibility. 58 | 59 | This package includes : 60 | 61 | - a rosnode that will be launched in a separate process to maintain isolation. 62 | - a client library to connect to this node and interface with the `ROS`_ system. 63 | 64 | Standard python packages can then use pyros to access the `ROS`_ system via pyros pure python API. 65 | That means each package depending on pyros will create their own ROS node. 66 | It is intended so that the configuration ( what is exposed or not ) can be different for each one of them. 67 | Be careful in settings where we have multiple similar clients ( like web server scaling up, celery multiprocess worker, etc. ), only one pyros node is needed per client type... 68 | 69 | Why Pyros ? 70 | ^^^^^^^^^^^ 71 | Historically Pyros spawned from `rostful`_. 72 | With multiple multiprocess systems like `celery`_ and `flask`_ needing to communicate with ROS underneath, it became necessary to implement a generic python interface that could be used from any usual python software, the way a python developer would expect it. 73 | 74 | `rospy`_ is an interface to python inside ROS, but it also enforces some behaviors that are specific to ROS : 75 | 76 | - expect the whole ROS environment is already setup before start (``source workspace/devel/setup.bash``) 77 | - initializing a node is mandatory to communicate on topics (but not services) 78 | - only one node can be initialized once per process 79 | - some global side effects can break things around. 80 | 81 | These are not a problem to use python as a language inside a `ROS`_ environment, always using `rospy`_. But they make interfacing a `ROS`_ system with existing python packages much more tricky than needed, and often the outcome is not quite the expected one : 82 | 83 | - Most web servers spawn multiple processes to handle concurrent requests, and an interpreter is loaded in each process, which also has the WSGI application executed for each request. That means, if you use `rospy`_ directly you end up with as many nodes as there is web processes (at best). Exactly duplicated nodes which you don't need in your `ROS`_ environment. Also these can take time to start, become inconsistent after a while, etc. This is not what a developer working on a robot wants to happen. 84 | - Custom multiprocess software (like `celery`_) have their own expectation and complex setup regarding global state and how processes are managed by the application, between the import from multiple places, multiple launches, and the way memory is managed. Mixing this with the way `rospy`_ does things is just a recipe for disaster. 85 | 86 | Pyros solves these problems by : 87 | 88 | - dynamically loading `ROS`_ environment on python import (with `pyros-setup`_). 89 | - not relying on `ROS`_ to implement multiprocess communications. 90 | - keeping each multiprocess system implementation isolated in their own process. 91 | 92 | Supported distributions currently 93 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 94 | 95 | - ROS : Indigo 96 | 97 | This package can also be used from source as a normal ROS package. 98 | Add it to your ROS workspace as a usual repository and everything should work. 99 | If it doesn't work from source as you would expect it, please submit an issue. 100 | 101 | Coming up soon 102 | ^^^^^^^^^^^^^^ 103 | 104 | - [ ] a pip package is available to be installed from source (works via rosdep). 105 | - [ ] an ubuntu (virtual env embedded) is provided to install pyros on your Ubuntu Trusty to use pyros from binary. 106 | 107 | => If you want to build a binary package depending on pyros, you should rely on the pip dependency mechanism and embed the appropriate version into your deb (virtual env embedded). 108 | 109 | Roadmap 110 | ------- 111 | 112 | We should do what we can to catch up on IoT platforms design. 113 | Although Pyros focus on pure python design, another package depending on it should enable ROS systems to connect as device to these IoT platforms. 114 | 115 | Currently Rostful provide HTTP connection to your robot. 116 | For example another project could provide MQTT connection to your robot, following amazon choice of using MQTT (OASIS Standard). 117 | 118 | IoT platforms design : 119 | 120 | - Amazon : http://docs.aws.amazon.com/iot/latest/developerguide/protocols.html 121 | 122 | Will NOT do in pyros 123 | -------------------- 124 | - Support for Actions (http://wiki.ros.org/actionlib) in pyros itself. 125 | 126 | Actions are built upon multiple ROS topics, and can be interfaced like that, 127 | by writing an action client on the other hand ( pure python, javascript on top of rostful, etc. ), 128 | with pyros just forwarding all the required topics. 129 | 130 | If this statement happens to be not true, this can be reconsidered. 131 | Additionally when interfacing with other systems "outside" of ROS, services are probably what you want, 132 | maybe also topics sometimes, but probably not actions. 133 | And actions can probably be implemented with just only a couple of services. 134 | The problem here is likely the latency + communication rate that will, anyway, be hard to maintain while moving through layers to go outside of `ROS`_. 135 | 136 | - Support for Rocon (http://wiki.ros.org/rocon) in pyros itself. 137 | 138 | Although early versions of pyros were supporting rocon features (Rapps, interactions, etc.), the maintenance effort required is too high right now, and anyway it is probably better to have a more solid pyros available to allow people to develop packages on top of pyros to match Rocon features from outside of ROS, if the need arises. 139 | 140 | => We probably will not support anything that is not part of the core of ROS. As long as implementation in another python package is possible using pyros. 141 | 142 | .. _ROS : http://wiki.ros.org/ 143 | .. _ROS node : http://wiki.ros.org/Nodes 144 | .. _pyros-setup : https://github.com/asmodehn/pyros-setup 145 | .. _rostful : https://github.com/asmodehn/rostful 146 | .. _rospy : https://github.com/ros/ros_comm/tree/indigo-devel/clients/rospy 147 | .. _celery : https://github.com/celery/celery 148 | .. _flask : https://github.com/mitsuhiko/flask 149 | -------------------------------------------------------------------------------- /doc/changelog_link.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../CHANGELOG.rst 2 | -------------------------------------------------------------------------------- /doc/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # pyros documentation build configuration file 4 | # 5 | # This file is execfile()d with the current directory set to its 6 | # 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 | # If extensions (or modules to document with autodoc) are in another directory, 15 | # add these directories to sys.path here. If the directory is relative to the 16 | # documentation root, use os.path.abspath to make it absolute, like shown here. 17 | 18 | ## get catkin package information 19 | # import os 20 | # import catkin_pkg.package 21 | # catkin_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 22 | # catkin_package = catkin_pkg.package.parse_package( 23 | # os.path.join(catkin_dir, catkin_pkg.package.PACKAGE_MANIFEST_FILENAME)) 24 | # Ref : https://packaging.python.org/single_source_version/#single-sourcing-the-version 25 | with open('../pyros/_version.py') as vf: 26 | exec(vf.read()) 27 | 28 | # -- General configuration --------------------------------------------------- 29 | 30 | # If your documentation needs a minimal Sphinx version, state it here. 31 | #needs_sphinx = '1.0' 32 | 33 | # Add any Sphinx extension module names here, as strings. They can be 34 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 35 | # ones. 36 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 37 | 'sphinx.ext.todo', 'sphinx.ext.viewcode', 'sphinx.ext.graphviz'] 38 | 39 | # List automatically-documented module members in source code order. 40 | autodoc_member_order = 'bysource' 41 | 42 | # Add any paths that contain templates here, relative to this directory. 43 | templates_path = ['_templates'] 44 | 45 | # The suffix of source filenames. 46 | source_suffix = '.rst' 47 | 48 | # The encoding of source files. 49 | #source_encoding = 'utf-8-sig' 50 | 51 | # The master toctree document. 52 | master_doc = 'index' 53 | 54 | # General information about the project. 55 | project = u'pyros' 56 | copyright = u'2015, AlexV' 57 | 58 | # The version info for the project you're documenting, acts as replacement for 59 | # |version| and |release|, also used in various other places throughout the 60 | # built documents. 61 | # 62 | # The short X.Y version. 63 | version = __version__ 64 | # The full version, including alpha/beta/rc tags. 65 | release = __version__ 66 | 67 | # The language for content autogenerated by Sphinx. Refer to documentation 68 | # for a list of supported languages. 69 | #language = None 70 | 71 | # There are two options for replacing |today|: either, you set today to some 72 | # non-false value, then it is used: 73 | #today = '' 74 | # Else, today_fmt is used as the format for a strftime call. 75 | #today_fmt = '%B %d, %Y' 76 | 77 | # List of patterns, relative to source directory, that match files and 78 | # directories to ignore when looking for source files. 79 | exclude_patterns = ['weblinks.rst'] 80 | 81 | # The reST default role (used for this markup: `text`) to use for all 82 | #documents. default_role = None 83 | 84 | # If true, '()' will be appended to :func: etc. cross-reference text. 85 | #add_function_parentheses = True 86 | 87 | # If true, the current module name will be prepended to all description 88 | # unit titles (such as .. function::). 89 | #add_module_names = True 90 | 91 | # If true, sectionauthor and moduleauthor directives will be shown in the 92 | # output. They are ignored by default. 93 | #show_authors = False 94 | 95 | # The name of the Pygments (syntax highlighting) style to use. 96 | pygments_style = 'sphinx' 97 | 98 | # A list of ignored prefixes for module index sorting. 99 | #modindex_common_prefix = [] 100 | 101 | 102 | # -- Options for HTML output ----------------------------------------------- 103 | 104 | # The theme to use for HTML and HTML Help pages. See the documentation for 105 | # a list of builtin themes. 106 | html_theme = 'nature' 107 | 108 | # Theme options are theme-specific and customize the look and feel of a theme 109 | # further. For a list of options available for each theme, see the 110 | # documentation. 111 | #html_theme_options = {} 112 | 113 | # Add any paths that contain custom themes here, relative to this directory. 114 | #html_theme_path = [] 115 | 116 | # The name for this set of Sphinx documents. If None, it defaults to 117 | # " v documentation". 118 | #html_title = None 119 | 120 | # A shorter title for the navigation bar. Default is the same as html_title. 121 | #html_short_title = None 122 | 123 | # The name of an image file (relative to this directory) to place at the top 124 | # of the sidebar. 125 | #html_logo = None 126 | 127 | # The name of an image file (within the static path) to use as favicon of the 128 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 129 | # pixels large. 130 | #html_favicon = None 131 | 132 | # Add any paths that contain custom static files (such as style sheets) here, 133 | # relative to this directory. They are copied after the builtin static files, 134 | # so a file named "default.css" will overwrite the builtin "default.css". 135 | #html_static_path = ['_static'] 136 | 137 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 138 | # using the given strftime format. 139 | #html_last_updated_fmt = '%b %d, %Y' 140 | 141 | # If true, SmartyPants will be used to convert quotes and dashes to 142 | # typographically correct entities. 143 | #html_use_smartypants = True 144 | 145 | # Custom sidebar templates, maps document names to template names. 146 | #html_sidebars = {} 147 | 148 | # Additional templates that should be rendered to pages, maps page names to 149 | # template names. 150 | #html_additional_pages = {} 151 | 152 | # If false, no module index is generated. 153 | #html_domain_indices = True 154 | 155 | # If false, no index is generated. 156 | #html_use_index = True 157 | 158 | # If true, the index is split into individual pages for each letter. 159 | #html_split_index = False 160 | 161 | # If true, links to the reST sources are added to the pages. 162 | #html_show_sourcelink = True 163 | 164 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 165 | #html_show_sphinx = True 166 | 167 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 168 | #html_show_copyright = True 169 | 170 | # If true, an OpenSearch description file will be output, and all pages will 171 | # contain a tag referring to it. The value of this option must be the 172 | # base URL from which the finished HTML is served. 173 | #html_use_opensearch = '' 174 | 175 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 176 | #html_file_suffix = None 177 | 178 | # Output file base name for HTML help builder. 179 | htmlhelp_basename = 'pyros_pydoc' 180 | 181 | 182 | # -- Options for LaTeX output ------------------------------------------------ 183 | 184 | latex_elements = { 185 | # The paper size ('letterpaper' or 'a4paper'). 186 | #'papersize': 'letterpaper', 187 | 188 | # The font size ('10pt', '11pt' or '12pt'). 189 | #'pointsize': '10pt', 190 | 191 | # Additional stuff for the LaTeX preamble. 192 | #'preamble': '', 193 | } 194 | 195 | # Grouping the document tree into LaTeX files. List of tuples (source 196 | # start file, target name, title, author, documentclass 197 | # [howto/manual]). 198 | latex_documents = [ 199 | ('index', 'pyros.tex', 200 | u'Pyros.', 201 | u'AlexV', 'manual'), 202 | ] 203 | 204 | # The name of an image file (relative to this directory) to place at the top of 205 | # the title page. 206 | #latex_logo = None 207 | 208 | # For "manual" documents, if this is true, then toplevel headings are parts, 209 | # not chapters. 210 | #latex_use_parts = False 211 | 212 | # If true, show page references after internal links. 213 | #latex_show_pagerefs = False 214 | 215 | # If true, show URL addresses after external links. 216 | #latex_show_urls = False 217 | 218 | # Documents to append as an appendix to all manuals. 219 | #latex_appendices = [] 220 | 221 | # If false, no module index is generated. 222 | #latex_domain_indices = True 223 | 224 | 225 | # -- Options for manual page output ------------------------------------------ 226 | 227 | # One entry per manual page. List of tuples 228 | # (source start file, name, description, authors, manual section). 229 | man_pages = [ 230 | ('index', 'pyros', 231 | u'Pyros', 232 | [u'AlexV'], 1) 233 | ] 234 | 235 | # If true, show URL addresses after external links. 236 | #man_show_urls = False 237 | 238 | 239 | # -- Options for Texinfo output ---------------------------------------------- 240 | 241 | # Grouping the document tree into Texinfo files. List of tuples 242 | # (source start file, target name, title, author, 243 | # dir menu entry, description, category) 244 | texinfo_documents = [ 245 | ('index', 'pyros', 246 | u'pyros Documentation', 247 | u'AlexV', 'pyros', 'pyros.', 248 | 'Miscellaneous'), 249 | ] 250 | 251 | # Documents to append as an appendix to all manuals. 252 | #texinfo_appendices = [] 253 | 254 | # If false, no module index is generated. 255 | #texinfo_domain_indices = True 256 | 257 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 258 | #texinfo_show_urls = 'footnote' 259 | 260 | 261 | # Example configuration for intersphinx: refer to the Python standard library. 262 | intersphinx_mapping = {'http://docs.python.org/': None} 263 | -------------------------------------------------------------------------------- /doc/index.rst: -------------------------------------------------------------------------------- 1 | .. _index: 2 | 3 | Pyros 4 | ===== 5 | 6 | This `Python package`_ and `ROS`_ package interface multiple multi-process systems. 7 | 8 | For example you can have a ROS system on one side, with his different nodes, services, etc. 9 | and a python multiprocess system (celery, tornado, etc.) on the other side, with also his own nodes, services, etc. 10 | 11 | Pyros allows them to communicate, while keeping each other isolated in their own process context. 12 | 13 | If you want to know more about how Pyros does this, you can have a look at the :ref:`overview`. 14 | 15 | .. include:: weblinks.rst 16 | 17 | 18 | Contents: 19 | 20 | .. toctree:: 21 | :maxdepth: 2 22 | 23 | readme_link 24 | overview 25 | roadmap 26 | pyros_internals 27 | zmp_internals 28 | changelog_link 29 | 30 | Indices and tables 31 | ================== 32 | 33 | * :ref:`genindex` 34 | * :ref:`modindex` 35 | -------------------------------------------------------------------------------- /doc/overview.rst: -------------------------------------------------------------------------------- 1 | .. _overview: 2 | 3 | Overview 4 | ======== 5 | 6 | `Pyros`_ is connecting multiple processes together, from different "multiprocess environments" while assuming the strict minimum about these environments. 7 | The main focus is interfacing Python multiprocess environment to `ROS`_ multiprocess environment, but we should work towards solving any kind of multiprocess bridging. 8 | 9 | A process is supposed to be one program, isolated from all others, so how can you write a program that interface two programs without using one of the underlying mutliprocess system ? 10 | It s a chicken and egg problem : to be able to have one set of processes talk to another set of processes, you need to have a multiprocess framework that enable interprocess communication. 11 | 12 | `Pyros`_ solves that problem by providing his own multiprocess model, that strives to be minimal, yet complete enough to be able to solve any problem that is traditionally solved with `imperative`_ or `functional`_ `paradigms`_. 13 | All you might want to be able to do with a multiprocess software should be doable with `Pyros`_. 14 | 15 | Standing on the shoulder of giants 16 | ---------------------------------- 17 | 18 | We are leveraging existing technologies, and concepts : 19 | 20 | - Multiprocess is done by `python.multiprocessing`_ . Eventually this can be extended to support other concurrency models, following a similar API : 21 | 22 | - Threads (with `python.threading`_) (TO COME) 23 | - monothread (TO COME : Entities as understood from `Entity Component Systems`_ design) 24 | 25 | - Communication is handled by `pyzmq`_ and can be done via multiple channels : 26 | 27 | - OS pipes 28 | - network sockets 29 | - shared memory (TO COME) 30 | 31 | - Serialization is done by `pickle`_, with planning to plug any other serialization library that can support the same pickle API: 32 | 33 | - `jsonpickle`_ (TO COME) 34 | - etc. 35 | 36 | Assumptions 37 | ----------- 38 | We are working based on the assumption that there is a minimal general-purpose distributed computing model, able to solve all traditionally solvable computational problems. 39 | While this model is not clearly define theoretically (if you know one, let `me`_ know !), a big part of the work is identifying which concepts are present in the many multiprocess framework existing and extract the lowest common denominator from them. 40 | 41 | `Functional programming`_ can completely abstract the underlying realities of computers for the sake of nice algorithm. 42 | While it can be useful in some cases, most real world applications still need to care about how they run, and developers, as humans, naturally think in the "Subject Action Object" form. 43 | On a running system, the process is the subject, and therefore abstracting it will remove a concept that is useful for most developer while thinking about his application. 44 | 45 | Currently our multiprocessing model is made of : 46 | 47 | - Nodes : Think web servers, think one command line process reading from a unix pipe and writing into a file, think `stream processing`_, think `dataflow programming`_. 48 | 49 | - Services : think `REST`_ API backend, think `remote procedure call`_, think `subroutine`_, synchronous or `asynchronous`_ from the client point of view. 50 | 51 | - Params : these are here to be able to interface params from ROS. But ultimately there should be some concept of global state, a set of `global variables`_. 52 | 53 | While this is not strictly needed ( functional languages dont have that ), it is widely used, and not having it can make some simple task more complicated than needed. 54 | The current plan is to provide some resilient distributed global state by implementing a `consensus`_ algorithm like `raft`_ in multiprocessing python. Using it in an appliation is not mandatory, and not optimum, but it will be stable and usable from any node, without single point of failure. 55 | 56 | - Something else... : There is a concept missing here, that is the complementary of a service. 57 | 58 | Something that always broadcast data, where loss is acceptable, and that can reach fast transfer rate. 59 | ROS has Topics. But Topics are actually not really useful, since you always need to worry about the endpoints, 60 | the connection ends from publisher side or from subscriber side. We need a concept that can interface with topics, 61 | yet that doesnt require people to think about connection endpoints. 62 | This is probably the type of communication used for stream/dataflow programming, however most implementation do that inside one process and we need to do that between processes. 63 | 64 | Note : This will evolve as the software, and our understanding of complex multiprocess systems evolves. 65 | 66 | 67 | 68 | .. include:: weblinks.rst -------------------------------------------------------------------------------- /doc/pyros_internals.rst: -------------------------------------------------------------------------------- 1 | Pyros 2 | ========= 3 | 4 | 5 | .. automodule:: pyros.exceptions 6 | :members: 7 | :undoc-members: 8 | :inherited-members: 9 | 10 | .. automodule:: pyros.pyros_client 11 | :members: 12 | :undoc-members: 13 | :inherited-members: 14 | 15 | .. automodule:: pyros.pyros_ctx_server 16 | :members: 17 | :undoc-members: 18 | :inherited-members: 19 | 20 | .. automodule:: pyros.pyros_prtcl 21 | :members: 22 | :undoc-members: 23 | :inherited-members: 24 | 25 | baseinterface 26 | ------------- 27 | 28 | .. automodule:: pyros.baseinterface.baseinterface 29 | :members: 30 | :undoc-members: 31 | :inherited-members: 32 | 33 | .. automodule:: pyros.baseinterface.basemsg 34 | :members: 35 | :undoc-members: 36 | :inherited-members: 37 | 38 | .. automodule:: pyros.baseinterface.basenode 39 | :members: 40 | :undoc-members: 41 | :inherited-members: 42 | 43 | .. automodule:: pyros.baseinterface.baseservice 44 | :members: 45 | :undoc-members: 46 | :inherited-members: 47 | 48 | .. automodule:: pyros.baseinterface.basetopic 49 | :members: 50 | :undoc-members: 51 | :inherited-members: 52 | 53 | mockinterface 54 | ------------- 55 | 56 | .. automodule:: pyros.mockinterface.exceptions 57 | :members: 58 | :undoc-members: 59 | :inherited-members: 60 | 61 | .. automodule:: pyros.mockinterface.mockinterface 62 | :members: 63 | :undoc-members: 64 | :inherited-members: 65 | 66 | .. automodule:: pyros.mockinterface.mockmessage 67 | :members: 68 | :undoc-members: 69 | :inherited-members: 70 | 71 | .. automodule:: pyros.mockinterface.mocknode 72 | :members: 73 | :undoc-members: 74 | :inherited-members: 75 | 76 | .. automodule:: pyros.mockinterface.mockparam 77 | :members: 78 | :undoc-members: 79 | :inherited-members: 80 | 81 | .. automodule:: pyros.mockinterface.mockservice 82 | :members: 83 | :undoc-members: 84 | :inherited-members: 85 | 86 | .. automodule:: pyros.mockinterface.mocktopic 87 | :members: 88 | :undoc-members: 89 | :inherited-members: 90 | 91 | 92 | rosinterface 93 | ------------ 94 | 95 | 96 | .. automodule:: pyros.rosinterface.exceptions 97 | :members: 98 | :undoc-members: 99 | :inherited-members: 100 | 101 | .. automodule:: pyros.rosinterface.ros_interface 102 | :members: 103 | :undoc-members: 104 | :inherited-members: 105 | 106 | .. automodule:: pyros.rosinterface.message_conversion 107 | :members: 108 | :undoc-members: 109 | :inherited-members: 110 | 111 | .. automodule:: pyros.rosinterface.pyros_ros 112 | :members: 113 | :undoc-members: 114 | :inherited-members: 115 | 116 | .. automodule:: pyros.rosinterface.param 117 | :members: 118 | :undoc-members: 119 | :inherited-members: 120 | 121 | .. automodule:: pyros.rosinterface.service 122 | :members: 123 | :undoc-members: 124 | :inherited-members: 125 | 126 | .. automodule:: pyros.rosinterface.topic 127 | :members: 128 | :undoc-members: 129 | :inherited-members: 130 | 131 | 132 | -------------------------------------------------------------------------------- /doc/readme_link.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../README.rst 2 | -------------------------------------------------------------------------------- /doc/roadmap.rst: -------------------------------------------------------------------------------- 1 | .. _roadmap: 2 | 3 | Roadmap 4 | ======= 5 | 6 | Mid to long-term goals include : 7 | 8 | - extract zmp package from `Pyros`_. it is a generic multiprocess framework, and should only be used as a dependency. 9 | - find or implement the `raft`_ algorithm in python, to enable distributed global state between multiple processes safely. 10 | - interface with `ROS 2`_ 11 | - identify and implement the dataflow/topic style concept. 12 | 13 | 14 | .. include:: weblinks.rst -------------------------------------------------------------------------------- /doc/weblinks.rst: -------------------------------------------------------------------------------- 1 | .. 2 | This file contains a common collection of web links. Include it 3 | wherever these links are needed. 4 | 5 | It is explicitly excluded in ``conf.py``, because it does not 6 | appear anywhere in the TOC tree. 7 | 8 | .. _ROS: http://wiki.ros.org 9 | .. _Pyros: http://github.com/asmodehn/pyros 10 | .. _python.multiprocessing: https://docs.python.org/2/library/multiprocessing.html 11 | .. _python.threading: https://docs.python.org/2/library/threading.html 12 | .. _Entity Component Systems: https://en.wikipedia.org/wiki/Entity_component_system 13 | .. _jsonpickle: https://jsonpickle.github.io/ 14 | .. _imperative: https://en.wikipedia.org/wiki/Imperative_programming 15 | .. _functional: https://en.wikipedia.org/wiki/Functional_programming 16 | .. _Functional programming: https://en.wikipedia.org/wiki/Functional_programming 17 | .. _paradigms: https://en.wikipedia.org/wiki/Programming_paradigm 18 | .. _pickle: https://docs.python.org/2/library/pickle.html 19 | .. _pyzmq: https://pyzmq.readthedocs.org/en/latest/ 20 | .. _stream processing: https://en.wikipedia.org/wiki/Stream_processing 21 | .. _dataflow programming: https://en.wikipedia.org/wiki/Dataflow_programming 22 | .. _REST: https://en.wikipedia.org/wiki/Representational_state_transfer 23 | .. _remote procedure call: https://en.wikipedia.org/wiki/Remote_procedure_call 24 | .. _subroutine: https://en.wikipedia.org/wiki/Subroutine 25 | .. _asynchronous: https://en.wikipedia.org/wiki/Asynchronous_method_invocation 26 | .. _global variables: https://en.wikipedia.org/wiki/Global_variable 27 | .. _raft: https://raft.github.io/ 28 | .. _consensus: https://en.wikipedia.org/wiki/Consensus_(computer_science) 29 | .. _ROS 2: http://design.ros2.org/ 30 | .. _Standing on the shoulder of giants: https://en.wikipedia.org/wiki/Standing_on_the_shoulders_of_giants 31 | .. _Python package: http://python-packaging.readthedocs.org/en/latest/ 32 | .. _me: https://github.com/asmodehn -------------------------------------------------------------------------------- /doc/zmp_internals.rst: -------------------------------------------------------------------------------- 1 | ZMP 2 | ========= 3 | 4 | 5 | .. automodule:: zmp.exceptions 6 | :members: 7 | :undoc-members: 8 | :inherited-members: 9 | 10 | .. automodule:: zmp.master 11 | :members: 12 | :undoc-members: 13 | :inherited-members: 14 | 15 | .. automodule:: zmp.message 16 | :members: 17 | :undoc-members: 18 | :inherited-members: 19 | 20 | .. automodule:: zmp.node 21 | :members: 22 | :undoc-members: 23 | :inherited-members: 24 | 25 | .. automodule:: zmp.service 26 | :members: 27 | :undoc-members: 28 | :inherited-members: 29 | 30 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # pyros-examples 2 | A Set of Examples for Pyros 3 | 4 | Assumptions 5 | -------------- 6 | 7 | - OS is Ubuntu Trusty 8 | - ROS is already known. If not, get informations and tutorials on http://ros.org 9 | 10 | ROS Indigo System Setup 11 | ----------------------- 12 | - Install useful python packages if you don't already have them : 13 | ```bash 14 | $ sudo apt-get install virtualenvwrapper 15 | ``` 16 | - Install ROS Indigo : http://wiki.ros.org/indigo/Installation/Ubuntu 17 | TL;DR : 18 | ```bash 19 | $ sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list' 20 | $ sudo apt-key adv --keyserver hkp://ha.pool.sks-keyservers.net:80 --recv-key 421C365BD9FF1F717815A3895523BAEEB01FA116 21 | $ sudo apt-get update 22 | $ sudo apt-get install ros-indigo-desktop 23 | $ sudo rosdep init 24 | ``` 25 | 26 | Examples 27 | -------- 28 | 29 | These examples will show you how to : 30 | - launch an existing ROS system (using ROS packages) 31 | - install & configure pyros in a virtual environment 32 | - use pyros to interract dynamically with the running ros system. 33 | 34 | Notes 35 | ----- 36 | 37 | Pyros is usable as a usual python package, and can be used from any python environment. 38 | 39 | Pyros is also extensible to support other multiprocessing environments, but ROS is the first complete implementation available. 40 | 41 | -------------------------------------------------------------------------------- /examples/ROS/README.md: -------------------------------------------------------------------------------- 1 | Pyros Talker 2 | ============ 3 | 4 | This example shows how to use pyros with ROS talker example 5 | 6 | ROS Talker Launch 7 | ----------------- 8 | Run the ROS talker and listener nodes as usual using roslaunch, in a separate terminal : 9 | 10 | ``` 11 | $ source /opt/ros/indigo/setup.bash 12 | $ roslaunch roscpp_tutorials talker_listener.launch 13 | ``` 14 | 15 | Pyros (v0.3.x) Setup 16 | ------------------ 17 | 18 | Pyros makes using a ROS system from a python setup simpler. 19 | From a new terminal, run : 20 | 21 | ``` 22 | $ mkvirtualenv pyros --system-site-packages 23 | New python executable in /home/alexv/.virtualenvs/pyros/bin/python 24 | Installing setuptools, pip, wheel...done. 25 | ``` 26 | 27 | Checkout this repository, activate hte virtual env 28 | ``` 29 | $ git clone https://github.com/asmodehn/pyros-examples.git 30 | $ cd pyros-examples/ROS 31 | ``` 32 | 33 | Activate the virtual environment if it was not already done 34 | ``` 35 | $ workon pyros 36 | (pyros) $ 37 | ``` 38 | 39 | And install the required package (pyros) with the proper interface package for ROS from [PyPI](https://pypi.python.org/pypi) 40 | ``` 41 | (pyros) $ pip install pyros[ros] 42 | ``` 43 | 44 | The first command to know is pyros_setup. It is installed as a dependency to pyros_interfaces_ros. 45 | It will let you configure how the virtual environment can access the ROS installation on your machine : 46 | ``` 47 | (pyros) $ pyros_setup --help 48 | Pyros_setup is a tool to help you manipulate python environment setup. 49 | It is especially useful with ROS and other environments that rely on system python with PYTHONPATH modifications. 50 | Usage: pyros_setup [--pytest | --version | --help] 51 | ``` 52 | 53 | The first time, you should run a self check on pyros_setup. 54 | This will setup the default configuration on your system, by automatically detecting your ROS installation : 55 | ``` 56 | (pyros) $ pyros_setup --pytest 57 | ================================================================= test session starts ================================================================= 58 | platform linux2 -- Python 2.7.6, pytest-3.0.7, py-1.4.33, pluggy-0.4.0 59 | rootdir: /home/alexv/Projects/pyros-examples, inifile: 60 | plugins: timeout-1.2.0, hypothesis-3.1.0 61 | collected 1 items 62 | 63 | . INFO pyros_config.confighandler:Loading configuration from /home/alexv/.virtualenvs/pyros/var/pyros_setup-instance/pyros_setup.cfg 64 | WARNING pyros_config.confighandler:Default configuration has been generated in /home/alexv/.virtualenvs/pyros/var/pyros_setup-instance/pyros_setup.cfg 65 | WARNING pyros_setup:Dynamic PyROS setup starting... 66 | WARNING pyros_setup.ros_setup: => Pyros_setup v0.2.1 Emulating ROS setup now for distro indigo and workspaces () 67 | WARNING pyros_setup.ros_setup:Prepending path /opt/ros/indigo to CMAKE_PREFIX_PATH 68 | WARNING pyros_setup.ros_setup:Prepending path /opt/ros/indigo/share to ROS_PACKAGE_PATH 69 | WARNING pyros_setup.ros_setup:Prepending path /opt/ros/indigo/bin to PATH 70 | WARNING pyros_setup.ros_setup:Prepending path /opt/ros/indigo/lib to LD_LIBRARY_PATH 71 | WARNING pyros_setup.ros_setup:Prepending path /opt/ros/indigo/lib/pkgconfig to PKG_CONFIG_PATH 72 | WARNING pyros_setup.ros_setup:Prepending path /opt/ros/indigo/lib/python2.7/dist-packages to sys.path 73 | Re-adding /opt/ros/indigo/lib/python2.7/dist-packages in front of sys.path 74 | WARNING pyros_setup.ros_setup: => ROS setup emulation done. 75 | WARNING pyros_setup:Dynamic PyROS setup done. 76 | . 77 | 78 | ============================================================== 1 passed in 0.05 seconds =============================================================== 79 | (pyros) $ 80 | ``` 81 | 82 | This will allow you to check in detail your setup and edit the configuration file if necessary. 83 | For more detail about pyros-setup, you should check the [pyros-setup website](https://github.com/asmodehn/pyros-setup.git) 84 | 85 | 86 | Using Pyros (v0.4.x) 87 | -------------------- 88 | 89 | If a pyros ROS node was not launched previously on the ROS system, you can dynamically start the pyros node inside the running ROS system, and create a client to connect to it : 90 | ``` 91 | (pyros) $ python 92 | Python 2.7.6 (default, Oct 26 2016, 20:30:19) 93 | [GCC 4.8.4] on linux2 94 | Type "help", "copyright", "credits" or "license" for more information. 95 | >>> import pyros_interfaces_ros 96 | WARNING:root:ZMQ : Protobuf message implementation not found. Using pickle based protocol 97 | WARNING:pyros_interfaces_ros:loading pyros_setup and configuring your ROS environment 98 | WARNING:pyros_setup:Dynamic PyROS setup starting... 99 | WARNING:pyros_setup.ros_setup: => Pyros_setup v0.2.1 Emulating ROS setup now for distro indigo and workspaces () 100 | WARNING:pyros_setup.ros_setup:Prepending path /opt/ros/indigo to CMAKE_PREFIX_PATH 101 | WARNING:pyros_setup.ros_setup:Prepending path /opt/ros/indigo/share to ROS_PACKAGE_PATH 102 | WARNING:pyros_setup.ros_setup:Prepending path /opt/ros/indigo/bin to PATH 103 | WARNING:pyros_setup.ros_setup:Prepending path /opt/ros/indigo/lib to LD_LIBRARY_PATH 104 | WARNING:pyros_setup.ros_setup:Prepending path /opt/ros/indigo/lib/pkgconfig to PKG_CONFIG_PATH 105 | WARNING:pyros_setup.ros_setup:Prepending path /opt/ros/indigo/lib/python2.7/dist-packages to sys.path 106 | Re-adding /opt/ros/indigo/lib/python2.7/dist-packages in front of sys.path 107 | WARNING:pyros_setup.ros_setup: => ROS setup emulation done. 108 | WARNING:pyros_setup:Dynamic PyROS setup done. 109 | WARNING:rosout:Failed to load Python extension for LZ4 support. LZ4 compression will not be available. 110 | >>> subproc = pyros_interfaces_ros.PyrosROS("pyrosnode") 111 | >>> client_conn = subproc.start() 112 | [pyrosnode] Node started as [9226 <= ipc:///tmp/zmp-pyrosnode-9wwwUn/services.pipe] 113 | [INFO] [WallTime: 1493190663.803954] RosInterface pyrosnode node started with args : [] 114 | [INFO] [WallTime: 1493190663.808503] [pyros_interfaces_ros.ros_interface] ROS Interface initialized with: 115 | - services : [] 116 | - publishers : [] 117 | - subscribers : [] 118 | - params : [] 119 | - enable_cache : False 120 | ``` 121 | If you connect onto your ROS system, you will see a node has been added : 122 | ``` 123 | alexv@AlexV-Linux:~$ source /opt/ros/indigo/setup.bash 124 | alexv@AlexV-Linux:~$ rosnode list 125 | /listener 126 | /pyrosnode 127 | /rosout 128 | /talker 129 | ``` 130 | More details about this on the [pyros-rosinterface website](https://github.com/asmodehn/pyros-rosinterface.git) 131 | 132 | We can now, from python, create a client and connect to our new pyros ROS node 133 | ``` 134 | >>> import pyros 135 | >>> import pyros.client 136 | >>> client = pyros.client.PyrosClient(client_conn) 137 | 138 | ``` 139 | 140 | _**TODO : connect to an existing pyros node on ROS system**_ 141 | 142 | 143 | Now you can connect to the Pyros ROS node to introspect and interract with the ROS system. 144 | ``` 145 | >>> client = pyros.PyrosClient(client_conn) 146 | >>> client.subscribers() 147 | {} 148 | >>> client.publishers() 149 | {} 150 | >>> client.services() 151 | {} 152 | >>> client.params() 153 | {} 154 | ``` 155 | 156 | To finally stop the node you launched, you can : 157 | ``` 158 | >>> subproc.shutdown() 159 | Shutdown initiated 160 | ``` -------------------------------------------------------------------------------- /pyros/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import, division, print_function 3 | 4 | # Note this is a normal python package (not a namespaced one like pyros_interfaces could be) 5 | -------------------------------------------------------------------------------- /pyros/__main__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # All ways to run pyros and all Manager commands 3 | # should be defined here for consistency 4 | from __future__ import absolute_import 5 | import os 6 | import sys 7 | 8 | import six 9 | 10 | import click 11 | 12 | # logging configuration should be here to not be imported by python users of pyros. 13 | # only used from command line 14 | 15 | import logging 16 | import logging.config 17 | # Setting up logging for this test 18 | logging.config.dictConfig( 19 | { 20 | 'version': 1, 21 | 'formatters': { 22 | 'verbose': { 23 | 'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s' 24 | }, 25 | 'simple': { 26 | 'format': '%(levelname)s %(name)s:%(message)s' 27 | }, 28 | }, 29 | 'handlers': { 30 | 'null': { 31 | 'level': 'DEBUG', 32 | 'class': 'logging.NullHandler', 33 | }, 34 | 'console': { 35 | 'level': 'DEBUG', 36 | 'class': 'logging.StreamHandler', 37 | 'formatter': 'simple' 38 | }, 39 | }, 40 | 'loggers': { 41 | 'pyros_config': { 42 | 'handlers': ['console'], 43 | 'level': 'INFO', 44 | 'propagate': False, 45 | }, 46 | 'pyros_setup': { 47 | 'handlers': ['console'], 48 | 'level': 'INFO', 49 | }, 50 | 'pyros': { 51 | 'handlers': ['console'], 52 | 'level': 'INFO', 53 | } 54 | } 55 | } 56 | ) 57 | 58 | 59 | import nose 60 | import pkg_resources 61 | 62 | _path = pkg_resources.resource_filename("pyros", "__main__.py") 63 | _parent = os.path.normpath(os.path.join(os.path.dirname(_path), "..")) 64 | 65 | 66 | # importing current package if needed ( solving relative package import from __main__ problem ) 67 | # if __package__ is None: 68 | # sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) 69 | # from rostful import create_app, set_pyros_client, setup_app_routes 70 | # else: 71 | # from . import create_app, set_pyros_client, setup_app_routes 72 | 73 | #TODO : enable starting different interface nodes for different MP frameworks 74 | 75 | 76 | def nosemain(): 77 | args = sys.argv + [opt for opt in ( 78 | # "--exe", # DO NOT look in exe (maybe old rostests ?) 79 | # "--all-modules", # DO NOT look in all modules 80 | "--traverse-namespace", 81 | "--verbosity=2", 82 | "--with-id", 83 | "--with-xunit", 84 | "--where=%s" % _parent 85 | ) 86 | if opt not in sys.argv 87 | ] 88 | nose.run(argv=args) 89 | 90 | 91 | def pyros_rosinterface_launch(node_name=None, pyros_config=None, ros_argv=None): 92 | 93 | node_name = node_name or 'pyros_rosinterface' 94 | ros_argv = ros_argv or [] 95 | 96 | # dynamic setup and import 97 | try: 98 | import pyros 99 | node_proc = pyros.PyrosROS( 100 | node_name, 101 | ros_argv 102 | ) 103 | # Attribute error is triggered when using pyros < 0.1.0 104 | except (ImportError, AttributeError) as e: 105 | #logging.warn("{name} Error: Could not import pyros : {e}".format(name=__name__, e=e)) 106 | ### TMP ### 107 | logging.warn("{name} Attempting bwcompat import : {e}".format(name=__name__, e=e)) 108 | # BWcompat with old pyros : 109 | try: 110 | # this will import rosinterface and if needed simulate ROS setup 111 | import sys 112 | import pyros 113 | import pyros.rosinterface 114 | # this doesnt work with celery handling of imports 115 | # sys.modules["pyros.rosinterface"] = pyros.rosinterface.delayed_import_auto( 116 | # distro='indigo', 117 | # base_path=os.path.join(os.path.dirname(__file__), '..', '..', '..') 118 | # ) 119 | pyros.rosinterface = pyros.rosinterface.delayed_import_auto( 120 | distro='indigo', 121 | base_path=os.path.join(os.path.dirname(__file__), '..', '..', '..') 122 | ) 123 | 124 | node_proc = pyros.rosinterface.PyrosROS( 125 | node_name, 126 | ros_argv, 127 | base_path=os.path.join(os.path.dirname(__file__), '..', '..', '..') 128 | ) 129 | 130 | node_proc.configure(pyros_config) 131 | 132 | except ImportError as e: 133 | logging.warn("{name} Error: Could not import pyros.rosinterface : {e}".format(name=__name__, e=e)) 134 | raise 135 | 136 | return node_proc 137 | 138 | 139 | # Change that into something cleaner and related with the app itself (not server) 140 | # Middleware ? app context ? Tricky with dynamic imports... 141 | # middleware ref : https://github.com/miguelgrinberg/python-engineio/blob/master/engineio/middleware.py#L49 142 | def pyros_start(config, ros_args='', pyros_ctx_impl=None): 143 | 144 | # implementing Config.get_namespace() for flask version < 1.0: 145 | namespace = 'PYROS_' 146 | trim_namespace = True 147 | lowercase = False 148 | rv = {} 149 | for k, v in six.iteritems(config): 150 | if not k.startswith(namespace): 151 | continue 152 | if trim_namespace: 153 | key = k[len(namespace):] 154 | else: 155 | key = k 156 | if lowercase: 157 | key = key.lower() 158 | rv[key] = v 159 | # rv should now contain a dictionary of namespaced key:value from config 160 | 161 | try: 162 | from pyros import pyros_ctx, PyrosClient 163 | except Exception as e: 164 | logging.error("pyros module is not accessible in sys.path. It is required to run rostful.", exc_info=True) 165 | logging.error("sys.path = {0}".format(sys.path)) 166 | raise 167 | 168 | # default to real module, if no other implementation passed as parameter (used for mock) 169 | pyros_ctx_impl = pyros_ctx_impl or pyros_ctx 170 | 171 | # One PyrosNode is needed for Flask. 172 | # TODO : check everything works as expected, even if the WSGI app is used by multiple processes 173 | 174 | try: 175 | node_ctx_gen = pyros_ctx_impl(name='rostful', argv=ros_args, pyros_config=rv) 176 | except TypeError as te: 177 | # bwcompat trick 178 | node_ctx_gen = pyros_ctx_impl(name='rostful', argv=ros_args, 179 | base_path=os.path.join(os.path.dirname(__file__), '..', '..', '..')) 180 | return node_ctx_gen 181 | 182 | 183 | # TODO : handle ros arguments here 184 | # http://click.pocoo.org/5/commands/#group-invocation-without-command 185 | @click.group() 186 | def cli(): 187 | pass 188 | 189 | 190 | @cli.command() 191 | def test(): 192 | errno = nosemain() 193 | sys.exit(errno) 194 | 195 | 196 | # 197 | # Arguments' default value is None here 198 | # to use default values from config file if one is provided. 199 | # If no config file is provided, internal defaults are used. 200 | # 201 | @cli.command() 202 | @click.option('-i', '--interface', default='ros', type=click.Choice(['ros', 'ros_mock'])) 203 | # @click.option('-a', '--async', default=False) # wether to activate async implementation (instead of pyros main loop) 204 | @click.option('-c', '--config', default=None) # this is the last possible config override, and has to be explicit. 205 | @click.option('-l', '--logfile', default=None) # this is the last possible logfile override, and has to be explicit. 206 | @click.option('ros_args', '-r', '--ros-arg', multiple=True, default='') 207 | def run(interface, config, logfile, ros_args): 208 | """ 209 | Start a pyros node. 210 | :param interface: the interface implementation (ROS, Mock, ZMP, etc.) 211 | :param config: the config file path, absolute, or relative to working directory 212 | :param logfile: the logfile path, absolute, or relative to working directory 213 | :param ros_args: the ros arguments (useful to absorb additional args when launched with roslaunch) 214 | """ 215 | logging.info( 216 | 'pyros started with : interface {interface} config {config} logfile {logfile} ros_args {ros_args}'.format( 217 | interface=interface, config=config, logfile=logfile, ros_args=ros_args)) 218 | 219 | if interface == 'ros': 220 | node_proc = pyros_rosinterface_launch(node_name='pyros_rosinterface', pyros_config=config, ros_argv=ros_args) 221 | else: 222 | node_proc = None # NOT IMPLEMENTED 223 | 224 | # node_proc.daemon = True # we do NOT want a daemon(would stop when this main process exits...) 225 | client_conn = node_proc.start() # in a sub process 226 | # DISABLING THIS FOR NOW, process tree is a bit unexpected... 227 | # TODO : investigate 228 | # client_conn = node_proc.run() # in same process 229 | 230 | 231 | if __name__ == '__main__': 232 | cli() 233 | -------------------------------------------------------------------------------- /pyros/_version.py: -------------------------------------------------------------------------------- 1 | # Store the version here so: 2 | # 1) we don't load dependencies by storing it in __init__.py 3 | # 2) we can import it in setup.py for the same reason 4 | # 3) we can import it into your module module 5 | __version_info__ = ('0', '4', '3') 6 | __version__ = '.'.join(__version_info__) 7 | -------------------------------------------------------------------------------- /pyros/client/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import, division, print_function 3 | 4 | 5 | import logging 6 | 7 | """ 8 | Hopefully this should endup in ros.__doc__ 9 | """ 10 | 11 | # create logger 12 | # TODO solve multiprocess logger problem(s)... 13 | _logger = logging.getLogger(__name__) 14 | 15 | # The client part should be the same for any kind of backend. 16 | # The only requirement for this client is to be used in a python environment. 17 | # Therefore it is not, and will not be, a namespace package. 18 | 19 | from .client import PyrosClient 20 | 21 | __all__ = [ 22 | 'PyrosClient', 23 | ] 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /pyros/client/client.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | import sys 4 | import unicodedata 5 | 6 | import six 7 | 8 | """ 9 | Client to pyros node, Python style. 10 | Required for multiprocess communication. 11 | """ 12 | 13 | # When importing this your environment should already be setup 14 | # and pyzmp should be found (from ROS packages or from python packages) 15 | import pyzmp 16 | 17 | 18 | from pyros_common.exceptions import PyrosException 19 | 20 | # TODO : Requirement : Check TOTAL send/receive SYMMETRY. 21 | # If needed get rid of **kwargs arguments in call. Makes the interface less obvious and can trap unaware devs. 22 | 23 | 24 | class PyrosServiceNotFound(PyrosException): 25 | def __init__(self, message): 26 | super(PyrosServiceNotFound, self).__init__(message) 27 | 28 | 29 | # CAREFUL : exceptions must be pickleable ( we need to pass all arguments to the superclass ) 30 | class PyrosServiceTimeout(PyrosException): 31 | def __init__(self, message): 32 | super(PyrosServiceTimeout, self).__init__(message) 33 | 34 | 35 | 36 | # TODO : provide a test client ( similar to what werkzeug/flask does ) 37 | # The goal is to make it easy for users of pyros to test and validate their library only against the client, 38 | # without having to have all the ROS environment installed and setup, and running extra processing 39 | # just for unit testing... 40 | class PyrosClient(object): 41 | # TODO : improve ZMP to return the socket_bind address to point to the exact IPC/socket channel. 42 | # And pass it here, instead of assuming node name is unique... 43 | def __init__(self, node_name=None): 44 | # Link to only one Server 45 | self.node_name = node_name 46 | 47 | # Discover all Services. Wait for at least one, and make sure it s provided by our expected Server 48 | self.msg_build_svc = pyzmp.Service.discover('msg_build', 5) 49 | if self.msg_build_svc is None or ( 50 | self.node_name is not None and 51 | self.node_name not in [p[0] for p in self.msg_build_svc.providers] 52 | ): 53 | raise PyrosServiceNotFound('msg_build') 54 | 55 | self.setup_svc = pyzmp.Service.discover('setup', 5) 56 | if self.setup_svc is None or ( 57 | self.node_name is not None and 58 | self.node_name not in [p[0] for p in self.setup_svc.providers] 59 | ): 60 | raise PyrosServiceNotFound('setup') 61 | 62 | self.topic_svc = pyzmp.Service.discover('topic', 5) 63 | if self.topic_svc is None or ( 64 | self.node_name is not None and 65 | self.node_name not in [p[0] for p in self.topic_svc.providers] 66 | ): 67 | raise PyrosServiceNotFound('topic') 68 | 69 | self.service_svc = pyzmp.Service.discover('service', 5) 70 | if self.service_svc is None or ( 71 | self.node_name is not None and 72 | self.node_name not in [p[0] for p in self.service_svc.providers] 73 | ): 74 | raise PyrosServiceNotFound('service') 75 | 76 | self.param_svc = pyzmp.Service.discover('param', 5) 77 | if self.param_svc is None or ( 78 | self.node_name is not None and 79 | self.node_name not in [p[0] for p in self.param_svc.providers] 80 | ): 81 | raise PyrosServiceNotFound('param') 82 | 83 | self.topics_svc = pyzmp.Service.discover('topics', 5) 84 | if self.topics_svc is None or ( 85 | self.node_name is not None and 86 | self.node_name not in [p[0] for p in self.topics_svc.providers] 87 | ): 88 | raise PyrosServiceNotFound('topics') 89 | 90 | self.services_svc = pyzmp.Service.discover('services', 5) 91 | if self.services_svc is None or ( 92 | self.node_name is not None and 93 | self.node_name not in [p[0] for p in self.services_svc.providers] 94 | ): 95 | raise PyrosServiceNotFound('services') 96 | 97 | self.params_svc = pyzmp.Service.discover('params', 5) 98 | if self.params_svc is None or ( 99 | self.node_name is not None and 100 | self.node_name not in [p[0] for p in self.params_svc.providers] 101 | ): 102 | raise PyrosServiceNotFound('params') 103 | 104 | def buildMsg(self, connection_name, suffix=None): 105 | #changing unicode to string ( testing stability of multiprocess debugging ) 106 | if isinstance(connection_name, unicode): 107 | connection_name = unicodedata.normalize('NFKD', connection_name).encode('ascii', 'ignore') 108 | res = self.msg_build_svc.call(args=(connection_name,)) 109 | return res 110 | 111 | def topic_inject(self, topic_name, _msg_content=None, **kwargs): 112 | """ 113 | Injecting message into topic. if _msg_content, we inject it directly. if not, we use all extra kwargs 114 | :param topic_name: name of the topic 115 | :param _msg_content: optional message content 116 | :param kwargs: each extra kwarg will be put int he message is structure matches 117 | :return: 118 | """ 119 | #changing unicode to string ( testing stability of multiprocess debugging ) 120 | if isinstance(topic_name, unicode): 121 | topic_name = unicodedata.normalize('NFKD', topic_name).encode('ascii', 'ignore') 122 | 123 | if _msg_content is not None: 124 | # logging.warn("injecting {msg} into {topic}".format(msg=_msg_content, topic=topic_name)) 125 | res = self.topic_svc.call(args=(topic_name, _msg_content,)) 126 | else: # default kwargs is {} 127 | # logging.warn("injecting {msg} into {topic}".format(msg=kwargs, topic=topic_name)) 128 | res = self.topic_svc.call(args=(topic_name, kwargs,)) 129 | 130 | return res is None # check if message has been consumed 131 | 132 | def topic_extract(self, topic_name): 133 | #changing unicode to string ( testing stability of multiprocess debugging ) 134 | if isinstance(topic_name, unicode): 135 | topic_name = unicodedata.normalize('NFKD', topic_name).encode('ascii', 'ignore') 136 | 137 | try: 138 | res = self.topic_svc.call(args=(topic_name, None,)) 139 | except pyzmp.service.ServiceCallTimeout as exc: 140 | six.reraise(PyrosServiceTimeout("Pyros Service call timed out."), None, sys.exc_info()[2]) 141 | 142 | # TODO : if topic_name not exposed, we get None as res. 143 | # We should improve that behavior (display warning ? allow auto -dynamic- expose ?) 144 | 145 | return res 146 | 147 | def service_call(self, service_name, _msg_content=None, **kwargs): 148 | #changing unicode to string ( testing stability of multiprocess debugging ) 149 | if isinstance(service_name, unicode): 150 | service_name = unicodedata.normalize('NFKD', service_name).encode('ascii', 'ignore') 151 | 152 | try: 153 | if _msg_content is not None: 154 | res = self.service_svc.call(args=(service_name, _msg_content,)) 155 | else: # default kwargs is {} 156 | res = self.service_svc.call(args=(service_name, kwargs,)) 157 | except pyzmp.service.ServiceCallTimeout as exc: 158 | six.reraise(PyrosServiceTimeout("Pyros Service call timed out."), None, sys.exc_info()[2]) 159 | # A service that doesn't exist on the node will return res_content.resp_content None. 160 | # It should probably except... 161 | # TODO : improve error handling, maybe by checking the type of res ? 162 | 163 | return res 164 | 165 | def param_set(self, param_name, _value=None, **kwargs): 166 | """ 167 | Setting parameter. if _value, we inject it directly. if not, we use all extra kwargs 168 | :param topic_name: name of the topic 169 | :param _value: optional value 170 | :param kwargs: each extra kwarg will be put in the value if structure matches 171 | :return: 172 | """ 173 | #changing unicode to string ( testing stability of multiprocess debugging ) 174 | if isinstance(param_name, unicode): 175 | param_name = unicodedata.normalize('NFKD', param_name).encode('ascii', 'ignore') 176 | 177 | _value = _value or {} 178 | 179 | if kwargs: 180 | res = self.param_svc.call(args=(param_name, kwargs,)) 181 | elif _value is not None: 182 | res = self.param_svc.call(args=(param_name, _value,)) 183 | else: # if _msg_content is None the request is invalid. 184 | # just return something to mean False. 185 | res = 'WRONG SET' 186 | 187 | return res is None # check if message has been consumed 188 | 189 | def param_get(self, param_name): 190 | #changing unicode to string ( testing stability of multiprocess debugging ) 191 | if isinstance(param_name, unicode): 192 | param_name = unicodedata.normalize('NFKD', param_name).encode('ascii', 'ignore') 193 | res = self.param_svc.call(args=(param_name, None,)) 194 | return res 195 | 196 | def topics(self): 197 | try: 198 | res = self.topics_svc.call(send_timeout=5000, recv_timeout=10000) # Need to be generous on timeout in case we are starting up multiprocesses 199 | except pyzmp.service.ServiceCallTimeout as exc: 200 | six.reraise(PyrosServiceTimeout("Pyros Service call timed out."), None, sys.exc_info()[2]) 201 | return res 202 | 203 | def services(self): 204 | try: 205 | res = self.services_svc.call(send_timeout=5000, recv_timeout=10000) # Need to be generous on timeout in case we are starting up multiprocesses 206 | except pyzmp.service.ServiceCallTimeout as exc: 207 | six.reraise(PyrosServiceTimeout("Pyros Service call timed out."), None, sys.exc_info()[2]) 208 | return res 209 | 210 | def params(self): 211 | res = self.params_svc.call(send_timeout=5000, recv_timeout=10000) # Need to be generous on timeout in case we are starting up multiprocesses 212 | return res 213 | 214 | def setup(self, publishers=None, subscribers=None, services=None, params=None): #, enable_cache=False): 215 | res = self.setup_svc.call(kwargs={ 216 | 'publishers': publishers, 217 | 'subscribers': subscribers, 218 | 'services': services, 219 | 'params': params, 220 | #'enable_cache': enable_cache, # TODO : CAREFUL : check if we can actually enable the cache dynamically ? 221 | }, send_timeout=5000, recv_timeout=10000) # Need to be generous on timeout in case we are starting up multiprocesses 222 | return res 223 | 224 | #def listacts(self): 225 | # return {} 226 | 227 | # def namespaces(self): 228 | # res = self.namespaces_svc.call(args=({},)) 229 | # return res 230 | 231 | # def interactions(self): 232 | # res = self.interactions_svc.call(args=({},)) 233 | # return res 234 | 235 | # def interaction(self, name): 236 | # res = self.interaction_svc.call(args=("",)) 237 | # return res 238 | 239 | # def has_rocon(self): 240 | # res = self.has_rocon_svc.call(args=(False,)) 241 | # return res 242 | 243 | # TODO : test client with Rostful Node ( and detect ROS or not to confirm behvior ) 244 | -------------------------------------------------------------------------------- /pyros/config.py: -------------------------------------------------------------------------------- 1 | ### 2 | # Generic 3 | ### 4 | # Settings to pass to pyros node to interface with another system 5 | 6 | 7 | ### 8 | # Mock specific 9 | ### 10 | 11 | TOPICS = [] 12 | SERVICES = [] 13 | PARAMS = [] 14 | 15 | # MOCK_ 16 | -------------------------------------------------------------------------------- /pyros/server/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import, division, print_function 3 | 4 | """ 5 | This is hte server side of pyros 6 | client-Server Architecture was chosen because it enforces direction of knowledge : 7 | - the client knows the server 8 | - the server doesn't know the client(s) 9 | The design is to have only one server/node per multiprocess system we want to interface with. 10 | client will be able to send requests to them. 11 | 12 | TODO : requests will setup stream (Functional Reactive Programming / HTTP2 style) 13 | to allow pushing data from server to connected client 14 | """ -------------------------------------------------------------------------------- /pyros/server/ctx_server.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | import logging 4 | import mock 5 | from collections import namedtuple 6 | from contextlib import contextmanager 7 | 8 | from pyros.client import PyrosClient 9 | import pyros.config 10 | from pyros_interfaces_mock.pyros_mock import PyrosMock 11 | 12 | 13 | # A context manager to handle server process launch and shutdown properly. 14 | # It also creates a communication channel and passes it to a client. 15 | @contextmanager 16 | def pyros_ctx(name='pyros', 17 | argv=None, # TODO : think about passing ros arguments http://wiki.ros.org/Remapping%20Arguments 18 | mock_client=False, 19 | node_impl=PyrosMock, 20 | pyros_config=None): 21 | 22 | pyros_config = pyros_config or pyros.config # using internal config if no other config passed 23 | 24 | subproc = None 25 | ctx = namedtuple("pyros_context", "client") 26 | 27 | if mock_client: 28 | 29 | logging.warning("Setting up pyros mock client...") 30 | with mock.patch('pyros.client.PyrosClient', autospec=True) as client: 31 | yield ctx(client=client) 32 | else: 33 | 34 | logging.warning("Setting up pyros {0} node...".format(node_impl)) 35 | subproc = node_impl(name, argv).configure(pyros_config) 36 | 37 | client_conn = subproc.start() 38 | 39 | logging.warning("Setting up pyros actual client...") 40 | yield ctx(client=PyrosClient(client_conn)) 41 | 42 | if subproc is not None: 43 | subproc.shutdown() 44 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | --index-url https://pypi.python.org/simple/ 2 | # --extra-index-url https://testpypi.python.org/simple/ 3 | 4 | # dependencies for development 5 | # Note : all tests included in package, which means we don't need anything here 6 | 7 | # dependency sources for development only 8 | # Note for ROS : this will end up in the wstool workspace. so we dont need to define it in the rosinstall... 9 | # -e git+https://github.com/asmodehn/pyros-setup.git@config_refactor#egg=pyros_setup 10 | 11 | # -e git+https://github.com/pyros-dev/pyros-common.git@fix_exception#egg=pyros_common 12 | 13 | # TMP interface Dependencies 14 | # -e git+https://github.com/asmodehn/pyros-rosinterface.git@namespace#egg=pyros_interfaces_ros 15 | 16 | # To always have requirements installing current package (and dependencies) as well 17 | # if we try to install requirements 18 | -e . -------------------------------------------------------------------------------- /requirements/ROS/indigo.txt: -------------------------------------------------------------------------------- 1 | # Requirements for running on top of indigo 2 | 3 | # trusty packages versions to validate behavior with these versions for a potential ROS package for rostful 4 | # pytest==2.5.1 5 | # pytest-xdist==1.8 # for --boxed 6 | # hypothesis==3.0.1 # backported to indigo as https://github.com/asmodehn/hypothesis-rosrelease 7 | # numpy>=1.8.1 8 | # TESTS are outside the package : they dont need to match the system packages on the corresponding ROS DISTRO 9 | # since they willnot be distributed as part of the package. 10 | 11 | 12 | # Package Dependencies need to match ROS (or trusty's) package version 13 | 14 | tblib==1.2.0 # this might not always install six (latest version does not) 15 | # six==1.5.2 # dropping six here, our usage is minimal and it breaks test tools 16 | pyzmq==14.0.1 17 | pyzmp==0.0.17 18 | pyros_config==0.2.0 19 | pyros_common==0.5.1 20 | #pyros_interfaces_ros==0.4.0 # TODO : follow up on what packages from ROS are needed as pure python to test this... 21 | mock==1.0.1 22 | -------------------------------------------------------------------------------- /requirements/ROS/kinetic.txt: -------------------------------------------------------------------------------- 1 | # Requirements for running on top of kinetic 2 | 3 | # xenial packages versions to validate behavior with these versions for a potential ROS package for rostful 4 | # pytest==2.8.7 5 | # pytest-xdist==1.8 # for --boxed 6 | # hypothesis==3.0.1 7 | # numpy==1.11.0 8 | # TESTS are outside the package : they dont need to match the system packages on the corresponding ROS DISTRO 9 | # since they willnot be distributed as part of the package. 10 | 11 | # Package Dependencies need to match ROS (or xenial's) package version 12 | 13 | tblib==1.2.0 # this might not always install six (latest version does not) 14 | six==1.10.0 15 | pyzmq==15.2.0 16 | pyzmp==0.0.17 17 | pyros_config==0.2.0 18 | pyros_common==0.5.1 19 | # pyros_interfaces_ros==0.4.0 # TODO : follow up on what packages from ROS are needed as pure python to test this... 20 | mock==1.3.0 21 | -------------------------------------------------------------------------------- /requirements/dev.txt: -------------------------------------------------------------------------------- 1 | # Requirements to get specific development version fo various dependencies 2 | 3 | # -e git+https://github.com/pyros-dev/pyzmp.git#egg=pyzmp 4 | 5 | -------------------------------------------------------------------------------- /requirements/tests.txt: -------------------------------------------------------------------------------- 1 | # latest version to validate behavior on pure python env 2 | pytest 3 | # pytest-xdist # for --boxed # dropping this because of compat req with six... 4 | hypothesis 5 | numpy 6 | -------------------------------------------------------------------------------- /requirements/tools.txt: -------------------------------------------------------------------------------- 1 | # for release, version doesnt matter, we always work in virtualenv 2 | gitchangelog 3 | twine -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [aliases] 2 | test=py.test 3 | 4 | [bdist_wheel] 5 | universal=1 6 | 7 | [metadata] 8 | description_file=README.rst -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | import subprocess 4 | import sys 5 | import tempfile 6 | import setuptools 7 | 8 | # Ref : https://packaging.python.org/single_source_version/#single-sourcing-the-version 9 | with open('pyros/_version.py') as vf: 10 | exec(vf.read()) 11 | 12 | # Best Flow : 13 | # Clean previous build & dist 14 | # $ gitchangelog >CHANGELOG.rst 15 | # change version in code and changelog 16 | # $ python setup.py prepare_release 17 | # WAIT FOR TRAVIS CHECKS 18 | # $ python setup.py publish 19 | # => TODO : try to do a simpler "release" command 20 | 21 | # TODO : command to retrieve extra ROS stuff from a third party release repo ( for ROS devs ). useful in dev only so maybe "rosdevelop" ? or via catkin_pip ? 22 | # TODO : command to release to Pip and ROS (bloom) same version one after the other... 23 | 24 | 25 | # Clean way to add a custom "python setup.py " 26 | # Ref setup.py command extension : https://blog.niteoweb.com/setuptools-run-custom-code-in-setup-py/ 27 | class PrepareReleaseCommand(setuptools.Command): 28 | """Command to release this package to Pypi""" 29 | description = "prepare a release of pyros" 30 | user_options = [] 31 | 32 | def initialize_options(self): 33 | """init options""" 34 | pass 35 | 36 | def finalize_options(self): 37 | """finalize options""" 38 | pass 39 | 40 | def run(self): 41 | """runner""" 42 | 43 | # change version in code and changelog before running this 44 | subprocess.check_call("git commit CHANGELOG.rst pyros/_version.py CHANGELOG.rst -m 'v{0}'".format(__version__), shell=True) 45 | subprocess.check_call("git push", shell=True) 46 | 47 | print("You should verify travis checks, and you can publish this release with :") 48 | print(" python setup.py publish") 49 | sys.exit() 50 | 51 | 52 | # Clean way to add a custom "python setup.py " 53 | # Ref setup.py command extension : https://blog.niteoweb.com/setuptools-run-custom-code-in-setup-py/ 54 | class PublishCommand(setuptools.Command): 55 | """Command to release this package to Pypi""" 56 | description = "releases pyros to Pypi" 57 | user_options = [] 58 | 59 | def initialize_options(self): 60 | """init options""" 61 | pass 62 | 63 | def finalize_options(self): 64 | """finalize options""" 65 | pass 66 | 67 | def run(self): 68 | """runner""" 69 | # TODO : clean build/ and dist/ before building... 70 | subprocess.check_call("python setup.py sdist", shell=True) 71 | subprocess.check_call("python setup.py bdist_wheel", shell=True) 72 | # OLD way: 73 | # os.system("python setup.py sdist bdist_wheel upload") 74 | # NEW way: 75 | # Ref: https://packaging.python.org/distributing/ 76 | subprocess.check_call("twine upload dist/*", shell=True) 77 | 78 | subprocess.check_call("git tag -a {0} -m 'version {0}'".format(__version__), shell=True) 79 | subprocess.check_call("git push --tags", shell=True) 80 | sys.exit() 81 | 82 | 83 | # Clean way to add a custom "python setup.py " 84 | # Ref setup.py command extension : https://blog.niteoweb.com/setuptools-run-custom-code-in-setup-py/ 85 | class RosDevelopCommand(setuptools.Command): 86 | 87 | """Command to mutate this package to a ROS package, using its ROS release repository""" 88 | description = "mutate this package to a ROS package using its release repository" 89 | user_options = [] 90 | 91 | def initialize_options(self): 92 | """init options""" 93 | # TODO : add distro selector 94 | pass 95 | 96 | def finalize_options(self): 97 | """finalize options""" 98 | pass 99 | 100 | def run(self): 101 | # dynamic import for this command only to not need these in usual python case... 102 | import git 103 | import yaml 104 | 105 | """runner""" 106 | repo_path = tempfile.mkdtemp(prefix='rosdevelop-' + os.path.dirname(__file__)) # TODO get actual package name ? 107 | print("Getting ROS release repo in {0}...".format(repo_path)) 108 | # TODO : get release repo from ROSdistro 109 | rosrelease_repo = git.Repo.clone_from('https://github.com/asmodehn/pyros-rosrelease.git', repo_path) 110 | 111 | # Reset our working tree to master 112 | origin = rosrelease_repo.remotes.origin 113 | rosrelease_repo.remotes.origin.fetch() # assure we actually have data. fetch() returns useful information 114 | # Setup a local tracking branch of a remote branch 115 | rosrelease_repo.create_head('master', origin.refs.master).set_tracking_branch(origin.refs.master).checkout() 116 | 117 | print("Reading tracks.yaml...") 118 | with open(os.path.join(rosrelease_repo.working_tree_dir, 'tracks.yaml'), 'r') as tracks: 119 | try: 120 | tracks_dict = yaml.load(tracks) 121 | except yaml.YAMLError as exc: 122 | raise 123 | 124 | patch_dir = tracks_dict.get('tracks', {}).get('indigo', {}).get('patches', {}) 125 | 126 | print("Found patches for indigo in {0}".format(patch_dir)) 127 | src_files = os.listdir(os.path.join(rosrelease_repo.working_tree_dir, patch_dir)) 128 | 129 | working_repo = git.Repo(os.path.dirname(os.path.abspath(__file__))) 130 | 131 | # adding patched files to ignore list if needed (to prevent accidental commit of patch) 132 | # => BETTER if the patch do not erase previous file. TODO : fix problem with both .travis.yml 133 | with open(os.path.join(working_repo.working_tree_dir, '.gitignore'), 'a+') as gitignore: 134 | skipit = [] 135 | for line in gitignore: 136 | if line in src_files: 137 | skipit += line 138 | else: # not found, we are at the eof 139 | for f in src_files: 140 | if f not in skipit: 141 | gitignore.write(f+'\n') # append missing data 142 | 143 | working_repo.git.add(['.gitignore']) # adding .gitignore to the index so git applies it (and hide new files) 144 | 145 | for file_name in src_files: 146 | print("Patching {0}".format(file_name)) 147 | full_file_name = os.path.join(rosrelease_repo.working_tree_dir, patch_dir, file_name) 148 | if os.path.isfile(full_file_name): 149 | # Special case for package.xml and version template string 150 | if file_name == 'package.xml': 151 | with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'package.xml'), "wt") as fout: 152 | with open(full_file_name, "rt") as fin: 153 | for line in fin: 154 | fout.write(line.replace(':{version}', __version__)) # TODO: proper template engine ? 155 | else: 156 | shutil.copy(full_file_name, os.path.dirname(os.path.abspath(__file__))) 157 | 158 | sys.exit() 159 | 160 | 161 | class ROSPublishCommand(setuptools.Command): 162 | """Command to release this package to Pypi""" 163 | description = "releases pyros to ROS" 164 | user_options = [] 165 | 166 | def initialize_options(self): 167 | """init options""" 168 | pass 169 | 170 | def finalize_options(self): 171 | """finalize options""" 172 | pass 173 | 174 | def run(self): 175 | """runner""" 176 | # TODO : distro from parameter. default : ['indigo', 'jade', 'kinetic'] 177 | subprocess.check_call("git tag -a ros-{0} -m 'version {0} for ROS'".format(__version__), shell=True) 178 | subprocess.check_call("git push --tags", shell=True) 179 | 180 | subprocess.check_call("bloom-release --rosdistro indigo --track indigo pyros", shell=True) 181 | sys.exit() 182 | 183 | 184 | setuptools.setup(name='pyros', 185 | version=__version__, 186 | description='ROS Node to provide ROS introspection for non-ROS users.', 187 | url='http://github.com/asmodehn/pyros', 188 | author='AlexV', 189 | author_email='asmodehn@gmail.com', 190 | license='BSD', 191 | packages=[ 192 | 'pyros', 193 | 'pyros.client', 194 | # 'pyros.client.tests', 195 | 'pyros.server', 196 | # 'pyros.server.tests', 197 | ], 198 | entry_points={ 199 | 'console_scripts': [ 200 | 'pyros = pyros.__main__:nosemain' 201 | ] 202 | }, 203 | # this is better than using package data ( since behavior is a bit different from distutils... ) 204 | include_package_data=True, # use MANIFEST.in during install. 205 | install_requires=[ 206 | 'tblib', # this might not always install six (latest version does not) 207 | 'six', 208 | 'pyzmq', 209 | 'pyzmp>=0.0.14', # lets match the requirement in package.xml (greater than) 210 | 'pyros_setup>=0.1.5', # Careful : pyros-setup < 0.0.8 might already be installed as a deb in /opt/ros/indigo/lib/python2.7/dist-packages/ => we still need to force hte install in the venv to have permissions to create hte configuration file... 211 | 'pyros_config>=0.1.4', 212 | 'pyros-common>=0.5.1', 213 | 'nose>=1.3.7', 214 | 'mock>=1.0.1', # old mock to be compatible with trusty versions 215 | ], 216 | extras_require={ 217 | 'ros': 'pyros_interfaces_ros', 218 | }, 219 | dependency_links=[ 220 | 'git+https://github.com/asmodehn/pyros-rosinterface.git@namespace#egg=pyros_interfaces_ros' 221 | ], 222 | # Reference for optional dependencies : http://stackoverflow.com/questions/4796936/does-pip-handle-extras-requires-from-setuptools-distribute-based-sources 223 | 224 | cmdclass={ 225 | 'rosdevelop': RosDevelopCommand, 226 | 'prepare_release': PrepareReleaseCommand, 227 | 'publish': PublishCommand, 228 | 'rospublish': ROSPublishCommand, 229 | }, 230 | zip_safe=False, # TODO testing... 231 | ) 232 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyros-dev/pyros/59f6c8848a66481a4039cbf28b7673428181842e/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_pyros/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyros-dev/pyros/59f6c8848a66481a4039cbf28b7673428181842e/tests/test_pyros/__init__.py -------------------------------------------------------------------------------- /tests/test_pyros/profile_pyros_ros.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from __future__ import absolute_import 3 | 4 | import cProfile 5 | import logging 6 | import sys 7 | import time 8 | 9 | import rosgraph 10 | import roslaunch 11 | import rospy 12 | 13 | from pyros import PyrosROS 14 | 15 | roscore_process = None 16 | 17 | # BROKEN ?? start roscore beofre running this... 18 | # if not rosgraph.masterapi.is_online(): 19 | # # Trying to solve this : http://answers.ros.org/question/215600/how-can-i-run-roscore-from-python/ 20 | # def ros_core_launch(): 21 | # roslaunch.main(['roscore', '--core']) # same as rostest_main implementation 22 | # 23 | # roscore_process = multiprocessing.Process(target=ros_core_launch) 24 | # roscore_process.start() 25 | # 26 | # while not roscore_process.is_alive(): 27 | # time.sleep(0.2) # waiting for roscore to be born 28 | # 29 | # assert roscore_process.is_alive() 30 | 31 | assert rosgraph.masterapi.is_online() 32 | 33 | # Start roslaunch 34 | launch = roslaunch.scriptapi.ROSLaunch() 35 | launch.start() 36 | 37 | # starting connection cache is available 38 | rospy.set_param('/connection_cache/spin_freq', 2) # 2 Hz 39 | connection_cache_node = roslaunch.core.Node('rocon_python_comms', 'connection_cache.py', name='connection_cache', 40 | remap_args=[('~list', '/pyros_ros/connections_list'), 41 | ('~diff', '/pyros_ros/connections_diff'), 42 | ]) 43 | try: 44 | connection_cache_proc = launch.launch(connection_cache_node) 45 | except roslaunch.RLException as rlexc: 46 | pass # ignore 47 | 48 | time.sleep(2) 49 | 50 | # start a bunch of node (this will load ros interface) 51 | pub_proc = [] 52 | 53 | def start_pub_node(pubnum): 54 | node_name = 'string_pub_node_' + str(pubnum) 55 | rospy.set_param('/' + node_name + '/topic_name', 'pub_' + str(pubnum)) 56 | rospy.set_param('/' + node_name + '/test_message', 'msg_' + str(pubnum)) 57 | node = roslaunch.core.Node('pyros_test', 'string_pub_node.py', name=node_name) 58 | try: 59 | pub_proc.append(launch.launch(node)) 60 | except roslaunch.RLException as rlexc: 61 | logging.error( 62 | "pyros_test is needed to run this. Please verify that it is installed in your ROS environment") 63 | raise 64 | 65 | # TODO : make MANY node / services / params to simulate complex robot and make profiling more realistic. 66 | time.sleep(2) # waiting for node to be up 67 | 68 | rosn = PyrosROS() 69 | 70 | rosn.setup( 71 | services=['/test/empsrv', '/test/trgsrv'], 72 | params=['/test/confirm_param'], 73 | enable_cache=connection_cache_proc.is_alive() 74 | ) 75 | 76 | print("Module LOADED") 77 | 78 | 79 | def update_loop(): 80 | total_count = 1024*1024*255 81 | count = 0 82 | start = time.time() 83 | pct = 0 84 | last_pct = -1 85 | max_pubnodes = 42 86 | node_step = 0 87 | last_node_step = -1 88 | while count < total_count: 89 | # time is ticking 90 | now = time.time() 91 | timedelta = now - start 92 | start = now 93 | 94 | rosn.update(timedelta) 95 | 96 | count += 1 97 | 98 | # creating and removing node while looping 99 | node_step = count * max_pubnodes * 2/ total_count 100 | if node_step != last_node_step: 101 | last_node_step = node_step 102 | if count < total_count/2: 103 | # adding node 104 | print("adding node {0}".format(node_step)) 105 | start_pub_node(node_step) 106 | 107 | elif pub_proc: 108 | # stopping node LIFO 109 | print("stopping node {0}".format(len(pub_proc)-1)) 110 | pub_proc.pop().stop() 111 | 112 | pct = count * 100 / total_count 113 | if pct != last_pct: 114 | last_pct = pct 115 | sys.stdout.write("\r" + str(last_pct) + "%") 116 | sys.stdout.flush() 117 | 118 | # In case you want to run kernprof here 119 | #update_loop() 120 | cProfile.run('update_loop()', sort='cumulative') 121 | 122 | # ensuring all process are finished 123 | for p in pub_proc: 124 | p.stop() 125 | 126 | if connection_cache_proc is not None: 127 | connection_cache_proc.stop() 128 | 129 | rospy.signal_shutdown('test complete') 130 | 131 | if roscore_process is not None: 132 | roscore_process.terminate() # make sure everything is stopped -------------------------------------------------------------------------------- /tests/test_pyros/test_ros_ctx_server.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function 2 | 3 | import time 4 | import pytest 5 | 6 | from pyros.client import PyrosClient 7 | from pyros.server.ctx_server import pyros_ctx 8 | 9 | pyros_interfaces_ros = pytest.importorskip("pyros_interfaces_ros") # , minversion="0.4") # TODO : make version avialable in pyros_interfaces_ros 10 | 11 | 12 | def testPyrosROSCtx(): 13 | # start ros system , before PyrosROS process and Client otherwise Client assumes there is problem ( discovery timeout ) 14 | # we might need to load pyros_setup here... 15 | try: 16 | import pyros_utils 17 | except ImportError: 18 | # TODO : find a proper way to log from a test like here... 19 | try : 20 | #_logger.warning("loading pyros_setup and configuring your ROS environment") 21 | import pyros_setup 22 | # This will load the pyros_setup configuration from the environment 23 | pyros_setup.configurable_import().configure().activate() 24 | import pyros_utils 25 | except ImportError: 26 | # This is expected when testing pyros by itself 27 | raise nose.SkipTest("pyros_utils could not be imported, and trying to import pyros_setup for dynamic ros setup failed.") 28 | 29 | master, roscore_proc = pyros_utils.get_master(spawn=True) # we start the master if needed 30 | 31 | assert master.is_online() 32 | 33 | with pyros_ctx(node_impl=pyros_interfaces_ros.PyrosROS) as ctx: 34 | 35 | assert isinstance(ctx.client, PyrosClient) 36 | 37 | # TODO : assert the context manager does his job ( HOW ? ) 38 | 39 | if roscore_proc is not None: 40 | roscore_proc.terminate() 41 | while roscore_proc.is_alive(): 42 | time.sleep(0.2) # waiting for roscore to die 43 | 44 | 45 | # Just in case we run this directly 46 | if __name__ == '__main__': 47 | import pytest 48 | pytest.main([ 49 | '-s', __file__, 50 | ]) 51 | -------------------------------------------------------------------------------- /tests/test_pyros_client/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyros-dev/pyros/59f6c8848a66481a4039cbf28b7673428181842e/tests/test_pyros_client/__init__.py -------------------------------------------------------------------------------- /tests/test_pyros_client/test_client.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | import os 4 | import sys 5 | 6 | # This is needed if running this test directly (without using nose loader) 7 | # prepending because ROS relies on package dirs list in PYTHONPATH and not isolated virtualenvs 8 | # And we need our current module to be found first. 9 | current_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')) 10 | # if not current_path in sys.path: 11 | sys.path.insert(1, current_path) # sys.path[0] is always current path as per python spec 12 | 13 | import unittest 14 | 15 | from pyros_interfaces_mock import PyrosMock 16 | from pyros.client.client import PyrosClient 17 | 18 | 19 | class TestPyrosClientOnMock(unittest.TestCase): 20 | def setUp(self): 21 | self.mockInstance = PyrosMock() 22 | # setting up mockinterface instance 23 | cmd_conn = self.mockInstance.start() 24 | self.client = PyrosClient(cmd_conn) 25 | 26 | def tearDown(self): 27 | self.mockInstance.shutdown() 28 | 29 | ### TOPICS ### 30 | 31 | # TODO : test list features more ! 32 | def test_list_all(self): 33 | t = self.client.topics() 34 | # Make sure we get all mockinterface topics 35 | assert t is not None 36 | 37 | 38 | def test_inject_None(self): # injecting None is meaningless and should return false 39 | assert self.client.topic_inject('random_topic', None) # simply check that it injected (default Empty since we support kwargs) 40 | 41 | def test_inject_Empty(self): 42 | assert self.client.topic_inject('random_topic') # simply check if injected 43 | 44 | #TODO : how to test strict backend with Mock ? 45 | #def test_inject_Wrong(self): 46 | # with assert_raises(Exception) as expt: # TODO : be more specific 47 | # data = 42 48 | # self.client.topic_inject('random_topic', data) # simply check exception raised 49 | # # assert_equal(expt, smthg...) 50 | 51 | def test_extract_None(self): 52 | assert self.client.topic_extract('random_topic') is None # simply check if nothing extracted 53 | 54 | def test_inject_extract_echo_Empty(self): 55 | assert self.client.topic_inject('random_topic') # default should be {} 56 | print "injected message content {0}".format({}) 57 | recv = self.client.topic_extract('random_topic') 58 | print "extracted message {0}".format(recv) 59 | assert recv == {} 60 | 61 | def test_inject_extract_echo_Simple_Arg(self): 62 | data = 'data_string' 63 | assert self.client.topic_inject('random_topic', data) 64 | print "injected message content {0}".format(data) 65 | recv = self.client.topic_extract('random_topic') 66 | print "extracted message content {0}".format(recv) 67 | assert recv == data 68 | 69 | def test_inject_extract_echo_Complex_Arg(self): 70 | data = {'first': 'first_string', 'second': 'second_string'} 71 | assert self.client.topic_inject('random_topic', data) 72 | print "injected message content {0}".format(data) 73 | recv = self.client.topic_extract('random_topic') 74 | print "extracted message content {0}".format(recv) 75 | assert recv == data 76 | 77 | def test_inject_extract_echo_Simple_KWArgs(self): 78 | assert self.client.topic_inject('random_topic', data='data_string') 79 | print "injected message content {0}".format("data='data_string'") 80 | recv = self.client.topic_extract('random_topic') 81 | print "extracted message content {0}".format(recv) 82 | assert recv == {'data':'data_string'} 83 | 84 | def test_inject_extract_echo_Complex_KWArgs(self): 85 | assert self.client.topic_inject('random_topic', first='first_string', second='second_string') 86 | print "injected message content {0}".format("first='first_string', second='second_string'") 87 | recv = self.client.topic_extract('random_topic') 88 | print "extracted message content {0}".format(recv) 89 | assert recv == {'first': 'first_string', 'second': 'second_string'} 90 | 91 | ### SERVICES ### 92 | # TODO : think how to test strict backend with Mock ? 93 | #def test_call_Wrong(self): 94 | # with assert_raises(Exception) as expt: # TODO : be more specific 95 | # data = 42 96 | # print "request content {0}".format(data) 97 | # resp = self.client.service_call('random_service', data) # simply check exception raised 98 | # # assert_equal(expt, smthg...) 99 | 100 | def test_call_echo_Simple_Arg(self): 101 | data = 'data_string' 102 | print "request content {0}".format(data) 103 | resp = self.client.service_call('random_service', data) 104 | print "response content {0}".format(resp) 105 | assert resp == data 106 | 107 | def test_call_echo_None(self): 108 | print "request content {0}".format({}) 109 | resp = self.client.service_call('random_service', None) # calling with None is interpreted as default (Empty) call (since we support kwargs) 110 | print "response content {0}".format(resp) 111 | assert resp == {} 112 | 113 | def test_call_echo_Empty(self): 114 | print "request content {0}".format({}) 115 | resp = self.client.service_call('random_service') 116 | print "response content {0}".format(resp) 117 | assert resp == {} 118 | 119 | def test_call_echo_Complex_Arg(self): 120 | data = {'first': 'first_string', 'second': 'second_string'} 121 | print "request content {0}".format(data) 122 | resp = self.client.service_call('random_service', data) 123 | print "response content {0}".format(resp) 124 | assert resp == data 125 | 126 | def test_call_echo_Simple_KWArgs(self): 127 | print "request content {0}".format("data='data_string'") 128 | resp = self.client.service_call('random_service', data='data_string') 129 | print "response content {0}".format(resp) 130 | assert resp == {'data': 'data_string'} 131 | 132 | def test_call_echo_Complex_KWArgs(self): 133 | print "injected message content {0}".format("first='first_string', second='second_string'") 134 | resp = self.client.service_call('random_service', first='first_string', second='second_string') 135 | print "extracted message content {0}".format(resp) 136 | assert resp == {'first': 'first_string', 'second': 'second_string'} 137 | 138 | ### PARAMS ### 139 | #def test_set_None(self): # injecting None is meaningless and should return false 140 | # assert not self.client.param_set('random_param', None) # simply check that it didnt set 141 | 142 | def test_set_Empty(self): 143 | assert self.client.param_set('random_param') # simply check if set 144 | 145 | def test_get_None(self): 146 | assert self.client.param_get('random_param') is None # simply check if nothing got 147 | 148 | def test_set_get_echo_Empty(self): 149 | assert self.client.param_set('random_param') # default should be {} 150 | print "set value content {0}".format({}) 151 | recv = self.client.param_get('random_param') 152 | print "got value content {0}".format(recv) 153 | assert recv == {} 154 | 155 | def test_set_get_echo_Simple_Arg(self): 156 | data = 'data_string' 157 | assert self.client.param_set('random_param', data) 158 | print "set value content {0}".format(data) 159 | recv = self.client.param_get('random_param') 160 | print "got value content {0}".format(recv) 161 | assert recv == data 162 | 163 | def test_set_get_echo_Complex_Arg(self): 164 | data = {'first': 'first_string', 'second': 'second_string'} 165 | assert self.client.param_set('random_param', data) 166 | print "set value content {0}".format(data) 167 | recv = self.client.param_get('random_param') 168 | print "got value content {0}".format(recv) 169 | assert recv == data 170 | 171 | def test_set_get_echo_Simple_KWArgs(self): 172 | assert self.client.param_set('random_param', data='data_string') 173 | print "set value content {0}".format("data='data_string'") 174 | recv = self.client.param_get('random_param') 175 | print "got value content {0}".format(recv) 176 | assert recv == {'data':'data_string'} 177 | 178 | def test_set_get_echo_Complex_KWArgs(self): 179 | assert self.client.param_set('random_param', first='first_string', second='second_string') 180 | print "set value content {0}".format("first='first_string', second='second_string'") 181 | recv = self.client.param_get('random_param') 182 | print "got value content {0}".format(recv) 183 | assert recv == {'first': 'first_string', 'second': 'second_string'} 184 | 185 | # TODO test service that throw exception 186 | 187 | # Just in case we run this directly 188 | if __name__ == '__main__': 189 | import pytest 190 | pytest.main([ 191 | '-s', __file__, 192 | ]) 193 | -------------------------------------------------------------------------------- /tests/test_pyros_server/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyros-dev/pyros/59f6c8848a66481a4039cbf28b7673428181842e/tests/test_pyros_server/__init__.py -------------------------------------------------------------------------------- /tests/test_pyros_server/test_mock_ctx_server.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | from pyros.client.client import PyrosClient 4 | from pyros.server.ctx_server import pyros_ctx 5 | from pyros_interfaces_mock import PyrosMock 6 | 7 | 8 | def testPyrosMockCtx(): 9 | with pyros_ctx(node_impl=PyrosMock) as ctx: 10 | assert isinstance(ctx.client, PyrosClient) 11 | 12 | # TODO : assert the context manager does his job ( HOW ? ) 13 | 14 | 15 | # Just in case we run this directly 16 | if __name__ == '__main__': 17 | import pytest 18 | pytest.main([ 19 | '-s', __file__, 20 | ]) 21 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # content of: tox.ini , put in same dir as setup.py 2 | [tox] 3 | 4 | skip_missing_interpreters=True 5 | 6 | envlist = 7 | # based on ros distro with python2 base 8 | py27-{indigo,kinetic,latest}, 9 | py34-indigo, 10 | py35-kinetic, 11 | # check latest python with all distro, in case we get a late python on top of OS packages 12 | py36-{indigo,kinetic,latest} 13 | 14 | 15 | #, pypy 16 | #, pypy3 17 | 18 | [travis] 19 | python = 20 | 21 | # we test every current ROS1 distro on python 2.7 (official python support for ROS1) 22 | 2.7: py27 23 | # specific old python supported natively on ubuntu/ROS LTS distro 24 | 3.4: py34 25 | 3.5: py35 26 | # we test every current ROS1 distro on latest python (to ensure support from latest python) 27 | 3.6: py36 28 | 29 | # not tested yet 30 | #pypy = pypy 31 | #pypy3 = pypy3 32 | 33 | 34 | # We depend on travis matrix 35 | [travis:env] 36 | ROS_DISTRO = 37 | kinetic: kinetic 38 | indigo: indigo 39 | latest: latest 40 | 41 | 42 | [testenv] 43 | setenv = 44 | # prevent tox to create a bunch of useless bytecode files in tests/ 45 | PYTHONDONTWRITEBYTECODE=1 46 | 47 | # Dependencies matching the version in each ROS distro 48 | deps = 49 | 50 | indigo: -rrequirements/ROS/indigo.txt 51 | indigo: -rrequirements/tests.txt 52 | indigo: -rrequirements/dev.txt 53 | 54 | kinetic: -rrequirements/ROS/kinetic.txt 55 | kinetic: -rrequirements/tests.txt 56 | kinetic: -rrequirements/dev.txt 57 | 58 | latest: -rrequirements/tests.txt 59 | latest: -rrequirements/dev.txt 60 | 61 | # to always force recreation and avoid unexpected side effects 62 | recreate=True 63 | 64 | changedir = tests 65 | 66 | # to allow access to ROS packages 67 | # sitepackages=True 68 | # We do not want any access to system (same as basic travis python testing) 69 | 70 | commands= 71 | # we want to make sure python finds the installed package in tox env 72 | # and doesn't confuse with pyc generated during dev (which happens if we use self test feature here) 73 | python -m pytest --basetemp={envtmpdir} test_pyros_server {posargs} 74 | python -m pytest --basetemp={envtmpdir} test_pyros_client {posargs} 75 | # testing various interfaces integration... 76 | # python -m pytest --basetemp={envtmpdir} test_pyros {posargs} 77 | # TODO : make target platforms tests possible in pure python somehow... 78 | 79 | # Note : -s here might break your terminal... 80 | 81 | --------------------------------------------------------------------------------