├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── THIRD-PARTY-NOTICES ├── readmeimages ├── cloud_architecture.png ├── fleet_robomaker.png └── multibot.png ├── setup ├── aws_setup_sample.bash ├── custom_dependencies.yaml ├── fleetLauncherApp │ ├── README.md │ ├── base_template.yml │ ├── fleetLauncherLambda │ │ ├── __init__.py │ │ ├── app.py │ │ └── event.json │ ├── requirements.txt │ └── template.yml ├── requirements.txt └── ros_setup.bash └── simulation_ws ├── .rosinstall └── src └── robot_fleet ├── CMakeLists.txt ├── config ├── costmap_common.yaml └── costmap_local.yaml ├── launch ├── husky_app_only.launch ├── husky_app_with_rtsp.launch ├── include │ ├── amcl.launch │ └── move_base.launch └── robot_fleet_rosbridge.launch ├── package.xml ├── rviz └── basic_data.rviz ├── scripts ├── client_rosbridge.py └── gazebo_model_mover.py ├── src └── move_object.cc └── static_models_w_plugin └── husky ├── husky-1_3.sdf ├── meshes ├── base_link.stl ├── bumper.stl ├── top_plate.stl ├── user_rail.stl └── wheel.stl ├── model-1_4.sdf ├── model.config └── model.sdf /.gitignore: -------------------------------------------------------------------------------- 1 | simulation_ws/build 2 | simulation_ws/bundle 3 | simulation_ws/install 4 | simulation_ws/log 5 | simulation_ws/src/deps 6 | simulation_ws/devel 7 | simulation_ws/build 8 | .current-aws-stack 9 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Robot fleet simulation using concurrent gazebo instances 2 | 3 | robot_fleet rospackage within this repo enables co-ordination between multiple [gazebo](http://gazebosim.org/tutorials?tut=ros_overview) instances to simulate a fleet of robots. We can compound this further by having multiple robots within same simulation as mentioned [here](https://answers.ros.org/question/41433/multiple-robots-simulation-and-navigation/). However, this current version expects a single robot with physics in each simulation, and uses [rosbridge](http://wiki.ros.org/rosbridge_suite) server/clients to communicate between the simulation instances. 4 | 5 | ## Requirements 6 | * [ROS Melodic](http://wiki.ros.org/melodic) - Other ROS versions have not been tested. 7 | * [Colcon](https://colcon.readthedocs.io) - Tested on colcon. catkin confirmed working on melodic only. 8 | * [Gazebo9](http://gazebosim.org/blog/gazebo9) - Gazebo simulator. 9 | * [Colcon bundle](https://github.com/colcon/colcon-bundle) - Enables packaging the application for running on RoboMaker. 10 | * [boto3](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html) - AWS SDK for Python 11 | * [awscli](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html) - AWS CLI setup on local machine with appropriate [credentials](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html) 12 | 13 | ## Usage 14 | 15 | ### Local Execution 16 | This section is to run a single instace of the above application. This will NOT kick off multiple robots, but is a good way to see whats under the hood in this application. Has been testing on colcon/catkin on ROS Melodic, Ubuntu 18.04. 17 | ``` 18 | git clone https://github.com/aws-samples/multi-robot-fleet-sample-application.git 19 | cd multi-robot-fleet-sample-application 20 | ./setup/ros_setup.bash 21 | ``` 22 | 23 | Running this file performs 24 | * new rosdep definition for - roslibpy 25 | * rosdep update 26 | * rosws update - pulls in husky and aws-robomaker-small-warehouse-world repositories 27 | * Applies minor patches reqd to work with AWS RoboMaker 28 | * rosdep install -y —from-path 29 | * colcon build; source install/setup.bash 30 | * colcon bundle 31 | 32 | Set the appropriate environement variables required for the application 33 | ``` 34 | export ROBOT_NAME=server # unique robot name 35 | export ROSBRIDGE_STATE=SERVER # SERVER or CLIENT 36 | export ROSBRIDGE_IP=localhost # localhost for SERVER. IP of rosbridge for CLIENT 37 | export START_X=0 # start location of robot 38 | export START_Y=0 39 | export START_YAW=0 40 | export HUSKY_REALSENSE_ENABLED=true 41 | export HUSKY_LMS1XX_ENABLED=true 42 | export USE_CUSTOM_MOVE_OBJECT_GAZEBO_PLUGIN=true # set to true if you use custom plugin to move robot. False uses regular gazebo rostopics 43 | ``` 44 | 45 | To see the application running on your local machine, run the following command. 46 | ``` 47 | source simulation_ws/install/setup.bash 48 | roslaunch robot_fleet robot_fleet_rosbridge.launch gui:=true 49 | ``` 50 | 51 | ### Cloud Execution 52 | 53 | To setup multi robot fleet simulation on your AWS account, run the following command **AFTER** running the **Local execution** section. The local ROS application need not be running for this. 54 | 55 | ``` 56 | ./setup/aws_setup_sample.bash 57 | ``` 58 | 59 | Running this file performs 60 | 61 | * Deploying the cloudformation stack to setup the AWS environment 62 | * Creates and uploads the bundle file to AWS 63 | * Kicks off a lambda function to start the multi-robot-fleet on AWS 64 | * Picks the corresponding params for the simulations from setup/fleetLauncherApp/fleetLauncherLambda/event.json 65 | 66 | ![fleet_in_robomaker](readmeimages/fleet_robomaker.png) 67 | 68 | *Figure 1. A multiple robot fleet running in AWS RoboMaker.* 69 | 70 | #### Setting the parameters in the cloud-based simulation 71 | 72 | To update the parameters, such as robot name (ROBOT) and starting positions, you can edit the event.json file at **fleetLaucherApp/fleetLauncherLambda/event.json**. 73 | 74 | Here is the example JSON: 75 | 76 | ``` 77 | { 78 | "robots": [ 79 | { 80 | "name": "robot1", 81 | "environmentVariables": { 82 | "START_X": "2", 83 | "START_Y": "2", 84 | "START_YAW": "3.143", 85 | "HUSKY_REALSENSE_ENABLED": "true", 86 | "HUSKY_LMS1XX_ENABLED": "true", 87 | "USE_CUSTOM_MOVE_OBJECT_GAZEBO_PLUGIN":"true" 88 | }, 89 | "packageName": "robot_fleet", 90 | "launchFile": "robot_fleet_rosbridge.launch" 91 | } 92 | ... 93 | ], 94 | "server": { 95 | "name": "SERVER", 96 | "environmentVariables": { 97 | "START_X": "0", 98 | "START_Y": "0", 99 | "START_YAW": "0", 100 | "HUSKY_REALSENSE_ENABLED": "true", 101 | "HUSKY_LMS1XX_ENABLED": "true", 102 | "USE_CUSTOM_MOVE_OBJECT_GAZEBO_PLUGIN":"true" 103 | }, 104 | "packageName": "robot_fleet", 105 | "launchFile": "robot_fleet_rosbridge.launch" 106 | } 107 | } 108 | ``` 109 | 110 | There are a group of optional attributes you can set if a ROSBridge server is already running or you want to customize the application details. 111 | 112 | ``` 113 | "serverIP": "", 114 | "simulationJobParams": { 115 | "vpcConfig": { 116 | "subnets": [], 117 | "securityGroups": [] 118 | }, 119 | "iamRole": "", 120 | "outputLocation": { 121 | "s3Bucket": "", 122 | "s3Key": "" 123 | } 124 | }, 125 | "simulationApplicationArn": "" 126 | ``` 127 | 128 | #### Serverless Application Deployment 129 | 130 | If you want to run the simulation launcher lambda function in AWS and link to an automated testing workflow, follow the README instructions [here](setup/fleetLauncherApp/README.md). 131 | 132 | ## How it works 133 | 134 | * One gazebo instance acts as rosbridge_server and others as rosbridge clients 135 | * We use rosbridge to pass current position information of robots in all gazebo instances 136 | * For all other gazebo instances, we dynamically create and remove static robot models that represent the robot running its actual environment. We use gazebo plugin to move the static model in sync with its actual position information that we gather from rosbridge 137 | * With RoboMaker, its easy to spin up the simulation with ROSBRIGE_SERVER, wait for its IP and pass that along for rest of CLIENT robots. 138 | 139 | ## Architecture 140 | 141 | ![multibot_image](readmeimages/multibot.png) 142 | 143 | ![cloud_architecture](readmeimages/cloud_architecture.png) 144 | 145 | ## Workshop for the Application 146 | 147 | Follow the workshop built around this application by visiting our [workshop page](https://stage.robomakerworkshops.com/ws/multi_robot_fleet_simulations) 148 | 149 | ## Known issues 150 | 151 | This package does not perform time synchronization, and simulation update lockstep between gazebo instances. Best results have been seen with homogeneous software stacks running on the gazebo instances. 152 | 153 | ## Security 154 | 155 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. 156 | 157 | ## License 158 | 159 | This library is licensed under the MIT-0 License. See the LICENSE file. 160 | 161 | -------------------------------------------------------------------------------- /THIRD-PARTY-NOTICES: -------------------------------------------------------------------------------- 1 | ** gazebo_models; version 1.0 -- 2 | https://github.com/osrf/gazebo_models/tree/master/husky 3 | Software License Agreement (Creative Commons Attribution 3.0 Unported) 4 | 5 | Copyright 2012 Nathan Koenig 6 | 7 | Licensed under the Creative Commons Attribution 3.0 Unported License 8 | (the "License"); you may not use this file except in compliance with the 9 | License. You may obtain a copy of the License at 10 | 11 | http://creativecommons.org/licenses/by/3.0/ 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | 19 | Creative Commons Attribution 3.0 Unported CREATIVE COMMONS CORPORATION IS NOT A 20 | LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES 21 | NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 22 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING 23 | THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 24 | ITS USE. 25 | 26 | License 27 | 28 | THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE 29 | COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY 30 | COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS 31 | AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. 32 | 33 | BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE 34 | BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE 35 | CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE 36 | IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. 37 | 38 | 1. Definitions 39 | 40 | a. "Adaptation" means a work based upon the Work, or upon the Work and 41 | other pre-existing works, such as a translation, adaptation, derivative 42 | work, arrangement of music or other alterations of a literary or artistic 43 | work, or phonogram or performance and includes cinematographic 44 | adaptations or any other form in which the Work may be recast, 45 | transformed, or adapted including in any form recognizably derived from 46 | the original, except that a work that constitutes a Collection will not 47 | be considered an Adaptation for the purpose of this License. For the 48 | avoidance of doubt, where the Work is a musical work, performance or 49 | phonogram, the synchronization of the Work in timed-relation with a 50 | moving image ("synching") will be considered an Adaptation for the 51 | purpose of this License. 52 | 53 | b. "Collection" means a collection of literary or artistic works, such as 54 | encyclopedias and anthologies, or performances, phonograms or broadcasts, 55 | or other works or subject matter other than works listed in Section 1(f) 56 | below, which, by reason of the selection and arrangement of their 57 | contents, constitute intellectual creations, in which the Work is 58 | included in its entirety in unmodified form along with one or more other 59 | contributions, each constituting separate and independent works in 60 | themselves, which together are assembled into a collective whole. A work 61 | that constitutes a Collection will not be considered an Adaptation (as 62 | defined above) for the purposes of this License. 63 | 64 | c. "Distribute" means to make available to the public the original and 65 | copies of the Work or Adaptation, as appropriate, through sale or other 66 | transfer of ownership. 67 | 68 | d. "Licensor" means the individual, individuals, entity or entities that 69 | offer(s) the Work under the terms of this License. 70 | 71 | e. "Original Author" means, in the case of a literary or artistic work, 72 | the individual, individuals, entity or entities who created the Work or 73 | if no individual or entity can be identified, the publisher; and in 74 | addition (i) in the case of a performance the actors, singers, musicians, 75 | dancers, and other persons who act, sing, deliver, declaim, play in, 76 | interpret or otherwise perform literary or artistic works or expressions 77 | of folklore; (ii) in the case of a phonogram the producer being the 78 | person or legal entity who first fixes the sounds of a performance or 79 | other sounds; and, (iii) in the case of broadcasts, the organization that 80 | transmits the broadcast. 81 | 82 | f. "Work" means the literary and/or artistic work offered under the terms 83 | of this License including without limitation any production in the 84 | literary, scientific and artistic domain, whatever may be the mode or 85 | form of its expression including digital form, such as a book, pamphlet 86 | and other writing; a lecture, address, sermon or other work of the same 87 | nature; a dramatic or dramatico-musical work; a choreographic work or 88 | entertainment in dumb show; a musical composition with or without words; 89 | a cinematographic work to which are assimilated works expressed by a 90 | process analogous to cinematography; a work of drawing, painting, 91 | architecture, sculpture, engraving or lithography; a photographic work to 92 | which are assimilated works expressed by a process analogous to 93 | photography; a work of applied art; an illustration, map, plan, sketch or 94 | three-dimensional work relative to geography, topography, architecture or 95 | science; a performance; a broadcast; a phonogram; a compilation of data 96 | to the extent it is protected as a copyrightable work; or a work 97 | performed by a variety or circus performer to the extent it is not 98 | otherwise considered a literary or artistic work. 99 | 100 | g. "You" means an individual or entity exercising rights under this 101 | License who has not previously violated the terms of this License with 102 | respect to the Work, or who has received express permission from the 103 | Licensor to exercise rights under this License despite a previous 104 | violation. 105 | 106 | h. "Publicly Perform" means to perform public recitations of the Work and 107 | to communicate to the public those public recitations, by any means or 108 | process, including by wire or wireless means or public digital 109 | performances; to make available to the public Works in such a way that 110 | members of the public may access these Works from a place and at a place 111 | individually chosen by them; to perform the Work to the public by any 112 | means or process and the communication to the public of the performances 113 | of the Work, including by public digital performance; to broadcast and 114 | rebroadcast the Work by any means including signs, sounds or images. 115 | 116 | i. "Reproduce" means to make copies of the Work by any means including 117 | without limitation by sound or visual recordings and the right of 118 | fixation and reproducing fixations of the Work, including storage of a 119 | protected performance or phonogram in digital form or other electronic 120 | medium. 121 | 122 | 2. Fair Dealing Rights. Nothing in this License is intended to reduce, 123 | limit, or restrict any uses free from copyright or rights arising from 124 | limitations or exceptions that are provided for in connection with the 125 | copyright protection under copyright law or other applicable laws. 126 | 127 | 3. License Grant. Subject to the terms and conditions of this License, 128 | Licensor hereby grants You a worldwide, royalty-free, non-exclusive, 129 | perpetual (for the duration of the applicable copyright) license to exercise 130 | the rights in the Work as stated below: 131 | 132 | a. to Reproduce the Work, to incorporate the Work into one or more 133 | Collections, and to Reproduce the Work as incorporated in the 134 | Collections; 135 | 136 | b. to create and Reproduce Adaptations provided that any such Adaptation, 137 | including any translation in any medium, takes reasonable steps to 138 | clearly label, demarcate or otherwise identify that changes were made to 139 | the original Work. For example, a translation could be marked "The 140 | original work was translated from English to Spanish," or a modification 141 | could indicate "The original work has been modified."; 142 | 143 | c. to Distribute and Publicly Perform the Work including as incorporated 144 | in Collections; and, 145 | 146 | d. to Distribute and Publicly Perform Adaptations. 147 | 148 | e. For the avoidance of doubt: 149 | 150 | i. Non-waivable Compulsory License Schemes. In those jurisdictions in 151 | which the right to collect royalties through any statutory or 152 | compulsory licensing scheme cannot be waived, the Licensor reserves 153 | the exclusive right to collect such royalties for any exercise by You 154 | of the rights granted under this License; 155 | 156 | ii. Waivable Compulsory License Schemes. In those jurisdictions in 157 | which the right to collect royalties through any statutory or 158 | compulsory licensing scheme can be waived, the Licensor waives the 159 | exclusive right to collect such royalties for any exercise by You of 160 | the rights granted under this License; and, 161 | 162 | iii. Voluntary License Schemes. The Licensor waives the right to 163 | collect royalties, whether individually or, in the event that the 164 | Licensor is a member of a collecting society that administers 165 | voluntary licensing schemes, via that society, from any exercise by 166 | You of the rights granted under this License. 167 | 168 | The above rights may be exercised in all media and formats whether now known 169 | or hereafter devised. The above rights include the right to make such 170 | modifications as are technically necessary to exercise the rights in other 171 | media and formats. Subject to Section 8(f), all rights not expressly granted 172 | by Licensor are hereby reserved. 173 | 174 | 4. Restrictions. The license granted in Section 3 above is expressly made 175 | subject to and limited by the following restrictions: 176 | 177 | a. You may Distribute or Publicly Perform the Work only under the terms 178 | of this License. You must include a copy of, or the Uniform Resource 179 | Identifier (URI) for, this License with every copy of the Work You 180 | Distribute or Publicly Perform. You may not offer or impose any terms on 181 | the Work that restrict the terms of this License or the ability of the 182 | recipient of the Work to exercise the rights granted to that recipient 183 | under the terms of the License. You may not sublicense the Work. You must 184 | keep intact all notices that refer to this License and to the disclaimer 185 | of warranties with every copy of the Work You Distribute or Publicly 186 | Perform. When You Distribute or Publicly Perform the Work, You may not 187 | impose any effective technological measures on the Work that restrict the 188 | ability of a recipient of the Work from You to exercise the rights 189 | granted to that recipient under the terms of the License. This Section 190 | 4(a) applies to the Work as incorporated in a Collection, but this does 191 | not require the Collection apart from the Work itself to be made subject 192 | to the terms of this License. If You create a Collection, upon notice 193 | from any Licensor You must, to the extent practicable, remove from the 194 | Collection any credit as required by Section 4(b), as requested. If You 195 | create an Adaptation, upon notice from any Licensor You must, to the 196 | extent practicable, remove from the Adaptation any credit as required by 197 | Section 4(b), as requested. 198 | 199 | b. If You Distribute, or Publicly Perform the Work or any Adaptations or 200 | Collections, You must, unless a request has been made pursuant to Section 201 | 4(a), keep intact all copyright notices for the Work and provide, 202 | reasonable to the medium or means You are utilizing: (i) the name of the 203 | Original Author (or pseudonym, if applicable) if supplied, and/or if the 204 | Original Author and/or Licensor designate another party or parties (e.g., 205 | a sponsor institute, publishing entity, journal) for attribution 206 | ("Attribution Parties") in Licensor's copyright notice, terms of service 207 | or by other reasonable means, the name of such party or parties; (ii) the 208 | title of the Work if supplied; (iii) to the extent reasonably 209 | practicable, the URI, if any, that Licensor specifies to be associated 210 | with the Work, unless such URI does not refer to the copyright notice or 211 | licensing information for the Work; and (iv), consistent with Section 212 | 3(b), in the case of an Adaptation, a credit identifying the use of the 213 | Work in the Adaptation (e.g., "French translation of the Work by Original 214 | Author," or "Screenplay based on original Work by Original Author"). The 215 | credit required by this Section 4 (b) may be implemented in any 216 | reasonable manner; provided, however, that in the case of a Adaptation or 217 | Collection, at a minimum such credit will appear, if a credit for all 218 | contributing authors of the Adaptation or Collection appears, then as 219 | part of these credits and in a manner at least as prominent as the 220 | credits for the other contributing authors. For the avoidance of doubt, 221 | You may only use the credit required by this Section for the purpose of 222 | attribution in the manner set out above and, by exercising Your rights 223 | under this License, You may not implicitly or explicitly assert or imply 224 | any connection with, sponsorship or endorsement by the Original Author, 225 | Licensor and/or Attribution Parties, as appropriate, of You or Your use 226 | of the Work, without the separate, express prior written permission of 227 | the Original Author, Licensor and/or Attribution Parties. 228 | 229 | c. Except as otherwise agreed in writing by the Licensor or as may be 230 | otherwise permitted by applicable law, if You Reproduce, Distribute or 231 | Publicly Perform the Work either by itself or as part of any Adaptations 232 | or Collections, You must not distort, mutilate, modify or take other 233 | derogatory action in relation to the Work which would be prejudicial to 234 | the Original Author's honor or reputation. Licensor agrees that in those 235 | jurisdictions (e.g. Japan), in which any exercise of the right granted in 236 | Section 3(b) of this License (the right to make Adaptations) would be 237 | deemed to be a distortion, mutilation, modification or other derogatory 238 | action prejudicial to the Original Author's honor and reputation, the 239 | Licensor will waive or not assert, as appropriate, this Section, to the 240 | fullest extent permitted by the applicable national law, to enable You to 241 | reasonably exercise Your right under Section 3(b) of this License (right 242 | to make Adaptations) but not otherwise. 243 | 244 | 5. Representations, Warranties and Disclaimer 245 | 246 | UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR 247 | OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND 248 | CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, 249 | WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A 250 | PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER 251 | DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT 252 | DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED 253 | WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. 254 | 255 | 6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, 256 | IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY 257 | SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING 258 | OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN 259 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 260 | 261 | 7. Termination 262 | 263 | a. This License and the rights granted hereunder will terminate 264 | automatically upon any breach by You of the terms of this License. 265 | Individuals or entities who have received Adaptations or Collections from 266 | You under this License, however, will not have their licenses terminated 267 | provided such individuals or entities remain in full compliance with 268 | those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any 269 | termination of this License. 270 | 271 | b. Subject to the above terms and conditions, the license granted here is 272 | perpetual (for the duration of the applicable copyright in the Work). 273 | Notwithstanding the above, Licensor reserves the right to release the 274 | Work under different license terms or to stop distributing the Work at 275 | any time; provided, however that any such election will not serve to 276 | withdraw this License (or any other license that has been, or is required 277 | to be, granted under the terms of this License), and this License will 278 | continue in full force and effect unless terminated as stated above. 279 | 280 | 8. Miscellaneous 281 | 282 | a. Each time You Distribute or Publicly Perform the Work or a Collection, 283 | the Licensor offers to the recipient a license to the Work on the same 284 | terms and conditions as the license granted to You under this License. 285 | 286 | b. Each time You Distribute or Publicly Perform an Adaptation, Licensor 287 | offers to the recipient a license to the original Work on the same terms 288 | and conditions as the license granted to You under this License. 289 | 290 | c. If any provision of this License is invalid or unenforceable under 291 | applicable law, it shall not affect the validity or enforceability of the 292 | remainder of the terms of this License, and without further action by the 293 | parties to this agreement, such provision shall be reformed to the 294 | minimum extent necessary to make such provision valid and enforceable. 295 | 296 | d. No term or provision of this License shall be deemed waived and no 297 | breach consented to unless such waiver or consent shall be in writing and 298 | signed by the party to be charged with such waiver or consent. This 299 | License constitutes the entire agreement between the parties with respect 300 | to the Work licensed here. There are no understandings, agreements or 301 | representations with respect to the Work not specified here. Licensor 302 | shall not be bound by any additional provisions that may appear in any 303 | communication from You. 304 | 305 | e. This License may not be modified without the mutual written agreement 306 | of the Licensor and You. 307 | 308 | f. The rights granted under, and the subject matter referenced, in this 309 | License were drafted utilizing the terminology of the Berne Convention 310 | for the Protection of Literary and Artistic Works (as amended on 311 | September 28, 1979), the Rome Convention of 1961, the WIPO Copyright 312 | Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 and 313 | the Universal Copyright Convention (as revised on July 24, 1971). These 314 | rights and subject matter take effect in the relevant jurisdiction in 315 | which the License terms are sought to be enforced according to the 316 | corresponding provisions of the implementation of those treaty provisions 317 | in the applicable national law. If the standard suite of rights granted 318 | under applicable copyright law includes additional rights not granted 319 | under this License, such additional rights are deemed to be included in 320 | the License; this License is not intended to restrict the license of any 321 | rights under applicable law. 322 | 323 | Creative Commons Notice 324 | 325 | Creative Commons is not a party to this License, and makes no warranty 326 | whatsoever in connection with the Work. Creative Commons will not be liable to 327 | You or any party on any legal theory for any damages whatsoever, including 328 | without limitation any general, special, incidental or consequential damages 329 | arising in connection to this license. Notwithstanding the foregoing two (2) 330 | sentences, if Creative Commons has expressly identified itself as the Licensor 331 | hereunder, it shall have all rights and obligations of Licensor. 332 | 333 | Except for the limited purpose of indicating to the public that the Work is 334 | licensed under the CCPL, Creative Commons does not authorize the use by either 335 | party of the trademark "Creative Commons" or any related trademark or logo of 336 | Creative Commons without the prior written consent of Creative Commons. Any 337 | permitted use will be in compliance with Creative Commons' then-current 338 | trademark usage guidelines, as may be published on its website or otherwise 339 | made available upon request from time to time. For the avoidance of doubt, this 340 | trademark restriction does not form part of this License. 341 | 342 | Creative Commons may be contacted at http://creativecommons.org/. -------------------------------------------------------------------------------- /readmeimages/cloud_architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/multi-robot-fleet-sample-application/753f3ff4bf217297b35867aa0a536cd2391a7d21/readmeimages/cloud_architecture.png -------------------------------------------------------------------------------- /readmeimages/fleet_robomaker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/multi-robot-fleet-sample-application/753f3ff4bf217297b35867aa0a536cd2391a7d21/readmeimages/fleet_robomaker.png -------------------------------------------------------------------------------- /readmeimages/multibot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/multi-robot-fleet-sample-application/753f3ff4bf217297b35867aa0a536cd2391a7d21/readmeimages/multibot.png -------------------------------------------------------------------------------- /setup/aws_setup_sample.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script will create the AWS infrastructure required to launch the demo, as well as future multi-robot apps. 4 | BASE_DIR=`pwd` 5 | ROS_APP_DIR=$BASE_DIR/simulation_ws 6 | LAUNCHER_APP_DIR=$BASE_DIR/setup/fleetLauncherApp 7 | STACK_NAME=multibotrosbridge 8 | AWS_PROFILE=default 9 | CURRENT_STACK=.current-aws-stack 10 | S3_OUTPUT_KEY=multirobotdemo/bundle/output.tar 11 | 12 | sudo pip3 install boto3 13 | 14 | # Setup AWS resources for the application 15 | if [ ! -f "$CURRENT_STACK" ]; then 16 | # Deploy base stack (NOTE: This will NOT deploy the SAM-based Lambda function. To do that, follow the instructions in the README.) 17 | aws cloudformation deploy --template-file $LAUNCHER_APP_DIR/base_template.yml --stack-name $STACK_NAME --capabilities CAPABILITY_NAMED_IAM --parameter-overrides SimulationApplicationS3Key=$S3_OUTPUT_KEY --profile $AWS_PROFILE 18 | aws cloudformation wait stack-create-complete --stack-name $STACK_NAME --profile $AWS_PROFILE && echo "stackname=$STACK_NAME" > .current-aws-stack 19 | fi 20 | 21 | # Upload the application bundle. 22 | s3Bucket=$(aws cloudformation describe-stacks --stack-name $STACK_NAME --query "Stacks[0].Outputs[?OutputKey=='RoboMakerS3Bucket'].OutputValue" --profile $AWS_PROFILE --output text) 23 | 24 | if [ ! $s3Bucket==None ] || [ -f "$ROS_APP_DIR/bundle/output.tar" ] 25 | then 26 | echo "Uploading files..." 27 | aws s3 cp $ROS_APP_DIR/bundle/output.tar s3://$s3Bucket/$S3_OUTPUT_KEY --profile $AWS_PROFILE 28 | else 29 | echo "Bundle could not be uploaded. Please ensure \"$ROS_APP_DIR/bundle/output.tar\" and the S3 bucket \"$s3Bucket\" exist." 30 | exit 31 | fi 32 | 33 | read -t 15 -p "Press any key to launch the sample simulation, or Ctrl+c within 15 seconds to exit." some_key 34 | python3 $LAUNCHER_APP_DIR/fleetLauncherLambda/app.py $STACK_NAME 35 | -------------------------------------------------------------------------------- /setup/custom_dependencies.yaml: -------------------------------------------------------------------------------- 1 | roslibpy-pip: 2 | ubuntu: 3 | pip: 4 | packages: [roslibpy] 5 | -------------------------------------------------------------------------------- /setup/fleetLauncherApp/README.md: -------------------------------------------------------------------------------- 1 | # Multi-robot Fleet Simulation Launcher 2 | 3 | This SAM application will launch a fleet simulation on AWS RoboMaker. 4 | 5 | ![Cloud_Architecture](../..//readmeimages/cloud_architecture.png) 6 | 7 | The following attributes will be created on deployment: 8 | 9 | A nested base cloudformation stack (base_template.yml) with the following resources: 10 | - **VPC:** A reference to the created VPC 11 | - **Default Security Group:** The default security group created with the VPC 12 | - **Public Subnet 1:** A reference to the public subnet in the 1st Availability Zone 13 | - **Public Subnet 2:** A reference to the public subnet in the 2nd Availability Zone 14 | - **S3 Bucket:** The S3 bucket used to store your AWS RoboMaker assets. 15 | - **Simulation Role:** The IAM role that the simulation application will use to access AWS resources. 16 | - **Simulation Application:** A RoboMaker simulation application created on your behalf. 17 | 18 | The main stack with the serverless app (lambda function, template.yml): 19 | - **Launcher Lambda Function:** A Lambda function that will launch the set of simulations. 20 | - **Launcher Lambda Function Role:** An IAM role to use with the lambda function. 21 | 22 | ## Deployment 23 | 24 | To deploy the SAM application you will first need an S3 bucket. 25 | 26 | ``` 27 | aws s3 mb s3:// 28 | ``` 29 | 30 | Then, you can run the SAM build, package and deploy commands from this directory: 31 | ``` 32 | sam build --use-container -m ./requirements.txt 33 | sam package --output-template-file package.yml --s3-bucket 34 | sam deploy --template-file package.yml --stack-name multirobotstack --capabilities CAPABILITY_NAMED_IAM --s3-bucket 35 | ``` 36 | 37 | ## Running 38 | 39 | Once the lambda function is deployed, you can send the following event.json structure to launch simulations: 40 | 41 | Here is the example JSON: 42 | ``` 43 | { 44 | "robots": [ 45 | { 46 | "name": "robot1", 47 | "environmentVariables": { 48 | "START_X": "2", 49 | "START_Y": "2", 50 | "START_YAW": "3.143", 51 | "HUSKY_REALSENSE_ENABLED": "true", 52 | "HUSKY_LMS1XX_ENABLED": "true", 53 | "USE_CUSTOM_MOVE_OBJECT_GAZEBO_PLUGIN":"true" 54 | }, 55 | "packageName": "robot_fleet", 56 | "launchFile": "robot_fleet_rosbridge.launch" 57 | } 58 | ... 59 | ], 60 | "server": { 61 | "name": "SERVER", 62 | "environmentVariables": { 63 | "START_X": "0", 64 | "START_Y": "0", 65 | "START_YAW": "0", 66 | "HUSKY_REALSENSE_ENABLED": "true", 67 | "HUSKY_LMS1XX_ENABLED": "true", 68 | "USE_CUSTOM_MOVE_OBJECT_GAZEBO_PLUGIN":"true" 69 | }, 70 | "packageName": "robot_fleet", 71 | "launchFile": "robot_fleet_rosbridge.launch" 72 | } 73 | } 74 | ``` 75 | 76 | There are a group of optional attributes you can set if a ROSBridge server is already running or you want to customize the application details. 77 | ``` 78 | "serverIP": "", 79 | "simulationJobParams": { 80 | "vpcConfig": { 81 | "subnets": [], 82 | "securityGroups": [] 83 | }, 84 | "iamRole": "", 85 | "outputLocation": { 86 | "s3Bucket": "", 87 | "s3Key": "" 88 | } 89 | }, 90 | "simulationApplicationArn": "" 91 | ``` 92 | 93 | ## Tearing Down 94 | 95 | To remove the stack, simply log into AWS and open Cloudformation, find the main stack and press **Delete Stack**. 96 | 97 | 98 | ## Security 99 | 100 | See [CONTRIBUTING](../../CONTRIBUTING.md#security-issue-notifications) for more information. 101 | 102 | ## License 103 | 104 | This library is licensed under the MIT-0 License. See the LICENSE file. 105 | 106 | -------------------------------------------------------------------------------- /setup/fleetLauncherApp/base_template.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | 3 | Description: 4 | 5 | This base template deploys the backend stack for a RoboMaker application. 6 | 7 | Parameters: 8 | 9 | VpcCIDR: 10 | Description: Please enter the IP range (CIDR notation) for this VPC 11 | Type: String 12 | Default: 10.168.0.0/16 13 | 14 | PublicSubnet1CIDR: 15 | Description: Please enter the IP range (CIDR notation) for the public subnet in the first Availability Zone 16 | Type: String 17 | Default: 10.168.10.0/24 18 | 19 | PublicSubnet2CIDR: 20 | Description: Please enter the IP range (CIDR notation) for the public subnet in the second Availability Zone 21 | Type: String 22 | Default: 10.168.11.0/24 23 | 24 | SimulationApplicationRenderingEngine: 25 | Description: The rendering engine to use with the simulation application 26 | Type: String 27 | Default: OGRE 28 | 29 | SimulationApplicationRenderingEngineVersion: 30 | Description: The rendering engine to use with the simulation application version 31 | Type: String 32 | Default: 1.x 33 | 34 | SimulationApplicationROSSoftwareSuite: 35 | Description: Software suite for the simulation application 36 | Type: String 37 | Default: ROS 38 | 39 | SimulationApplicationSimulationSoftwareSuite: 40 | Description: Simulaton software suite for the simulation application 41 | Type: String 42 | Default: Gazebo 43 | 44 | SimulationApplicationSimulationSoftwareSuiteVersion: 45 | Description: Simulaton software suite version for the simulation application 46 | Type: String 47 | Default: "9" 48 | 49 | SimulationApplicationROSRelease: 50 | Description: Software suite for the simulation application 51 | Type: String 52 | Default: Melodic 53 | 54 | SimulationApplicationS3Key: 55 | Description: Location of bundle within S3. 56 | Type: String 57 | Default: multirobotdemo/bundle/output.tar 58 | 59 | Resources: 60 | 61 | VPC: 62 | Type: AWS::EC2::VPC 63 | Properties: 64 | CidrBlock: !Ref VpcCIDR 65 | Tags: 66 | - Key: Name 67 | Value: !Ref AWS::StackName 68 | 69 | SimulationApplication: 70 | Type: AWS::RoboMaker::SimulationApplication 71 | Properties: 72 | RenderingEngine: 73 | Name: !Ref SimulationApplicationRenderingEngine 74 | Version: !Ref SimulationApplicationRenderingEngineVersion 75 | RobotSoftwareSuite: 76 | Name: !Ref SimulationApplicationROSSoftwareSuite 77 | Version: !Ref SimulationApplicationROSRelease 78 | SimulationSoftwareSuite: 79 | Name: !Ref SimulationApplicationSimulationSoftwareSuite 80 | Version: !Ref SimulationApplicationSimulationSoftwareSuiteVersion 81 | Sources: 82 | - Architecture: X86_64 83 | S3Bucket: !Ref RoboMakerBasicS3Bucket 84 | S3Key: !Ref SimulationApplicationS3Key 85 | Tags: 86 | "Name" : "LaunchSource" 87 | "Type" : "MultiRobotServerlessLaunchApp" 88 | InternetGateway: 89 | Type: AWS::EC2::InternetGateway 90 | Properties: 91 | Tags: 92 | - Key: Name 93 | Value: !Ref AWS::StackName 94 | 95 | InternetGatewayAttachment: 96 | Type: AWS::EC2::VPCGatewayAttachment 97 | Properties: 98 | InternetGatewayId: !Ref InternetGateway 99 | VpcId: !Ref VPC 100 | 101 | RoboMakerBasicS3Bucket: 102 | Type: AWS::S3::Bucket 103 | Properties: 104 | VersioningConfiguration: 105 | Status: Enabled 106 | BucketEncryption: 107 | ServerSideEncryptionConfiguration: 108 | - ServerSideEncryptionByDefault: 109 | SSEAlgorithm: "AES256" 110 | BucketKeyEnabled: true 111 | 112 | PublicSubnet1: 113 | Type: AWS::EC2::Subnet 114 | Properties: 115 | VpcId: !Ref VPC 116 | AvailabilityZone: !Select [ 0, !GetAZs '' ] 117 | CidrBlock: !Ref PublicSubnet1CIDR 118 | MapPublicIpOnLaunch: true 119 | Tags: 120 | - Key: Name 121 | Value: !Sub ${AWS::StackName} ${AWS::Region} Public Subnet (AZ1) 122 | 123 | PublicSubnet2: 124 | Type: AWS::EC2::Subnet 125 | Properties: 126 | VpcId: !Ref VPC 127 | AvailabilityZone: !Select [ 1, !GetAZs '' ] 128 | CidrBlock: !Ref PublicSubnet2CIDR 129 | MapPublicIpOnLaunch: true 130 | Tags: 131 | - Key: Name 132 | Value: !Sub ${AWS::StackName} ${AWS::Region} Public Subnet (AZ2) 133 | 134 | PublicRouteTable: 135 | Type: AWS::EC2::RouteTable 136 | Properties: 137 | VpcId: !Ref VPC 138 | Tags: 139 | - Key: Name 140 | Value: !Sub ${AWS::StackName} ${AWS::Region} Public Routes 141 | 142 | DefaultPublicRoute: 143 | Type: AWS::EC2::Route 144 | DependsOn: InternetGatewayAttachment 145 | Properties: 146 | RouteTableId: !Ref PublicRouteTable 147 | DestinationCidrBlock: 0.0.0.0/0 148 | GatewayId: !Ref InternetGateway 149 | 150 | PublicSubnet1RouteTableAssociation: 151 | Type: AWS::EC2::SubnetRouteTableAssociation 152 | Properties: 153 | RouteTableId: !Ref PublicRouteTable 154 | SubnetId: !Ref PublicSubnet1 155 | 156 | PublicSubnet2RouteTableAssociation: 157 | Type: AWS::EC2::SubnetRouteTableAssociation 158 | Properties: 159 | RouteTableId: !Ref PublicRouteTable 160 | SubnetId: !Ref PublicSubnet2 161 | 162 | RoboMakerSimulationRole: 163 | Type: 'AWS::IAM::Role' 164 | Properties: 165 | AssumeRolePolicyDocument: 166 | Version: 2012-10-17 167 | Statement: 168 | - 169 | Effect: Allow 170 | Principal: 171 | Service: 172 | - robomaker.amazonaws.com 173 | - lambda.amazonaws.com 174 | Action: 175 | - sts:AssumeRole 176 | 177 | Policies: 178 | - 179 | PolicyName: !Sub robomaker-multi-robot-fleet-simulation-inline-policy-${AWS::Region} 180 | PolicyDocument: 181 | Version: 2012-10-17 182 | Statement: 183 | - 184 | Effect: Allow 185 | Resource: 186 | - !Join ['',['arn:aws:s3:::',!Ref RoboMakerBasicS3Bucket]] 187 | - !Join ['',['arn:aws:s3:::',!Ref RoboMakerBasicS3Bucket,'/*' ]] 188 | Action: 189 | - s3:List* 190 | - s3:Get* 191 | - s3:Put* 192 | - s3:DeleteObject 193 | - 194 | Effect: Allow 195 | Resource: 196 | - !Join [':',['arn:aws:logs', !Ref "AWS::Region", !Ref "AWS::AccountId", 'log-group:/aws/robomaker/SimulationJobs*']] 197 | Action: 198 | - logs:CreateLogGroup 199 | - logs:CreateLogStream 200 | - logs:PutLogEvents 201 | - logs:DescribeLogStreams 202 | - 203 | Effect: Allow 204 | Resource: 205 | - !Join ['', ['arn:aws:robomaker:::simulation-job*']] 206 | Action: 207 | - robomaker:UntagResource 208 | - robomaker:ListTagsForResource 209 | - robomaker:CancelSimulationJob 210 | - robomaker:TagResource 211 | Outputs: 212 | 213 | VPC: 214 | Description: A reference to the created VPC 215 | Value: !Ref VPC 216 | 217 | DefaultSecurityGroupID: 218 | Description: The default security group created with the VPC 219 | Value: !GetAtt VPC.DefaultSecurityGroup 220 | 221 | PublicSubnet1: 222 | Description: A reference to the public subnet in the 1st Availability Zone 223 | Value: !Ref PublicSubnet1 224 | 225 | PublicSubnet2: 226 | Description: A reference to the public subnet in the 2nd Availability Zone 227 | Value: !Ref PublicSubnet2 228 | 229 | RoboMakerS3Bucket: 230 | Description: The S3 bucket used to store your AWS RoboMaker assets. 231 | Value: !Ref RoboMakerBasicS3Bucket 232 | 233 | SimulationRole: 234 | Description: The IAM role that the simulation application will use to access AWS resources. 235 | Value: !GetAtt RoboMakerSimulationRole.Arn 236 | 237 | SimulationApplicationARN: 238 | Description: The IAM role that the simulation application will use to access AWS resources. 239 | Value: !GetAtt SimulationApplication.Arn 240 | -------------------------------------------------------------------------------- /setup/fleetLauncherApp/fleetLauncherLambda/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/multi-robot-fleet-sample-application/753f3ff4bf217297b35867aa0a536cd2391a7d21/setup/fleetLauncherApp/fleetLauncherLambda/__init__.py -------------------------------------------------------------------------------- /setup/fleetLauncherApp/fleetLauncherLambda/app.py: -------------------------------------------------------------------------------- 1 | import json 2 | import botocore 3 | import boto3 4 | from botocore.exceptions import WaiterError 5 | from botocore.waiter import WaiterModel 6 | from botocore.waiter import create_waiter_with_client 7 | import datetime 8 | import os 9 | import sys 10 | from copy import deepcopy 11 | 12 | DEFAULT_ROSBRIDGE_SERVER_PORT = 9090 13 | DEFAULT_IS_PUBLIC = False 14 | DEFAULT_MAX_DURATION = 3600 15 | DEFAULT_STREAM_UI = True 16 | 17 | SIM_SERVER_PORT_MAPPINGS = [ 18 | { 19 | "applicationPort": DEFAULT_ROSBRIDGE_SERVER_PORT, 20 | "enableOnPublicIp": DEFAULT_IS_PUBLIC, 21 | "jobPort": DEFAULT_ROSBRIDGE_SERVER_PORT 22 | } 23 | ] 24 | 25 | sim_job_params = { 26 | "maxJobDurationInSeconds": (DEFAULT_MAX_DURATION if "MAX_JOB_DURATION" not in os.environ else os.getenv("MAX_JOB_DURATION")), 27 | "iamRole": os.getenv("IAM_ROLE"), 28 | "failureBehavior": "Fail", 29 | "simulationApplications": [], 30 | "vpcConfig": { 31 | "securityGroups": [ os.getenv('SECURITY_GROUP') ] if "SECURITY_GROUP" in os.environ else [], 32 | "subnets": [ os.getenv('SUBNET_1'), os.getenv('SUBNET_2') ] if ("SUBNET_1" in os.environ and "SUBNET_2" in os.environ) else [] 33 | }, 34 | "outputLocation": { 35 | "s3Bucket": os.getenv("S3_BUCKET"), 36 | "s3Prefix": "logs" 37 | }, 38 | "loggingConfig": { 39 | "recordAllRosTopics": True 40 | } 41 | } 42 | 43 | app_arn = os.getenv('SIM_APPLICATION_ARN') 44 | 45 | waiter_config = { 46 | 'version': 2, 47 | 'waiters': { 48 | 'SimJobCreated': { 49 | 'operation': 'DescribeSimulationJob', 50 | 'delay': 5, 51 | 'maxAttempts': 70, 52 | 'acceptors': [ 53 | { 'matcher': 'path', 'expected': 'Pending', 'argument': 'status', 'state': 'retry' }, 54 | { 'matcher': 'path', 'expected': 'Running', 'argument': 'status', 'state': 'success' }, 55 | { 'matcher': 'path', 'expected': 'Terminated', 'argument': 'status', 'state': 'failure' }, 56 | { 'matcher': 'path', 'expected': 'Completed', 'argument': 'status', 'state': 'failure' }, 57 | { 'matcher': 'path', 'expected': 'Failed', 'argument': 'status', 'state': 'failure' } 58 | ] 59 | } 60 | } 61 | } 62 | 63 | robomaker = boto3.client('robomaker') 64 | 65 | def create_application_config(input_params, is_server, server_ip): 66 | 67 | if (is_server): 68 | port_mappings = SIM_SERVER_PORT_MAPPINGS 69 | else: 70 | port_mappings = [] 71 | 72 | to_set_params = { 73 | "application": app_arn, 74 | "applicationVersion": "$LATEST", 75 | "launchConfig": { 76 | "environmentVariables": input_params['environmentVariables'], 77 | "launchFile": input_params['launchFile'], 78 | "packageName": input_params['packageName'], 79 | "portForwardingConfig" : { 'portMappings': port_mappings }, 80 | "streamUI": DEFAULT_STREAM_UI 81 | } 82 | } 83 | 84 | to_set_params['launchConfig']['environmentVariables']['ROBOT_NAME'] = input_params['name'] 85 | 86 | if (is_server): 87 | to_set_params['launchConfig']['environmentVariables']['ROSBRIDGE_STATE'] = "SERVER" 88 | to_set_params['launchConfig']['environmentVariables']['ROSBRIDGE_IP'] = "localhost" 89 | elif (server_ip): 90 | to_set_params['launchConfig']['environmentVariables']['ROSBRIDGE_STATE'] = "CLIENT" 91 | to_set_params['launchConfig']['environmentVariables']['ROSBRIDGE_IP'] = server_ip 92 | 93 | return to_set_params 94 | 95 | def lambda_handler(event, context): 96 | 97 | # Set base parameters for each simulation job. Order from Lambda event, then environment variables. 98 | 99 | if 'simulationJobParams' in event: 100 | 101 | if 'vpcConfig' in event['simulationJobParams']: 102 | sim_job_params['vpcConfig'] = sim_job_params['vpcConfig'] 103 | 104 | if 'iamRole' in event['simulationJobParams']: 105 | sim_job_params['iamRole'] = sim_job_params['iamRole'] 106 | 107 | if 'outputLocation' in event['simulationJobParams']: 108 | sim_job_params['outputLocation'] = sim_job_params['outputLocation'] 109 | 110 | if 'simulationApplicationArn' in event: 111 | app_arn = event['simulationApplicationArn'] 112 | 113 | if 'serverIP' in event: 114 | private_ip = event['serverIP'] 115 | else: 116 | # Launch Server. 117 | server_app_params = create_application_config(event['server'], True, 'localhost') 118 | server_job_params = deepcopy(sim_job_params) 119 | server_job_params['simulationApplications'].append(server_app_params) 120 | server_job_response = robomaker.create_simulation_job( 121 | iamRole=server_job_params["iamRole"], 122 | maxJobDurationInSeconds=server_job_params["maxJobDurationInSeconds"], 123 | simulationApplications=[server_app_params], 124 | vpcConfig=server_job_params["vpcConfig"], 125 | loggingConfig=server_job_params["loggingConfig"], 126 | outputLocation=server_job_params["outputLocation"] 127 | ) 128 | 129 | # Wait for server to be available. 130 | waiter_name = 'SimJobCreated' 131 | waiter_model = WaiterModel(waiter_config) 132 | custom_waiter = create_waiter_with_client(waiter_name, waiter_model, robomaker) 133 | custom_waiter.wait(job=server_job_response['arn']) 134 | desc_result = robomaker.describe_simulation_job( job = server_job_response['arn'] ) 135 | private_ip = desc_result['networkInterface']['privateIpAddress'] 136 | 137 | # Launch multiple robot batches. 138 | batch_job_requests = [] 139 | client_app_params = {} 140 | client_job_params = {} 141 | for robot in event['robots']: 142 | client_app_params[robot['name']] = create_application_config(robot, False, private_ip) 143 | client_job_params[robot['name']] = deepcopy(sim_job_params) 144 | client_job_params[robot['name']]['simulationApplications'].append(client_app_params[robot['name']]) 145 | batch_job_requests.append(client_job_params[robot['name']]) 146 | 147 | response = robomaker.start_simulation_job_batch( 148 | batchPolicy={ 149 | 'timeoutInSeconds': DEFAULT_MAX_DURATION, 150 | 'maxConcurrency': len(event['robots']) 151 | }, 152 | createSimulationJobRequests=batch_job_requests, 153 | tags = { 154 | 'launcher': 'multi_robot_fleet' 155 | }) 156 | 157 | return { 158 | 'statusCode': 200, 159 | 'body': response['arn'] 160 | } 161 | 162 | # To run locally on your machine based on CFN stack outputs. 163 | if __name__ == "__main__": 164 | 165 | # Get Sample Event 166 | event_path = "%s/event.json" % sys.path[0] 167 | 168 | with open(event_path) as f: 169 | event = json.load(f) 170 | 171 | if (len(sys.argv)==2): 172 | cfn = boto3.client('cloudformation') 173 | response = cfn.describe_stacks( 174 | StackName=sys.argv[1] 175 | ) 176 | if len(response['Stacks'])>0: 177 | if len(response['Stacks'][0]['Outputs'])>0: 178 | for output in response['Stacks'][0]['Outputs']: 179 | if output['OutputKey'] == 'PublicSubnet1' or output['OutputKey'] == 'PublicSubnet2': 180 | sim_job_params['vpcConfig']['subnets'].append(output['OutputValue']) 181 | elif output['OutputKey'] == 'DefaultSecurityGroupID': 182 | sim_job_params['vpcConfig']['securityGroups'].append(output['OutputValue']) 183 | elif output['OutputKey'] == 'SimulationRole': 184 | sim_job_params['iamRole'] = output['OutputValue'] 185 | elif output['OutputKey'] == 'RoboMakerS3Bucket': 186 | sim_job_params['outputLocation']['s3Bucket'] = output['OutputValue'] 187 | elif output['OutputKey'] == 'SimulationApplicationARN': 188 | app_arn = output['OutputValue'] 189 | else: 190 | print("Cloudformation stack did not any outputs. Using event.json values or environment variables for AWS infrastructure configuration.") 191 | else: 192 | print("Cloudformation stack not found. Using event.json values or environment variables for AWS infrastructure configuration.") 193 | else: 194 | print("No cloudformation defined. Using event.json values or environment variables for AWS infrastructure configuration.") 195 | 196 | print("Starting handler") 197 | lambda_handler(event, {}) 198 | print("Simulations launched. Check out the AWS console to connect to the fleet simulation.") 199 | -------------------------------------------------------------------------------- /setup/fleetLauncherApp/fleetLauncherLambda/event.json: -------------------------------------------------------------------------------- 1 | { 2 | "robots": [ 3 | { 4 | "name": "robot1", 5 | "environmentVariables": { 6 | "START_X": "2", 7 | "START_Y": "2", 8 | "START_YAW": "3.143", 9 | "HUSKY_REALSENSE_ENABLED": "true", 10 | "HUSKY_LMS1XX_ENABLED": "true", 11 | "USE_CUSTOM_MOVE_OBJECT_GAZEBO_PLUGIN":"true" 12 | }, 13 | "packageName": "robot_fleet", 14 | "launchFile": "robot_fleet_rosbridge.launch" 15 | }, 16 | { 17 | "name": "robot2", 18 | "environmentVariables": { 19 | "START_X": "-3", 20 | "START_Y": "-1", 21 | "START_YAW": "0", 22 | "HUSKY_REALSENSE_ENABLED": "true", 23 | "HUSKY_LMS1XX_ENABLED": "true", 24 | "USE_CUSTOM_MOVE_OBJECT_GAZEBO_PLUGIN":"true" 25 | }, 26 | "packageName": "robot_fleet", 27 | "launchFile": "robot_fleet_rosbridge.launch" 28 | }, 29 | { 30 | "name": "robot3", 31 | "environmentVariables": { 32 | "START_X": "-3", 33 | "START_Y": "-3", 34 | "START_YAW": "0", 35 | "HUSKY_REALSENSE_ENABLED": "true", 36 | "HUSKY_LMS1XX_ENABLED": "true", 37 | "USE_CUSTOM_MOVE_OBJECT_GAZEBO_PLUGIN":"true" 38 | }, 39 | "packageName": "robot_fleet", 40 | "launchFile": "robot_fleet_rosbridge.launch" 41 | } 42 | ], 43 | "server": { 44 | "name": "SERVER", 45 | "environmentVariables": { 46 | "START_X": "0", 47 | "START_Y": "0", 48 | "START_YAW": "0", 49 | "HUSKY_REALSENSE_ENABLED": "true", 50 | "HUSKY_LMS1XX_ENABLED": "true", 51 | "USE_CUSTOM_MOVE_OBJECT_GAZEBO_PLUGIN":"true" 52 | }, 53 | "packageName": "robot_fleet", 54 | "launchFile": "robot_fleet_rosbridge.launch" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /setup/fleetLauncherApp/requirements.txt: -------------------------------------------------------------------------------- 1 | awscli>=1.18.105 2 | boto3>=1.14.28 -------------------------------------------------------------------------------- /setup/fleetLauncherApp/template.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Transform: AWS::Serverless-2016-10-31 3 | 4 | Description: 5 | 6 | This template deploys the SAM-based Lambda function to run multiple robots across a batch of RoboMaker simulation jobs, the ROS application consolidates the fleet simulation to test complex robot interactions. 7 | 8 | Resources: 9 | 10 | BaseStack: 11 | Type: AWS::Serverless::Application 12 | Properties: 13 | Location: base_template.yml 14 | TimeoutInMinutes: 10 15 | 16 | LaunchSimulations: 17 | Type: AWS::Serverless::Function 18 | Properties: 19 | CodeUri: fleetLauncherLambda/ 20 | Handler: app.lambda_handler 21 | Runtime: python3.8 22 | Role: !GetAtt LambdaRole.Arn 23 | Tracing: Active 24 | Environment: 25 | Variables: 26 | S3_BUCKET: !GetAtt BaseStack.Outputs.RoboMakerS3Bucket 27 | SIMULATION_APP_ARN: !GetAtt BaseStack.Outputs.SimulationApplicationARN 28 | IAM_ROLE: !GetAtt BaseStack.Outputs.SimulationRole 29 | SUBNET_1: !GetAtt BaseStack.Outputs.PublicSubnet1 30 | SUBNET_2: !GetAtt BaseStack.Outputs.PublicSubnet2 31 | SECURITY_GROUP: !GetAtt BaseStack.Outputs.DefaultSecurityGroupID 32 | 33 | LambdaRole: 34 | Type: 'AWS::IAM::Role' 35 | Properties: 36 | RoleName: !Sub robomaker-multi-robot-fleet-simulation-lambda-role-${AWS::Region} 37 | AssumeRolePolicyDocument: 38 | Version: 2012-10-17 39 | Statement: 40 | - 41 | Effect: Allow 42 | Principal: 43 | Service: 44 | - lambda.amazonaws.com 45 | Action: 46 | - sts:AssumeRole 47 | 48 | Policies: 49 | - 50 | PolicyName: !Sub robomaker-multi-robot-fleet-simulation-lambda-inline-policy-${AWS::Region} 51 | PolicyDocument: 52 | Version: 2012-10-17 53 | Statement: 54 | - 55 | Effect: Allow 56 | Resource: 57 | - !Join ['',['arn:aws:s3:::',!GetAtt BaseStack.Outputs.RoboMakerS3Bucket]] 58 | - !Join ['',['arn:aws:s3:::',!GetAtt BaseStack.Outputs.RoboMakerS3Bucket,'/*' ]] 59 | Action: 60 | - s3:List* 61 | - s3:Get* 62 | - 63 | Effect: Allow 64 | Resource: '*' 65 | Action: 66 | - robomaker:CreateSimulationJob 67 | - robomaker:StartSimulationJobBatch 68 | - 69 | Effect: Allow 70 | Resource: !GetAtt BaseStack.Outputs.SimulationRole 71 | Action: 72 | - iam:passRole 73 | 74 | ManagedPolicyArns: 75 | - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole' 76 | - 'arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess' -------------------------------------------------------------------------------- /setup/requirements.txt: -------------------------------------------------------------------------------- 1 | awscli>=1.18.105 2 | boto3>=1.14.28 -------------------------------------------------------------------------------- /setup/ros_setup.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "###############################################################################" 4 | echo "ROS environment setup starting.." 5 | echo "###############################################################################" 6 | 7 | BASE_DIR=`pwd` 8 | APP_DIR=$BASE_DIR/simulation_ws 9 | 10 | # Wait if apt is running. 11 | while : 12 | do 13 | count=`ps -ef | grep apt.systemd.daily | grep lock_is_held | grep -v grep | wc -l` 14 | if [ $count = 0 ]; then 15 | break 16 | else 17 | echo "System update is running.. Wait until the completion" 18 | sleep 10 19 | fi 20 | done 21 | 22 | sudo apt-get update 23 | source /opt/ros/$ROS_DISTRO/setup.sh 24 | 25 | #download 3rd party repositories 26 | cd $APP_DIR 27 | rosws update 28 | 29 | #Update source list 30 | cd $BASE_DIR 31 | # Setup custom rosdep dependencies 32 | CUSTOM_DEP_SOURCE_LIST_LOCATION=/etc/ros/rosdep/sources.list.d/21-customdependencies.list 33 | CUSTOM_DEP_FILE=$BASE_DIR/setup/custom_dependencies.yaml 34 | 35 | if [ -f "$CUSTOM_DEP_SOURCE_LIST_LOCATION" ]; then 36 | echo "rosdep file already exists. Skipping" 37 | else 38 | sudo touch $CUSTOM_DEP_SOURCE_LIST_LOCATION 39 | if grep -Fxq "yaml file://$CUSTOM_DEP_FILE" $CUSTOM_DEP_SOURCE_LIST_LOCATION 40 | then 41 | echo "dependency file already setup" 42 | else 43 | echo "source list not setup" 44 | echo "yaml file://$CUSTOM_DEP_FILE" | sudo tee -a $CUSTOM_DEP_SOURCE_LIST_LOCATION 45 | fi 46 | fi 47 | 48 | cd $APP_DIR 49 | rosdep update 50 | rosdep install --from-paths src --ignore-src -r -y 51 | 52 | sudo apt-get install python3-apt python3-pip -y 53 | sudo pip3 install -U setuptools pip 54 | sudo pip3 install colcon-ros-bundle 55 | colcon build 56 | source install/setup.bash 57 | 58 | echo -e "We can use roslaunch here. Press any key to colcon bundle, or Ctrl+c to exit" 59 | echo -e "Times out in 15 seconds" 60 | read -t 15 -p "Press any key to colcon bundle " some_key 61 | 62 | colcon bundle 63 | -------------------------------------------------------------------------------- /simulation_ws/.rosinstall: -------------------------------------------------------------------------------- 1 | - git: 2 | uri: https://github.com/aws-robotics/aws-robomaker-small-warehouse-world.git 3 | local-name: src/deps/aws-robomaker-small-warehouse-world 4 | version: 55775a5 5 | - git: 6 | uri: https://github.com/husky/husky.git 7 | local-name: src/deps/husky 8 | version: 5395962 9 | - git: 10 | uri: https://github.com/RobotWebTools/rosbridge_suite.git 11 | local-name: src/deps/rosbridge_suite 12 | version: 0.11.2 13 | -------------------------------------------------------------------------------- /simulation_ws/src/robot_fleet/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.3) 2 | project(robot_fleet) 3 | 4 | cmake_policy(SET CMP0054 NEW) 5 | 6 | # c++11 if using kinetic 7 | add_definitions(-std=c++11) 8 | 9 | find_package(catkin REQUIRED COMPONENTS 10 | geometry_msgs 11 | roscpp 12 | rospy 13 | std_msgs 14 | gazebo_ros 15 | ) 16 | 17 | find_package(gazebo REQUIRED) 18 | 19 | link_directories(${GAZEBO_LIBRARY_DIRS}) 20 | include_directories(${Boost_INCLUDE_DIR} ${catkin_INCLUDE_DIRS} ${GAZEBO_INCLUDE_DIRS}) 21 | 22 | add_library(move_object_plugin src/move_object.cc) 23 | target_link_libraries(move_object_plugin ${catkin_LIBRARIES} ${GAZEBO_LIBRARIES}) 24 | 25 | catkin_package( 26 | DEPENDS 27 | roscpp 28 | gazebo_ros 29 | ) 30 | 31 | catkin_install_python(PROGRAMS 32 | scripts/client_rosbridge.py 33 | scripts/gazebo_model_mover.py 34 | DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}) 35 | 36 | install(DIRECTORY launch static_models_w_plugin rviz config 37 | DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}) 38 | 39 | install(TARGETS move_object_plugin 40 | LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} 41 | ) 42 | -------------------------------------------------------------------------------- /simulation_ws/src/robot_fleet/config/costmap_common.yaml: -------------------------------------------------------------------------------- 1 | footprint: [[-0.5, -0.33], [-0.5, 0.33], [0.5, 0.33], [0.5, -0.33]] 2 | footprint_padding: 0.01 3 | 4 | robot_base_frame: base_link 5 | update_frequency: 4.0 6 | publish_frequency: 3.0 7 | transform_tolerance: 0.5 8 | 9 | resolution: 0.05 10 | 11 | obstacle_range: 5.5 12 | raytrace_range: 6.0 13 | 14 | #layer definitions 15 | static: 16 | map_topic: /map 17 | subscribe_to_updates: true 18 | 19 | obstacles_laser: 20 | observation_sources: laser 21 | laser: {sensor_frame: base_laser, data_type: LaserScan, clearing: true, marking: true, topic: scan, inf_is_valid: true} 22 | 23 | obstacles_3d: 24 | observation_sources: point_cloud_sensor 25 | point_cloud_sensor: {sensor_frame: realsense_mountpoint, data_type: PointCloud2, topic: realsense/depth/color/points, marking: true, clearing: true} 26 | 27 | inflation: 28 | inflation_radius: 1.0 29 | -------------------------------------------------------------------------------- /simulation_ws/src/robot_fleet/config/costmap_local.yaml: -------------------------------------------------------------------------------- 1 | global_frame: odom 2 | rolling_window: true 3 | 4 | plugins: 5 | - {name: obstacles_laser, type: "costmap_2d::ObstacleLayer"} 6 | - {name: obstacles_3d, type: "costmap_2d::VoxelLayer"} 7 | - {name: inflation, type: "costmap_2d::InflationLayer"} 8 | -------------------------------------------------------------------------------- /simulation_ws/src/robot_fleet/launch/husky_app_only.launch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /simulation_ws/src/robot_fleet/launch/husky_app_with_rtsp.launch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /simulation_ws/src/robot_fleet/launch/include/amcl.launch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /simulation_ws/src/robot_fleet/launch/include/move_base.launch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /simulation_ws/src/robot_fleet/launch/robot_fleet_rosbridge.launch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /simulation_ws/src/robot_fleet/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | robot_fleet 4 | 1.0.0 5 | The robot_fleet package 6 | 7 | AWS RoboMaker 8 | AWS RoboMaker 9 | 10 | MIT 11 | 12 | catkin 13 | geometry_msgs 14 | roscpp 15 | rospy 16 | std_msgs 17 | gazebo_ros 18 | geometry_msgs 19 | roscpp 20 | rospy 21 | std_msgs 22 | gazebo_ros 23 | geometry_msgs 24 | roscpp 25 | rviz 26 | rospy 27 | std_msgs 28 | gazebo_ros 29 | roslibpy-pip 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /simulation_ws/src/robot_fleet/rviz/basic_data.rviz: -------------------------------------------------------------------------------- 1 | Panels: 2 | - Class: rviz/Displays 3 | Help Height: 0 4 | Name: Displays 5 | Property Tree Widget: 6 | Expanded: ~ 7 | Splitter Ratio: 0.5 8 | Tree Height: 224 9 | - Class: rviz/Selection 10 | Name: Selection 11 | - Class: rviz/Tool Properties 12 | Expanded: 13 | - /2D Pose Estimate1 14 | - /2D Nav Goal1 15 | - /Publish Point1 16 | Name: Tool Properties 17 | Splitter Ratio: 0.5886790156364441 18 | - Class: rviz/Views 19 | Expanded: 20 | - /Current View1 21 | Name: Views 22 | Splitter Ratio: 0.5 23 | - Class: rviz/Time 24 | Experimental: false 25 | Name: Time 26 | SyncMode: 0 27 | SyncSource: LaserScan 28 | Preferences: 29 | PromptSaveOnExit: true 30 | Toolbars: 31 | toolButtonStyle: 2 32 | Visualization Manager: 33 | Class: "" 34 | Displays: 35 | - Alpha: 0.5 36 | Cell Size: 1 37 | Class: rviz/Grid 38 | Color: 160; 160; 164 39 | Enabled: true 40 | Line Style: 41 | Line Width: 0.029999999329447746 42 | Value: Lines 43 | Name: Grid 44 | Normal Cell Count: 0 45 | Offset: 46 | X: 0 47 | Y: 0 48 | Z: 0 49 | Plane: XY 50 | Plane Cell Count: 10 51 | Reference Frame: 52 | Value: true 53 | - Alpha: 1 54 | Axes Length: 1 55 | Axes Radius: 0.10000000149011612 56 | Class: rviz/PoseWithCovariance 57 | Color: 255; 25; 0 58 | Covariance: 59 | Orientation: 60 | Alpha: 0.5 61 | Color: 255; 255; 127 62 | Color Style: Unique 63 | Frame: Local 64 | Offset: 1 65 | Scale: 1 66 | Value: true 67 | Position: 68 | Alpha: 0.30000001192092896 69 | Color: 204; 51; 204 70 | Scale: 1 71 | Value: true 72 | Value: true 73 | Enabled: true 74 | Head Length: 0.30000001192092896 75 | Head Radius: 0.10000000149011612 76 | Name: PoseWithCovariance 77 | Shaft Length: 1 78 | Shaft Radius: 0.05000000074505806 79 | Shape: Arrow 80 | Topic: /amcl_pose 81 | Unreliable: false 82 | Value: true 83 | - Alpha: 0.699999988079071 84 | Class: rviz/Map 85 | Color Scheme: map 86 | Draw Behind: false 87 | Enabled: true 88 | Name: Map 89 | Topic: /map 90 | Unreliable: false 91 | Use Timestamp: false 92 | Value: true 93 | - Alpha: 0.30000001192092896 94 | Class: rviz/Map 95 | Color Scheme: costmap 96 | Draw Behind: false 97 | Enabled: true 98 | Name: Map 99 | Topic: /move_base/global_costmap/costmap 100 | Unreliable: false 101 | Use Timestamp: false 102 | Value: true 103 | - Alpha: 0.699999988079071 104 | Class: rviz/Map 105 | Color Scheme: costmap 106 | Draw Behind: false 107 | Enabled: true 108 | Name: Map 109 | Topic: /move_base/local_costmap/costmap 110 | Unreliable: false 111 | Use Timestamp: false 112 | Value: true 113 | - Alpha: 1 114 | Buffer Length: 1 115 | Class: rviz/Path 116 | Color: 25; 255; 0 117 | Enabled: true 118 | Head Diameter: 0.30000001192092896 119 | Head Length: 0.20000000298023224 120 | Length: 0.30000001192092896 121 | Line Style: Lines 122 | Line Width: 0.029999999329447746 123 | Name: Path 124 | Offset: 125 | X: 0 126 | Y: 0 127 | Z: 0 128 | Pose Color: 255; 85; 255 129 | Pose Style: None 130 | Radius: 0.029999999329447746 131 | Shaft Diameter: 0.10000000149011612 132 | Shaft Length: 0.10000000149011612 133 | Topic: /move_base/DWAPlannerROS/global_plan 134 | Unreliable: false 135 | Value: true 136 | - Alpha: 1 137 | Buffer Length: 1 138 | Class: rviz/Path 139 | Color: 25; 255; 0 140 | Enabled: true 141 | Head Diameter: 0.30000001192092896 142 | Head Length: 0.20000000298023224 143 | Length: 0.30000001192092896 144 | Line Style: Lines 145 | Line Width: 0.029999999329447746 146 | Name: Path 147 | Offset: 148 | X: 0 149 | Y: 0 150 | Z: 0 151 | Pose Color: 255; 85; 255 152 | Pose Style: None 153 | Radius: 0.029999999329447746 154 | Shaft Diameter: 0.10000000149011612 155 | Shaft Length: 0.10000000149011612 156 | Topic: /move_base/DWAPlannerROS/local_plan 157 | Unreliable: false 158 | Value: true 159 | - Alpha: 1 160 | Autocompute Intensity Bounds: true 161 | Autocompute Value Bounds: 162 | Max Value: 10 163 | Min Value: -10 164 | Value: true 165 | Axis: Z 166 | Channel Name: intensity 167 | Class: rviz/LaserScan 168 | Color: 239; 41; 41 169 | Color Transformer: FlatColor 170 | Decay Time: 0 171 | Enabled: true 172 | Invert Rainbow: false 173 | Max Color: 255; 255; 255 174 | Max Intensity: 0 175 | Min Color: 0; 0; 0 176 | Min Intensity: 0 177 | Name: LaserScan 178 | Position Transformer: XYZ 179 | Queue Size: 10 180 | Selectable: true 181 | Size (Pixels): 3 182 | Size (m): 0.20000000298023224 183 | Style: Flat Squares 184 | Topic: /scan 185 | Unreliable: false 186 | Use Fixed Frame: true 187 | Use rainbow: true 188 | Value: true 189 | - Class: rviz/Image 190 | Enabled: true 191 | Image Topic: /realsense/color/image_raw 192 | Max Value: 1 193 | Median window: 5 194 | Min Value: 0 195 | Name: Image 196 | Normalize Range: true 197 | Queue Size: 2 198 | Transport Hint: raw 199 | Unreliable: false 200 | Value: true 201 | - Alpha: 1 202 | Class: rviz/RobotModel 203 | Collision Enabled: false 204 | Enabled: true 205 | Links: 206 | All Links Enabled: true 207 | Expand Joint Details: false 208 | Expand Link Details: false 209 | Expand Tree: false 210 | Link Tree Style: Links in Alphabetic Order 211 | base_footprint: 212 | Alpha: 1 213 | Show Axes: false 214 | Show Trail: false 215 | base_laser: 216 | Alpha: 1 217 | Show Axes: false 218 | Show Trail: false 219 | Value: true 220 | base_laser_mount: 221 | Alpha: 1 222 | Show Axes: false 223 | Show Trail: false 224 | Value: true 225 | base_link: 226 | Alpha: 1 227 | Show Axes: false 228 | Show Trail: false 229 | Value: true 230 | camera_realsense: 231 | Alpha: 1 232 | Show Axes: false 233 | Show Trail: false 234 | camera_realsense_gazebo: 235 | Alpha: 1 236 | Show Axes: false 237 | Show Trail: false 238 | camera_realsense_lens: 239 | Alpha: 1 240 | Show Axes: false 241 | Show Trail: false 242 | Value: true 243 | front_bumper_link: 244 | Alpha: 1 245 | Show Axes: false 246 | Show Trail: false 247 | Value: true 248 | front_left_wheel_link: 249 | Alpha: 1 250 | Show Axes: false 251 | Show Trail: false 252 | Value: true 253 | front_right_wheel_link: 254 | Alpha: 1 255 | Show Axes: false 256 | Show Trail: false 257 | Value: true 258 | imu_link: 259 | Alpha: 1 260 | Show Axes: false 261 | Show Trail: false 262 | inertial_link: 263 | Alpha: 1 264 | Show Axes: false 265 | Show Trail: false 266 | realsense_mountpoint: 267 | Alpha: 1 268 | Show Axes: false 269 | Show Trail: false 270 | rear_bumper_link: 271 | Alpha: 1 272 | Show Axes: false 273 | Show Trail: false 274 | Value: true 275 | rear_left_wheel_link: 276 | Alpha: 1 277 | Show Axes: false 278 | Show Trail: false 279 | Value: true 280 | rear_right_wheel_link: 281 | Alpha: 1 282 | Show Axes: false 283 | Show Trail: false 284 | Value: true 285 | sensor_arch_mount_link: 286 | Alpha: 1 287 | Show Axes: false 288 | Show Trail: false 289 | Value: true 290 | top_chassis_link: 291 | Alpha: 1 292 | Show Axes: false 293 | Show Trail: false 294 | Value: true 295 | top_plate_front_link: 296 | Alpha: 1 297 | Show Axes: false 298 | Show Trail: false 299 | top_plate_link: 300 | Alpha: 1 301 | Show Axes: false 302 | Show Trail: false 303 | Value: true 304 | top_plate_rear_link: 305 | Alpha: 1 306 | Show Axes: false 307 | Show Trail: false 308 | user_rail_link: 309 | Alpha: 1 310 | Show Axes: false 311 | Show Trail: false 312 | Value: true 313 | Name: RobotModel 314 | Robot Description: robot_description 315 | TF Prefix: "" 316 | Update Interval: 0 317 | Value: true 318 | Visual Enabled: true 319 | Enabled: true 320 | Global Options: 321 | Background Color: 48; 48; 48 322 | Default Light: true 323 | Fixed Frame: map 324 | Frame Rate: 30 325 | Name: root 326 | Tools: 327 | - Class: rviz/Interact 328 | Hide Inactive Objects: true 329 | - Class: rviz/MoveCamera 330 | - Class: rviz/Select 331 | - Class: rviz/FocusCamera 332 | - Class: rviz/Measure 333 | - Class: rviz/SetInitialPose 334 | Theta std deviation: 0.2617993950843811 335 | Topic: /initialpose 336 | X std deviation: 0.5 337 | Y std deviation: 0.5 338 | - Class: rviz/SetGoal 339 | Topic: /move_base_simple/goal 340 | - Class: rviz/PublishPoint 341 | Single click: true 342 | Topic: /clicked_point 343 | Value: true 344 | Views: 345 | Current: 346 | Class: rviz/Orbit 347 | Distance: 24.24601936340332 348 | Enable Stereo Rendering: 349 | Stereo Eye Separation: 0.05999999865889549 350 | Stereo Focal Distance: 1 351 | Swap Stereo Eyes: false 352 | Value: false 353 | Focal Point: 354 | X: 0 355 | Y: 0 356 | Z: 0 357 | Focal Shape Fixed Size: true 358 | Focal Shape Size: 0.05000000074505806 359 | Invert Z Axis: false 360 | Name: Current View 361 | Near Clip Distance: 0.009999999776482582 362 | Pitch: 1.5697963237762451 363 | Target Frame: 364 | Value: Orbit (rviz) 365 | Yaw: 6.268586158752441 366 | Saved: ~ 367 | Window Geometry: 368 | Displays: 369 | collapsed: false 370 | Height: 846 371 | Hide Left Dock: false 372 | Hide Right Dock: true 373 | Image: 374 | collapsed: false 375 | QMainWindow State: 000000ff00000000fd0000000400000000000001f0000002b0fc0200000009fb0000001200530065006c0065006300740069006f006e00000001e10000009b0000005c00fffffffb0000001e0054006f006f006c002000500072006f007000650072007400690065007302000001ed000001df00000185000000a3fb000000120056006900650077007300200054006f006f02000001df000002110000018500000122fb000000200054006f006f006c002000500072006f0070006500720074006900650073003203000002880000011d000002210000017afb000000100044006900730070006c006100790073010000003d0000011d000000c900fffffffb0000002000730065006c0065006300740069006f006e00200062007500660066006500720200000138000000aa0000023a00000294fb00000014005700690064006500530074006500720065006f02000000e6000000d2000003ee0000030bfb0000000c004b0069006e0065006300740200000186000001060000030c00000261fb0000000a0049006d00610067006501000001600000018d0000001600ffffff000000010000010f000002b0fc0200000003fb0000001e0054006f006f006c002000500072006f00700065007200740069006500730100000041000000780000000000000000fb0000000a00560069006500770073000000003d000002b0000000a400fffffffb0000001200530065006c0065006300740069006f006e010000025a000000b200000000000000000000000200000490000000a9fc0100000001fb0000000a00560069006500770073030000004e00000080000002e10000019700000003000004b00000003efc0100000002fb0000000800540069006d00650100000000000004b0000002eb00fffffffb0000000800540069006d00650100000000000004500000000000000000000002ba000002b000000004000000040000000800000008fc0000000100000002000000010000000a0054006f006f006c00730100000000ffffffff0000000000000000 376 | Selection: 377 | collapsed: false 378 | Time: 379 | collapsed: false 380 | Tool Properties: 381 | collapsed: false 382 | Views: 383 | collapsed: true 384 | Width: 1200 385 | X: 67 386 | Y: 27 387 | -------------------------------------------------------------------------------- /simulation_ws/src/robot_fleet/scripts/client_rosbridge.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | # SPDX-License-Identifier: MIT-0 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 7 | # software and associated documentation files (the "Software"), to deal in the Software 8 | # without restriction, including without limitation the rights to use, copy, modify, 9 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 10 | # permit persons to whom the Software is furnished to do so. 11 | # 12 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 13 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 14 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 15 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 16 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 17 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 18 | # 19 | 20 | """ 21 | This nodes job is to collect position info and sends it over /client_robot_data via rosbridge. 22 | To grab data of all other robots as a client, subscribe to /client_robot_data and remap 23 | it to /remapped_client_robot_data. If you are a server, you have all the data in 24 | /client_robot_data already 25 | """ 26 | 27 | import rospy 28 | import roslibpy 29 | import json 30 | from std_msgs.msg import String 31 | import tf 32 | from tf.transformations import euler_from_quaternion 33 | from gazebo_msgs.msg import ModelStates 34 | 35 | 36 | class SendData: 37 | def __init__(self): 38 | self.data_to_rosbridge = {} 39 | self.data_to_rosbridge['name'] = rospy.get_param('ROBOT_NAME') 40 | self.data_to_rosbridge['robot_sdf_file'] = rospy.get_param('ROBOT_SDF_FILE') 41 | self.data_to_rosbridge['navigation_pose'] = {} 42 | self.data_to_rosbridge['gazebo_pose'] = {} 43 | self.rosbridge_ip = rospy.get_param('ROSBRIDGE_IP') 44 | 45 | self.rosbridge_state = rospy.get_param('ROSBRIDGE_STATE') 46 | self.client = roslibpy.Ros(host=self.rosbridge_ip, port=9090) 47 | self.client.run() 48 | 49 | if self.rosbridge_state == 'CLIENT': 50 | self.remap_pub = rospy.Publisher('remapped_client_robot_data', String, queue_size=1) 51 | self.robot_data_listener = roslibpy.Topic(self.client, 'client_robot_data', 'std_msgs/String') 52 | self.robot_data_listener.subscribe(self.remap_subscriber) 53 | 54 | self.gazebo_model_state_sub = rospy.Subscriber('gazebo/model_states', ModelStates, self.model_states_callback, queue_size=1) 55 | self.current_model_state = None 56 | self.init_rosbridge_talkers() 57 | 58 | def model_states_callback(self, msg): 59 | self.current_model_state = msg 60 | 61 | def init_rosbridge_talkers(self): 62 | self.talker = roslibpy.Topic(self.client, 'client_robot_data', 'std_msgs/String') 63 | 64 | def remap_subscriber(self, msg): 65 | data = msg['data'] 66 | self.remap_pub.publish(data) 67 | 68 | def main(self): 69 | rate = rospy.Rate(10.0) 70 | listener = tf.TransformListener() 71 | 72 | while not rospy.is_shutdown(): 73 | try: 74 | (trans,rot) = listener.lookupTransform('map', '/base_link', rospy.Time(0)) 75 | nav_pose = {} 76 | nav_pose['x'] = trans[0] 77 | nav_pose['y'] = trans[1] 78 | nav_pose['z'] = trans[2] 79 | 80 | nav_pose['qx'] = rot[0] 81 | nav_pose['qy'] = rot[1] 82 | nav_pose['qz'] = rot[2] 83 | nav_pose['qw'] = rot[3] 84 | 85 | self.data_to_rosbridge['navigation_pose'] = nav_pose 86 | 87 | gazebo_pose = {} 88 | gazebo_model_index = self.current_model_state.name.index("/") # Looking for main robot which in under namespace "/" 89 | gz_pose = self.current_model_state.pose[gazebo_model_index] 90 | gazebo_pose['x'] = gz_pose.position.x 91 | gazebo_pose['y'] = gz_pose.position.y 92 | gazebo_pose['z'] = gz_pose.position.z 93 | 94 | gazebo_pose['qx'] = gz_pose.orientation.x 95 | gazebo_pose['qy'] = gz_pose.orientation.y 96 | gazebo_pose['qz'] = gz_pose.orientation.z 97 | gazebo_pose['qw'] = gz_pose.orientation.w 98 | self.data_to_rosbridge['gazebo_pose'] = gazebo_pose 99 | 100 | self.talker.publish(roslibpy.Message( {'data': json.dumps(self.data_to_rosbridge)} )) 101 | except (tf.LookupException, tf.ConnectivityException, tf.ExtrapolationException): 102 | rospy.loginfo("[client_rosbridge] TF exception in gathering current position") 103 | 104 | rate.sleep() 105 | 106 | if __name__ == '__main__': 107 | rospy.init_node('client_rosbridge') 108 | send_data = SendData() 109 | send_data.main() 110 | -------------------------------------------------------------------------------- /simulation_ws/src/robot_fleet/scripts/gazebo_model_mover.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | # SPDX-License-Identifier: MIT-0 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 7 | # software and associated documentation files (the "Software"), to deal in the Software 8 | # without restriction, including without limitation the rights to use, copy, modify, 9 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 10 | # permit persons to whom the Software is furnished to do so. 11 | # 12 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 13 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 14 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 15 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 16 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 17 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 18 | # 19 | 20 | """ 21 | This node subscribes to rostopics sent over rosbridge from clients. 22 | It then sets up the corresponding static gazebo model that has the move plugin attached 23 | and also publishes the rostopic that the plugin subscribes to move it. 24 | 25 | The topic that it subscribes to is call /client_robot_data for robots running with 26 | rosbridge clients. It connects to /remapped_client_robot_data in case of server. 27 | 28 | It has the capability to recognize stale data and remove the objects. 29 | """ 30 | 31 | import rospy 32 | import json 33 | import rospkg 34 | from std_msgs.msg import String 35 | from geometry_msgs.msg import Pose 36 | from subprocess32 import check_output 37 | from gazebo_msgs.srv import SpawnModelRequest,SpawnModel, DeleteModel, DeleteModelRequest 38 | from gazebo_msgs.msg import ModelState 39 | 40 | 41 | class MasterGazeboMover: 42 | def __init__(self): 43 | self.robot_name = rospy.get_param('ROBOT_NAME') 44 | self.rosbridge_state = rospy.get_param('ROSBRIDGE_STATE') 45 | self.sdf_file_path = rospy.get_param('ROBOT_SDF_FILE') 46 | self.use_custom_move_plugin = rospy.get_param('use_custom_move_object_gazebo_plugin') 47 | self.init_pubs() 48 | self.init_subs() 49 | self.all_data = {} 50 | self.stale_msg_cleaner_timer = rospy.Timer(rospy.Duration(5) , self.timer_callback) 51 | self.rospack = rospkg.RosPack() 52 | 53 | if self.rosbridge_state == 'SERVER': 54 | self.robot_data_sub = rospy.Subscriber('client_robot_data', String, self.data_callback, queue_size=1) 55 | elif self.rosbridge_state == 'CLIENT': 56 | self.robot_data_sub = rospy.Subscriber('remapped_client_robot_data', String, self.data_callback, queue_size=1) 57 | else: 58 | rospy.logerr("Unexpected ROSBRIDGE_STATE. Ensure either SERVER or CLIENT") 59 | 60 | self.path_xacro = check_output(["which", "xacro" ]).strip('\n') 61 | 62 | def init_pubs(self): 63 | if not self.use_custom_move_plugin: 64 | self.gazebo_set_state_pub = rospy.Publisher('gazebo/set_model_state', ModelState, queue_size=1) 65 | self.all_active_data_pub = rospy.Publisher('all_active_data_debug', String, queue_size=1) 66 | 67 | def timer_callback(self, event): 68 | """In this callback, we find out all the stale data and remove the corresponding models.""" 69 | if bool(self.all_data): # if dict not empty 70 | for name, data_w_time in self.all_data.iteritems(): 71 | if rospy.Time.now() - data_w_time[1] > rospy.Duration(10): # If data is staler than 30 secs 72 | rospy.logwarn("Removing {} from data since its stale".format(name)) 73 | self.all_data.pop(name) 74 | 75 | request = DeleteModelRequest() 76 | request.model_name = name 77 | 78 | response = self.delete_model(request) 79 | rospy.loginfo(response) 80 | break # Since we are popping an element, it will mess up the for loop. Remove one at a time. 81 | 82 | def create_pub(self, robot_name): 83 | return rospy.Publisher(robot_name + '/topic_to_move', Pose, queue_size=1) 84 | 85 | def init_subs(self): 86 | rospy.wait_for_service('/gazebo/spawn_sdf_model') 87 | self.add_model = rospy.ServiceProxy('/gazebo/spawn_sdf_model', SpawnModel) 88 | 89 | rospy.wait_for_service('/gazebo/delete_model') 90 | self.delete_model = rospy.ServiceProxy('/gazebo/delete_model',DeleteModel) 91 | 92 | 93 | def create_model(self, name, xml_file_name, pose_dict ): 94 | rospy.loginfo("Creating model {}".format(name)) 95 | 96 | xml_model = check_output([ self.path_xacro, self.sdf_file_path ]).strip('\n') 97 | 98 | request = SpawnModelRequest() 99 | request.model_name = name 100 | request.model_xml = xml_model 101 | request.robot_namespace = '' 102 | request.reference_frame = '' 103 | 104 | request.initial_pose.position.x = pose_dict['x'] 105 | request.initial_pose.position.y = pose_dict['y'] 106 | request.initial_pose.position.z = pose_dict['z'] 107 | 108 | request.initial_pose.orientation.x = pose_dict['qx'] 109 | request.initial_pose.orientation.y = pose_dict['qy'] 110 | request.initial_pose.orientation.z = pose_dict['qz'] 111 | request.initial_pose.orientation.w = pose_dict['qw'] 112 | 113 | response = self.add_model(request) 114 | rospy.loginfo(response) 115 | 116 | def data_callback(self, msg): 117 | """ This is the callback that gets called by data from all robots. We just update a datastructure 118 | with the latest data, and create an entry if it doesn't exist.""" 119 | data = json.loads(msg.data) 120 | 121 | # Ignore data from current robot since it's already available 122 | if data['name'] == self.robot_name: 123 | pass 124 | elif self.all_data.get( data['name'], None) is None: 125 | self.create_model(data['name'], data['robot_sdf_file'], data['gazebo_pose'] ) 126 | if self.use_custom_move_plugin: 127 | self.all_data[data['name']] = [ data, rospy.Time.now(), self.create_pub(data['name']) ] 128 | else: 129 | self.all_data[data['name']] = [ data, rospy.Time.now() ] 130 | else: 131 | self.all_data[data['name']][0] = data 132 | self.all_data[data['name']][1] = rospy.Time.now() 133 | 134 | def main(self): 135 | rate = rospy.Rate(10.0) 136 | 137 | while not rospy.is_shutdown(): 138 | all_active_data = {} 139 | for name, data_w_timestamp in self.all_data.iteritems(): 140 | data = data_w_timestamp[0] 141 | gazebo_pose = data['gazebo_pose'] 142 | 143 | robot_pose = Pose() 144 | robot_pose.position.x = gazebo_pose['x'] 145 | robot_pose.position.y = gazebo_pose['y'] 146 | robot_pose.position.z = gazebo_pose['z'] 147 | robot_pose.orientation.x = gazebo_pose['qx'] 148 | robot_pose.orientation.y = gazebo_pose['qy'] 149 | robot_pose.orientation.z = gazebo_pose['qz'] 150 | robot_pose.orientation.w = gazebo_pose['qw'] 151 | 152 | if self.use_custom_move_plugin: 153 | publisher = data_w_timestamp[2] 154 | publisher.publish(robot_pose) 155 | else: 156 | model_state = ModelState() 157 | model_state.model_name = name 158 | model_state.pose = robot_pose 159 | self.gazebo_set_state_pub.publish(model_state) 160 | 161 | all_active_data[data['name']] = data 162 | 163 | self.all_active_data_pub.publish(json.dumps(all_active_data)) 164 | rate.sleep() 165 | 166 | 167 | if __name__ == '__main__': 168 | rospy.init_node('gazebo_model_mover') 169 | master = MasterGazeboMover() 170 | master.main() 171 | -------------------------------------------------------------------------------- /simulation_ws/src/robot_fleet/src/move_object.cc: -------------------------------------------------------------------------------- 1 | /* 2 | # 3 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | # SPDX-License-Identifier: MIT-0 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 7 | # software and associated documentation files (the "Software"), to deal in the Software 8 | # without restriction, including without limitation the rights to use, copy, modify, 9 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 10 | # permit persons to whom the Software is furnished to do so. 11 | # 12 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 13 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 14 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 15 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 16 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 17 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 18 | # 19 | */ 20 | /* 21 | This plugin subscribes to a topic call "topic_to_move" and sets the pose of the model that it is 22 | attached to based on the data from the topic. It is intended to be attached to a static model. 23 | 24 | When spawning a robot with gazebo/spawn_sdf_model rosservice call, the topic to subscribe gets 25 | a namespace attached to it. So this plugin then looks for /topic_to_move 26 | */ 27 | 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include "ros/ros.h" 35 | #include "ros/callback_queue.h" 36 | #include "ros/subscribe_options.h" 37 | #include "std_msgs/Float32.h" 38 | #include "geometry_msgs/Pose.h" 39 | #include 40 | #include 41 | 42 | 43 | using namespace gazebo; 44 | 45 | class MoveObject : public ModelPlugin 46 | { 47 | public: void Load(physics::ModelPtr _parent, sdf::ElementPtr _sdf) 48 | { 49 | this->model = _parent; 50 | this->model_name = this->model->GetName(); 51 | #if GAZEBO_MAJOR_VERSION < 8 52 | this->current_pose = this->model->GetWorldPose(); 53 | #else 54 | this->current_pose = this->model->WorldPose(); 55 | #endif 56 | 57 | // Listen to the update event. This event is broadcast every simulation iteration. 58 | this->updateConnection = event::Events::ConnectWorldUpdateBegin(std::bind(&MoveObject::OnUpdate, this)); 59 | this->old_secs =ros::Time::now().toSec(); 60 | 61 | // Initialize ros, if it has not already been initialized. 62 | if (!ros::isInitialized()) 63 | { 64 | ROS_FATAL_STREAM("A ROS node for Gazebo has not been initialized, unable to load plugin. " 65 | << "Load the Gazebo system plugin 'libgazebo_ros_api_plugin.so' in the gazebo_ros package)"); 66 | return; 67 | } 68 | 69 | // Create our ROS node. This acts in a similar manner to the Gazebo node 70 | this->rosNode.reset(new ros::NodeHandle(this->model->GetName())); 71 | 72 | this->rosNode->getParam(std::string("/use_custom_move_object_gazebo_plugin"), this->use_custom_move_object_gazebo_plugin); 73 | ROS_INFO_STREAM("use_move is set to: " << this->use_custom_move_object_gazebo_plugin); 74 | 75 | 76 | if (this->use_custom_move_object_gazebo_plugin) 77 | { 78 | // Store the pointer to the model 79 | 80 | // Subscribe to topic here. Note that if you pass model name, it adds namespace to the topic 81 | ros::SubscribeOptions sub_options = ros::SubscribeOptions::create("topic_to_move", 1, 82 | boost::bind(&MoveObject::OnPoseCallback, this, _1), 83 | ros::VoidPtr(), &this->rosQueue); 84 | this->rosSubscription = this->rosNode->subscribe(sub_options); 85 | 86 | // Spin up the queue helper thread. 87 | this->rosQueueThread = std::thread(std::bind(&MoveObject::QueueThread, this)); 88 | } 89 | } 90 | 91 | // Destructor 92 | public: virtual ~MoveObject() 93 | { 94 | if (this->use_custom_move_object_gazebo_plugin) 95 | { 96 | this->rosQueue.disable(); 97 | this->rosQueueThread.detach(); 98 | } 99 | this->rosNode->shutdown(); 100 | } 101 | 102 | // Called by the world update start event 103 | public: void OnUpdate() 104 | { 105 | if (this->use_custom_move_object_gazebo_plugin) 106 | { 107 | this->model->SetWorldPose(this->current_pose); 108 | } 109 | } 110 | 111 | public: void OnPoseCallback(const geometry_msgs::PoseConstPtr &_msg) 112 | { 113 | // Update location variables based on data from callback. 114 | this->current_pose = ignition::math::Pose3d( _msg->position.x, _msg->position.y, _msg->position.z, 115 | _msg->orientation.w, _msg->orientation.x, _msg->orientation.y,_msg->orientation.z); 116 | } 117 | 118 | /// \brief ROS helper function that processes messages 119 | private: void QueueThread() 120 | { 121 | static const double timeout = 0.01; 122 | while (this->rosNode->ok()) 123 | { 124 | this->rosQueue.callAvailable(ros::WallDuration(timeout)); 125 | } 126 | } 127 | 128 | 129 | // Pointer to the model 130 | private: physics::ModelPtr model; 131 | #if GAZEBO_MAJOR_VERSION < 8 132 | private: math::Pose current_pose; 133 | #else 134 | private: ignition::math::Pose3d current_pose; 135 | #endif 136 | private: std::string rostopic_to_follow; 137 | private: std::string model_name; 138 | 139 | // Pointer to the update event connection 140 | private: event::ConnectionPtr updateConnection; 141 | private: bool use_custom_move_object_gazebo_plugin; 142 | 143 | // Time Memory 144 | double old_secs; 145 | 146 | // \brief A node use for ROS transport 147 | private: std::unique_ptr rosNode; 148 | 149 | /// \brief A ROS subscriber 150 | private: ros::Subscriber rosSubscription; 151 | /// \brief A ROS callbackqueue that helps process messages 152 | private: ros::CallbackQueue rosQueue; 153 | /// \brief A thread the keeps running the rosQueue 154 | private: std::thread rosQueueThread; 155 | }; 156 | 157 | // Register this plugin with the simulator 158 | GZ_REGISTER_MODEL_PLUGIN(MoveObject); 159 | -------------------------------------------------------------------------------- /simulation_ws/src/robot_fleet/static_models_w_plugin/husky/husky-1_3.sdf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 0 0 .5 0 0 0 5 | 6 | 7 | 8 | 9 | 10 | 0 0 0.1 0 0 0 11 | 12 | 13 | 1.0074 0.5709 0.2675 14 | 15 | 16 | 17 | 18 | 19 | 33.855 20 | -0.0856132 -0.000839955 0.238145 0 0 0 21 | 22 | 2.2343 23 | -0.023642 24 | 0.275174 25 | 3.42518 26 | 0.00239624 27 | 2.1241 28 | 29 | 30 | 31 | 32 | 33 | 34 | 0 0 0 0 0 0 35 | 36 | model://husky/meshes/base_link.stl 37 | 38 | 39 | 42 | 43 | 44 | 45 | 46 | 47 | 0 0 0 0 0 0 48 | 49 | model://husky/meshes/top_plate.stl 50 | 51 | 52 | 55 | 56 | 57 | 58 | 59 | 60 | 0.47 0 0.091 0 0 0 61 | 62 | model://husky/meshes/bumper.stl 63 | 64 | 65 | 68 | 69 | 70 | 71 | 72 | 73 | -0.47 0 0.091 0 0 3.141 74 | 75 | model://husky/meshes/bumper.stl 76 | 77 | 78 | 81 | 82 | 83 | 84 | 85 | 86 | 0.272 0 0.245 0 0 0 87 | 88 | model://husky/meshes/user_rail.stl 89 | 90 | 91 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | -0.256 0.285475 0.035 0 0 0 101 | 102 | 2.6357 103 | 0 0 0 0 0 0 104 | 105 | 0.0246688 106 | 0 107 | 0 108 | 0.0246688 109 | 0 110 | 0.0441058 111 | 112 | 113 | 114 | 0 0 0 -1.5707 0 0 115 | 116 | 117 | 0.17775 118 | 0.1143 119 | 120 | 121 | 122 | 123 | 124 | 100000.0 125 | 100000.0 126 | 0.0 127 | 0.0 128 | 129 | 130 | 131 | 132 | 133 | 0 0 0 -3.14159 0 0 134 | 135 | model://husky/meshes/wheel.stl 136 | 137 | 138 | 141 | 142 | 143 | 144 | 145 | base_link 146 | back_left_wheel 147 | 148 | 0 1 0 149 | 150 | 151 | 0.000000 152 | 0.900000 153 | 154 | 155 | 156 | 157 | 158 | -0.256 -0.285475 0.035 0 0 0 159 | 160 | 2.6357 161 | 0 0 0 0 0 0 162 | 163 | 0.0246688 164 | 0 165 | 0 166 | 0.0246688 167 | 0 168 | 0.0441058 169 | 170 | 171 | 172 | 0 0 0 -1.5707 0 0 173 | 174 | 175 | 0.17775 176 | 0.1143 177 | 178 | 179 | 180 | 181 | 182 | 100000.0 183 | 100000.0 184 | 0.0 185 | 0.0 186 | 187 | 188 | 189 | 190 | 191 | 0 0 0 -3.14159 0 0 192 | 193 | model://husky/meshes/wheel.stl 194 | 195 | 196 | 199 | 200 | 201 | 202 | 203 | base_link 204 | back_right_wheel 205 | 206 | 0 1 0 207 | 208 | 209 | 0.000000 210 | 0.900000 211 | 212 | 213 | 214 | 215 | 216 | 0.256 0.285475 0.035 0 0 0 217 | 218 | 2.6357 219 | 0 0 0 0 0 0 220 | 221 | 0.0246688 222 | 0 223 | 0 224 | 0.0246688 225 | 0 226 | 0.0441058 227 | 228 | 229 | 230 | 0 0 0 -1.5707 0 0 231 | 232 | 233 | 0.17775 234 | 0.1143 235 | 236 | 237 | 238 | 239 | 240 | 100000.0 241 | 100000.0 242 | 0.0 243 | 0.0 244 | 245 | 246 | 247 | 248 | 249 | 0 0 0 -3.14159 0 0 250 | 251 | model://husky/meshes/wheel.stl 252 | 253 | 254 | 257 | 258 | 259 | 260 | 261 | base_link 262 | front_left_wheel 263 | 264 | 0 1 0 265 | 266 | 267 | 0.000000 268 | 0.900000 269 | 270 | 271 | 272 | 273 | 274 | 0.256 -0.285475 0.035 0 0 0 275 | 276 | 2.6357 277 | 0 0 0 0 0 0 278 | 279 | 0.0246688 280 | 0 281 | 0 282 | 0.0246688 283 | 0 284 | 0.0441058 285 | 286 | 287 | 288 | 0 0 0 -1.5707 0 0 289 | 290 | 291 | 0.17775 292 | 0.1143 293 | 294 | 295 | 296 | 297 | 298 | 100000.0 299 | 100000.0 300 | 0.0 301 | 0.0 302 | 303 | 304 | 305 | 306 | 307 | 0 0 0 -3.14159 0 0 308 | 309 | model://husky/meshes/wheel.stl 310 | 311 | 312 | 315 | 316 | 317 | 318 | 319 | base_link 320 | front_right_wheel 321 | 322 | 0 1 0 323 | 324 | 325 | 0.000000 326 | 0.900000 327 | 328 | 329 | 330 | true 331 | 100.0 332 | back_left_joint 333 | back_right_joint 334 | front_left_joint 335 | front_right_joint 336 | 0.5709 337 | 0.3555 338 | 35 339 | 340 | 341 | 342 | -------------------------------------------------------------------------------- /simulation_ws/src/robot_fleet/static_models_w_plugin/husky/meshes/base_link.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/multi-robot-fleet-sample-application/753f3ff4bf217297b35867aa0a536cd2391a7d21/simulation_ws/src/robot_fleet/static_models_w_plugin/husky/meshes/base_link.stl -------------------------------------------------------------------------------- /simulation_ws/src/robot_fleet/static_models_w_plugin/husky/meshes/bumper.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/multi-robot-fleet-sample-application/753f3ff4bf217297b35867aa0a536cd2391a7d21/simulation_ws/src/robot_fleet/static_models_w_plugin/husky/meshes/bumper.stl -------------------------------------------------------------------------------- /simulation_ws/src/robot_fleet/static_models_w_plugin/husky/meshes/top_plate.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/multi-robot-fleet-sample-application/753f3ff4bf217297b35867aa0a536cd2391a7d21/simulation_ws/src/robot_fleet/static_models_w_plugin/husky/meshes/top_plate.stl -------------------------------------------------------------------------------- /simulation_ws/src/robot_fleet/static_models_w_plugin/husky/meshes/user_rail.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/multi-robot-fleet-sample-application/753f3ff4bf217297b35867aa0a536cd2391a7d21/simulation_ws/src/robot_fleet/static_models_w_plugin/husky/meshes/user_rail.stl -------------------------------------------------------------------------------- /simulation_ws/src/robot_fleet/static_models_w_plugin/husky/meshes/wheel.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/multi-robot-fleet-sample-application/753f3ff4bf217297b35867aa0a536cd2391a7d21/simulation_ws/src/robot_fleet/static_models_w_plugin/husky/meshes/wheel.stl -------------------------------------------------------------------------------- /simulation_ws/src/robot_fleet/static_models_w_plugin/husky/model-1_4.sdf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 0 0 .5 0 0 0 5 | 6 | 7 | 8 | 9 | 10 | 0 0 0.1 0 0 0 11 | 12 | 13 | 1.0074 0.5709 0.2675 14 | 15 | 16 | 17 | 18 | 19 | 33.855 20 | -0.0856132 -0.000839955 0.238145 0 0 0 21 | 22 | 2.2343 23 | -0.023642 24 | 0.275174 25 | 3.42518 26 | 0.00239624 27 | 2.1241 28 | 29 | 30 | 31 | 32 | 33 | 34 | 0 0 0 0 0 0 35 | 36 | model://husky/meshes/base_link.stl 37 | 38 | 39 | 42 | 43 | 44 | 45 | 46 | 47 | 0 0 0 0 0 0 48 | 49 | model://husky/meshes/top_plate.stl 50 | 51 | 52 | 55 | 56 | 57 | 58 | 59 | 60 | 0.47 0 0.091 0 0 0 61 | 62 | model://husky/meshes/bumper.stl 63 | 64 | 65 | 68 | 69 | 70 | 71 | 72 | 73 | -0.47 0 0.091 0 0 3.141 74 | 75 | model://husky/meshes/bumper.stl 76 | 77 | 78 | 81 | 82 | 83 | 84 | 85 | 86 | 0.272 0 0.245 0 0 0 87 | 88 | model://husky/meshes/user_rail.stl 89 | 90 | 91 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | -0.256 0.285475 0.035 0 0 0 101 | 102 | 2.6357 103 | 0 0 0 0 0 0 104 | 105 | 0.0246688 106 | 0 107 | 0 108 | 0.0246688 109 | 0 110 | 0.0441058 111 | 112 | 113 | 114 | 0 0 0 -1.5707 0 0 115 | 116 | 117 | 0.17775 118 | 0.1143 119 | 120 | 121 | 122 | 123 | 124 | 100000.0 125 | 100000.0 126 | 0.0 127 | 0.0 128 | 129 | 130 | 131 | 132 | 133 | 0 0 0 -3.14159 0 0 134 | 135 | model://husky/meshes/wheel.stl 136 | 137 | 138 | 141 | 142 | 143 | 144 | 145 | base_link 146 | back_left_wheel 147 | 148 | 0 1 0 149 | 150 | 151 | 0.000000 152 | 0.900000 153 | 154 | 155 | 156 | 157 | 158 | -0.256 -0.285475 0.035 0 0 0 159 | 160 | 2.6357 161 | 0 0 0 0 0 0 162 | 163 | 0.0246688 164 | 0 165 | 0 166 | 0.0246688 167 | 0 168 | 0.0441058 169 | 170 | 171 | 172 | 0 0 0 -1.5707 0 0 173 | 174 | 175 | 0.17775 176 | 0.1143 177 | 178 | 179 | 180 | 181 | 182 | 100000.0 183 | 100000.0 184 | 0.0 185 | 0.0 186 | 187 | 188 | 189 | 190 | 191 | 0 0 0 -3.14159 0 0 192 | 193 | model://husky/meshes/wheel.stl 194 | 195 | 196 | 199 | 200 | 201 | 202 | 203 | base_link 204 | back_right_wheel 205 | 206 | 0 1 0 207 | 208 | 209 | 0.000000 210 | 0.900000 211 | 212 | 213 | 214 | 215 | 216 | 0.256 0.285475 0.035 0 0 0 217 | 218 | 2.6357 219 | 0 0 0 0 0 0 220 | 221 | 0.0246688 222 | 0 223 | 0 224 | 0.0246688 225 | 0 226 | 0.0441058 227 | 228 | 229 | 230 | 0 0 0 -1.5707 0 0 231 | 232 | 233 | 0.17775 234 | 0.1143 235 | 236 | 237 | 238 | 239 | 240 | 100000.0 241 | 100000.0 242 | 0.0 243 | 0.0 244 | 245 | 246 | 247 | 248 | 249 | 0 0 0 -3.14159 0 0 250 | 251 | model://husky/meshes/wheel.stl 252 | 253 | 254 | 257 | 258 | 259 | 260 | 261 | base_link 262 | front_left_wheel 263 | 264 | 0 1 0 265 | 266 | 267 | 0.000000 268 | 0.900000 269 | 270 | 271 | 272 | 273 | 274 | 0.256 -0.285475 0.035 0 0 0 275 | 276 | 2.6357 277 | 0 0 0 0 0 0 278 | 279 | 0.0246688 280 | 0 281 | 0 282 | 0.0246688 283 | 0 284 | 0.0441058 285 | 286 | 287 | 288 | 0 0 0 -1.5707 0 0 289 | 290 | 291 | 0.17775 292 | 0.1143 293 | 294 | 295 | 296 | 297 | 298 | 100000.0 299 | 100000.0 300 | 0.0 301 | 0.0 302 | 303 | 304 | 305 | 306 | 307 | 0 0 0 -3.14159 0 0 308 | 309 | model://husky/meshes/wheel.stl 310 | 311 | 312 | 315 | 316 | 317 | 318 | 319 | base_link 320 | front_right_wheel 321 | 322 | 0 1 0 323 | 324 | 325 | 0.000000 326 | 0.900000 327 | 328 | 329 | 330 | true 331 | 100.0 332 | back_left_joint 333 | back_right_joint 334 | front_left_joint 335 | front_right_joint 336 | 0.5709 337 | 0.3555 338 | 35 339 | 340 | 341 | 342 | -------------------------------------------------------------------------------- /simulation_ws/src/robot_fleet/static_models_w_plugin/husky/model.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | Clearpath Robotics Husky A200 4 | 1.0 5 | husky-1_3.sdf 6 | model-1_4.sdf 7 | model.sdf 8 | 9 | 10 | Ryan Gariepy 11 | rgariepy@clearpathrobotics.com 12 | 13 | 14 | 15 | Clearpath Robotics Husky A200 - Base Model 16 | 17 | 18 | -------------------------------------------------------------------------------- /simulation_ws/src/robot_fleet/static_models_w_plugin/husky/model.sdf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | true 5 | 6 | 7 | 8 | 9 | 10 | 11 | 0 0 0.1 0 0 0 12 | 13 | 14 | 1.0074 0.5709 0.2675 15 | 16 | 17 | 18 | 19 | 33.855 20 | -0.0856132 -0.000839955 0.238145 0 0 0 21 | 22 | 2.2343 23 | -0.023642 24 | 0.275174 25 | 3.42518 26 | 0.00239624 27 | 2.1241 28 | 29 | 30 | 31 | 32 | 33 | 0 0 0 0 0 0 34 | 35 | 36 | model://husky/meshes/base_link.stl 37 | 38 | 39 | 40 | 43 | 44 | 45 | 46 | 47 | 0 0 0 0 0 0 48 | 49 | 50 | model://husky/meshes/top_plate.stl 51 | 52 | 53 | 54 | 57 | 58 | 59 | 60 | 61 | 0.47 0 0.091 0 0 0 62 | 63 | 64 | model://husky/meshes/bumper.stl 65 | 66 | 67 | 68 | 71 | 72 | 73 | 74 | 75 | -0.47 0 0.091 0 0 3.141 76 | 77 | 78 | model://husky/meshes/bumper.stl 79 | 80 | 81 | 82 | 85 | 86 | 87 | 88 | 89 | 0.272 0 0.245 0 0 0 90 | 91 | 92 | model://husky/meshes/user_rail.stl 93 | 94 | 95 | 96 | 99 | 100 | 101 | 102 | 103 | 104 | -0.256 0.285475 0.035 0 0 0 105 | 106 | 2.6357 107 | 0 0 0 0 0 0 108 | 109 | 0.0246688 110 | 0 111 | 0 112 | 0.0246688 113 | 0 114 | 0.0441058 115 | 116 | 117 | 118 | 0 0 0 -1.5707 0 0 119 | 120 | 121 | 0.17775 122 | 0.1143 123 | 124 | 125 | 126 | 127 | 128 | 100000.0 129 | 100000.0 130 | 0.0 131 | 0.0 132 | 133 | 134 | 135 | 136 | 137 | 0 0 0 -3.14159 0 0 138 | 139 | 140 | model://husky/meshes/wheel.stl 141 | 142 | 143 | 144 | 147 | 148 | 149 | 150 | 151 | base_link 152 | back_left_wheel 153 | 154 | 0 1 0 155 | true 156 | 157 | 158 | 159 | 160 | 0.000000 161 | 0.900000 162 | 163 | 164 | 165 | 166 | 167 | 168 | -0.256 -0.285475 0.035 0 0 0 169 | 170 | 2.6357 171 | 0 0 0 0 0 0 172 | 173 | 0.0246688 174 | 0 175 | 0 176 | 0.0246688 177 | 0 178 | 0.0441058 179 | 180 | 181 | 182 | 0 0 0 -1.5707 0 0 183 | 184 | 185 | 0.17775 186 | 0.1143 187 | 188 | 189 | 190 | 191 | 192 | 100000.0 193 | 100000.0 194 | 0.0 195 | 0.0 196 | 197 | 198 | 199 | 200 | 201 | 0 0 0 -3.14159 0 0 202 | 203 | 204 | model://husky/meshes/wheel.stl 205 | 206 | 207 | 208 | 211 | 212 | 213 | 214 | 215 | base_link 216 | back_right_wheel 217 | 218 | 0 1 0 219 | true 220 | 221 | 222 | 223 | 224 | 0.000000 225 | 0.900000 226 | 227 | 228 | 229 | 230 | 231 | 232 | 0.256 0.285475 0.035 0 0 0 233 | 234 | 2.6357 235 | 0 0 0 0 0 0 236 | 237 | 0.0246688 238 | 0 239 | 0 240 | 0.0246688 241 | 0 242 | 0.0441058 243 | 244 | 245 | 246 | 0 0 0 -1.5707 0 0 247 | 248 | 249 | 0.17775 250 | 0.1143 251 | 252 | 253 | 254 | 255 | 256 | 100000.0 257 | 100000.0 258 | 0.0 259 | 0.0 260 | 261 | 262 | 263 | 264 | 265 | 0 0 0 -3.14159 0 0 266 | 267 | 268 | model://husky/meshes/wheel.stl 269 | 270 | 271 | 272 | 275 | 276 | 277 | 278 | 279 | base_link 280 | front_left_wheel 281 | 282 | 0 1 0 283 | true 284 | 285 | 286 | 287 | 288 | 0.000000 289 | 0.900000 290 | 291 | 292 | 293 | 294 | 295 | 296 | 0.256 -0.285475 0.035 0 0 0 297 | 298 | 2.6357 299 | 0 0 0 0 0 0 300 | 301 | 0.0246688 302 | 0 303 | 0 304 | 0.0246688 305 | 0 306 | 0.0441058 307 | 308 | 309 | 310 | 0 0 0 -1.5707 0 0 311 | 312 | 313 | 0.17775 314 | 0.1143 315 | 316 | 317 | 318 | 319 | 320 | 100000.0 321 | 100000.0 322 | 0.0 323 | 0.0 324 | 325 | 326 | 327 | 328 | 329 | 0 0 0 -3.14159 0 0 330 | 331 | 332 | model://husky/meshes/wheel.stl 333 | 334 | 335 | 336 | 339 | 340 | 341 | 342 | 343 | base_link 344 | front_right_wheel 345 | 346 | 0 1 0 347 | true 348 | 349 | 350 | 351 | 352 | 0.000000 353 | 0.900000 354 | 355 | 356 | 357 | 358 | 369 | 370 | 371 | --------------------------------------------------------------------------------