Repository: RobotecAI/ros2-for-unity Branch: develop Commit: 096e46ccd16a Files: 59 Total size: 122.3 KB Directory structure: gitextract_58lbld25/ ├── .github/ │ └── ISSUE_TEMPLATE/ │ ├── bug_report.md │ ├── custom.md │ └── feature_request.md ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.AL2 ├── README-UBUNTU.md ├── README-WINDOWS.md ├── README.md ├── build.ps1 ├── build.sh ├── create_unity_package.ps1 ├── create_unity_package.sh ├── deploy_unity_plugins.ps1 ├── deploy_unity_plugins.sh ├── docker/ │ ├── Dockerfile │ ├── README.md │ ├── build_image.sh │ ├── custom_messages/ │ │ └── INSERT_CUSTOM_MESSAGES_HERE │ ├── entrypoint.sh │ └── run_container.sh ├── pull_repositories.ps1 ├── pull_repositories.sh ├── ros2_for_unity_custom_messages.repos ├── ros2cs.repos └── src/ ├── Ros2ForUnity/ │ ├── COLCON_IGNORE │ ├── Plugins.meta │ ├── Scripts/ │ │ ├── PostInstall.cs │ │ ├── ROS2ClientExample.cs │ │ ├── ROS2ForUnity.cs │ │ ├── ROS2ForUnity.cs.meta │ │ ├── ROS2ListenerExample.cs │ │ ├── ROS2ListenerExample.cs.meta │ │ ├── ROS2Node.cs │ │ ├── ROS2Node.cs.meta │ │ ├── ROS2PerformanceTest.cs │ │ ├── ROS2PerformanceTest.cs.meta │ │ ├── ROS2ServiceExample.cs │ │ ├── ROS2TalkerExample.cs │ │ ├── ROS2TalkerExample.cs.meta │ │ ├── ROS2UnityComponent.cs │ │ ├── ROS2UnityComponent.cs.meta │ │ ├── ROS2UnityCore.cs │ │ ├── ROS2UnityCore.cs.meta │ │ ├── Sensor.cs │ │ ├── Sensor.cs.meta │ │ ├── Time/ │ │ │ ├── DotnetTimeSource.cs │ │ │ ├── ITimeSource.cs │ │ │ ├── ROS2Clock.cs │ │ │ ├── ROS2Clock.cs.meta │ │ │ ├── ROS2ScalableTimeSource.cs │ │ │ ├── ROS2TimeSource.cs │ │ │ ├── TimeUtils.cs │ │ │ └── UnityTimeSource.cs │ │ ├── Transformations.cs │ │ └── Transformations.cs.meta │ └── Scripts.meta └── scripts/ └── metadata_generator.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - OS: [e.g. Ubuntu20.04, Windows 10] - ros2 distro [e.g. foxy, galactic] - ros2-for-unity version [e.g. 1.1.0, git-sha 6dc898352c9d45996fb0d43e2c8225707e239f4a] - ros2cs version [e.g. git-sha 2a45785b080fffb3ae5e2f645e976a69698810f0] - ros2 dds middleware [e.g. cyclonedds, fastdds] - ros2 environment setup [e.g. single pc, local network] **Additional context** Add any other context about the problem here. ================================================ FILE: .github/ISSUE_TEMPLATE/custom.md ================================================ --- name: Custom issue template about: Describe this issue template's purpose here. title: '' labels: '' assignees: '' --- ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. ================================================ FILE: .gitignore ================================================ install log build .idea src/ros2cs **/metadata*.xml src/Ros2ForUnity/Plugins !src/Ros2ForUnity/Plugins/.gitkeep ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience * Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: * The use of sexualized language or imagery, and sexual attention or advances of any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or email address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at office@robotec.ai. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. ================================================ FILE: CONTRIBUTING.md ================================================ Any contribution that you make to this repository will be under the Apache 2 License, as dictated by that [license](http://www.apache.org/licenses/LICENSE-2.0.html): > 5. Submission of Contributions. Unless You explicitly state otherwise, > any Contribution intentionally submitted for inclusion in the Work > by You to the Licensor shall be under the terms and conditions of > this License, without any additional terms or conditions. > Notwithstanding the above, nothing herein shall supersede or modify > the terms of any separate license agreement you may have executed > with Licensor regarding such Contributions. ================================================ FILE: LICENSE.AL2 ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README-UBUNTU.md ================================================ # ROS2 For Unity - Ubuntu 20.04 and 22.04 This readme contains information specific to Ubuntu 20.04/22.04. For general information, please see [README.md](README.md) ## Building We assume that working directory is `~/ros2-for-unity` and we are using `ROS2 galactic` (replace with `foxy` or `humble` where applicable). ### Prerequisites Start with installation of dependencies. Make sure to complete each step of `ros2cs` [Prerequisites section](https://github.com/RobotecAI/ros2cs/blob/master/README-UBUNTU.md#prerequisites). ### Steps * Clone this project. ```bash git clone git@github.com:RobotecAI/ros2-for-unity.git ~/ros2-for-unity ``` * You need to source your ROS2 installation before you proceed, for each new open terminal. It is convenient to include this command in your `~/.profile` file. ```bash # galactic . /opt/ros/galactic/setup.bash ``` * Enter `Ros2ForUnity` working directory. ```bash cd ~/ros2-for-unity ``` * Set up you custom messages in `ros2_for_unity_custom_messages.repos` * Import necessary and custom messages repositories. ```bash ./pull_repositories.sh ``` > *NOTE* `pull_repositories.sh` script doesn't update already existing repositories, you have to remove `src/ros2cs` folder to re-import new versions. * Build `Ros2ForUnty`. You can build it in standalone or overlay mode. ```bash # standalone mode ./build.sh --standalone # overlay mode ./build.sh ``` * You can add `--clean-install` flag to make sure your installation directory is cleaned before deploying. * Unity Asset is ready to import into your Unity project. You can find it in `install/asset/` directory. * (optionally) To create `.unitypackage` in `install/unity_package` ```bash create_unity_package.sh -u ``` > *NOTE* Unity license is required. ## OS-Specific usage remarks You can run Unity Editor or App executable from GUI (clicking) or from terminal as long as ROS2 is sourced in your environment. The best way to ensure that system-wide is to add `source /opt/ros/foxy/setup.bash` to your `~/.profile` file. Note that you need to re-log for changes in `~/.profile` to take place. Running Unity Editor through Unity Hub is also supported. ## Usage troubleshooting **No ROS environment sourced. You need to source your ROS2 (..)** * If you see `"No ROS environment sourced. You need to source your ROS2 (..)"` message in Unity3D Editor, it means your environment was not sourced properly. This could happen if you run Unity but it redirects to Hub and ignores your console environment variables (this behavior can depend on Unity3D version). In such case, run project directly with `-projectPath` or add ros2 sourcing to your `~/.profile` file (you need to re-log for it to take effect). * Keep in mind that `UnityHub` stays in the background after its first launch and Unity Editor launch without `-projectPath` will redirect to it and the Hub will start Unity Editor. Since environment variables for the process are set on launch and inherited by child processes, your sourced ros2 environment in the console launching the Editor this way won't be applied. To make sure it applies (and to change between different ros2 distributions), make sure to terminate existing UnityHub process and run it with the correct ros2 distribution sourced. **There are no errors but I can't see topics published by Ros2ForUnity** * Make sure your dds config is correct. * Sometimes ROS2 daemon brakes up when changing network interfaces or ROS2 version. Try to stop it forcefully (`pkill -9 _ros2_daemon`) and restart (`ros2 daemon start`). ================================================ FILE: README-WINDOWS.md ================================================ # ROS2 For Unity - Windows 10 This readme contains information specific to Window 10. For general information, please see [README.md](README.md). ## Building We assume that working directory is `C:\dev` and we are using `ROS2 galactic` (replace with `foxy` or `humble` where applicable). ### Prerequisites It is necessary to complete all the steps for `ros2cs` [Prerequisites](https://github.com/RobotecAI/ros2cs/blob/master/README-WINDOWS.md#prerequisites) and consider [Important notices](https://github.com/RobotecAI/ros2cs/blob/master/README-WINDOWS.md#important-notices) sections. ### Steps * Make sure [long paths on Windows are enabled](https://github.com/RobotecAI/ros2cs/blob/master/README-WINDOWS.md#important-notices) * Make sure you open [`Developer PowerShell for VS` with administrator privileges](https://github.com/RobotecAI/ros2cs/blob/master/README-WINDOWS.md#important-notices) * For `ros2 galactic` distribution, it is best to [create a `C:\ci\ws\install\include` directory](https://github.com/RobotecAI/ros2cs/blob/master/README-WINDOWS.md#important-notices) * Clone this project. ```powershell git clone git@github.com:RobotecAI/ros2-for-unity.git C:\dev\ros2-for-unity ``` * Source your ROS2 installation (`C:\dev\ros2_foxy\local_setup.ps1`) in the terminal before you proceed. ``` C:\dev\ros2_foxy\local_setup.ps1 ``` * Enter `Ros2ForUnity` working directory. ```powershell cd C:\dev\ros2-for-unity ``` * Set up you custom messages in `ros2_for_unity_custom_messages.repos` * Import necessary and custom messages repositories. ```powershell .\pull_repositories.ps1 ``` > *NOTE* `pull_repositories.ps1` script doesn't update already existing repositories, you have to remove `src\ros2cs` folder to re-import new versions. * Build `Ros2ForUnty`. You can build it in standalone or overlay mode. ```powershell # standalone mode ./build.ps1 -standalone # overlay mode ./build.ps1 ``` * You can build with `-clean_install` to make sure your installation directory is cleaned before deploying. * Unity Asset is ready to import into your Unity project. You can find it in `install/asset/` directory. * (optionally) To create `.unitypackage` in `install/unity_package` ```powershell create_unity_package.ps1 ``` > *NOTE* Please provide path to your Unity executable when prompted. Unity license is required. In case your Unity license has expired, the `create_unity_package.ps1` won't throw any errors but `Ros2ForUnity.unitypackage` won't be generated too. ## Build troubleshooting - If you see one of the following errors: > is not digitally signed > cannot be loaded because running scripts is disabled on this system Please execute `Set-ExecutionPolicy Bypass -Scope Process` in PS shell session to enable third party scripts execution only for this session. Otherwise please refer to official [Execution Policies](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_execution_policies?view=powershell-7.1). - If you see the following error: > [4.437s] Traceback (most recent call last): > [4.437s] File "", line 1, in > [4.437s] File "C:\Python38\lib\site-packages\numpy\__init__.py", line 148, in > [4.437s] from . import _distributor_init > [4.437s] File "C:\Python38\lib\site-packages\numpy\_distributor_init.py", line 26, in > [4.437s] WinDLL(os.path.abspath(filename)) > [4.437s] File "C:\Python38\lib\ctypes\__init__.py", line 373, in __init__ > [4.453s] self._handle = _dlopen(self._name, mode) > [4.453s] OSError: [WinError 193] %1 is not a valid Win32 application > [4.469s] CMake Error at C:/dev/ros2_foxy/share/rosidl_generator_py/cmake/rosidl_generator_py_generate_interfaces.cmake:213 (message) > [4.469s] execute_process(C:/Python38/python.exe -c 'import > [4.469s] numpy;print(numpy.get_include())') returned error code 1 > [4.469s] Call Stack (most recent call first): > [4.469s] C:/dev/ros2_foxy/share/ament_cmake_core/cmake/core/ament_execute_extensions.cmake:48 (include) > [4.469s] C:/dev/ros2_foxy/share/rosidl_cmake/cmake/rosidl_generate_interfaces.cmake:286 (ament_execute_extensions) > [4.484s] CMakeLists.txt:16 (rosidl_generate_interfaces) Please reinstall `numpy` package from python by typing: ```powershell pip uninstall numpy pip install numpy ``` **If no solution of your problem is present in the section above, please make sure to check out `ros2cs` [Troubleshooting section](https://github.com/RobotecAI/ros2cs/blob/master/README-WINDOWS.md#troubleshooting)** ## OS-Specific usage remarks > If the Asset is built with `-standalone` flag (the default), then nothing extra needs to be done. Otherwise, you have to source your ros distribution before launching either Unity3D Editor or Application. > Note that after you build the Asset, you can use it on a machine that has no ros2 installation (if built with `-standalone`). > You can simply copy over the `Ros2ForUnity` subdirectory to update your Asset. ================================================ FILE: README.md ================================================ Ros2 For Unity =============== > [!NOTE] > This project is officially supported for [AWSIM](https://github.com/tier4/AWSIM) users of Autoware. However, the Robotec team is unable to provide support and maintain the project for the general > community. If you are looking for an alternative to Unity3D, [Open 3D Engine (O3DE)](https://o3de.org/) is a great, open-source and free simulation engine with excellent [ROS 2 integration](https://development--o3deorg.netlify.app/docs/user-guide/interactivity/), which Robotec is actively supporting and developing. ROS2 For Unity is a high-performance communication solution to connect Unity3D and ROS2 ecosystem in a ROS2 "native" way. Communication is not bridged as in several other solutions, but instead it uses ROS2 middleware stack (rcl layer and below), which means you can have ROS2 nodes in your simulation. Advantages of this module include: - High performance - higher throughput and considerably lower latencies comparing to bridging solutions. - Your simulation entities are real ROS2 nodes / publishers / subscribers. They will behave correctly with e.g. command line tools such as `ros2 topic`. They will respect QoS settings and can use ROS2 native time. - The module supplies abstractions and tools to use in your Unity project, including transformations, sensor interface, a clock, spinning loop wrapped in a MonoBehavior, handling initialization and shutdown. - Supports all standard ROS2 messages. - Custom messages are generated automatically with build, using standard ROS2 way. It is straightforward to generate and use them without having to define `.cs` equivalents by hand. - The module is wrapped as a Unity asset. ## Platforms Supported OSes: - Ubuntu 22.04 (bash) - Ubuntu 20.04 (bash) - Windows 10 (powershell) - Windows 11* (powershel) > \* ROS2 Galactic and Humble support only Windows 10 ([ROS 2 Windows system requirements](https://docs.ros.org/en/humble/Installation/Windows-Install-Binary.html#system-requirements)), but it is proven that it also works fine on Windows 11. Supported ROS2 distributions: - Galactic - Humble Supported Unity3d: - 2020+ Older versions of Unity3d may work, but the editor executable most probably won't be detected properly by deployment script. This would require user confirmation for using unsupported version. This asset can be prepared in two flavours: - standalone mode, where no ROS2 installation is required on target machine, e.g., your Unity3D simulation server. All required dependencies are installed and can be used e.g. as a complete set of Unity3D plugins. - overlay mode, where the ROS2 installation is required on target machine. Only asset libraries and generated messages are installed therefore ROS2 instance must be sourced. ## Releases The best way to start quickly is to use our releases. You can download pre-built [releases](https://github.com/RobotecAI/ros2-for-unity/releases) of the Asset that support both platforms and specific ros2 and Unity3D versions. ## Building > **Note:** The project will pull `ros2cs` into the workspace, which also functions independently as it is a more general project aimed at any `C# / .Net` environment. It has its own README and scripting, but for building the Unity Asset, please use instructions and scripting in this document instead, unless you also wish to run tests or examples for `ros2cs`. Please see OS-specific instructions: - [Instructions for Ubuntu](README-UBUNTU.md) - [Instructions for Windows](README-WINDOWS.md) ## Custom messages Custom messages can be included in the build by either: * listing them in `ros2_for_unity_custom_messages.repos` file, or * manually inserting them in `src/ros2cs` directory. If the folder doesn't exist, you must pull repositories first (see building steps for each OS). ## Installation 1. Perform building steps described in the OS-specific readme or download pre-built Unity package. Do not source `ros2-for-unity` nor `ros2cs` project into ROS2 workspace. 1. Open or create Unity project. 1. Import asset into project: 1. copy `install/asset/Ros2ForUnity` into your project `Assets` folder, or 1. if you have deployed an `.unitypackage` - import it in Unity Editor by selecting `Import Package` → `Custom Package` ## Usage **Prerequisites** * If your build was prepared with `--standalone` flag then you are fine, and all you have to do is run the editor otherwise * source ROS2 which matches the `Ros2ForUnity` version, then run the editor from within the very same terminal/console. **Initializing Ros2ForUnity** 1. Initialize `Ros2ForUnity` by creating a "hook" object which will be your wrapper around ROS2. You have two options: 1. `ROS2UnityComponent` based on `MonoBehaviour` which must be attached to a `GameObject` somewhere in the scene, then: ```c# using ROS2; ... // Example method of getting component, if ROS2UnityComponent lives in different GameObject, just use different get component methods. ROS2UnityComponent ros2Unity = GetComponent(); ``` 1. or `ROS2UnityCore` which is a standard class that can be created anywhere ```c# using ROS2; ... ROS2UnityCore ros2Unity = new ROS2UnityCore(); ``` 1. Create a node. You must first check if `Ros2ForUnity` is initialized correctly: ```c# private ROS2Node ros2Node; ... if (ros2Unity.Ok()) { ros2Node = ros2Unity.CreateNode("ROS2UnityListenerNode"); } ``` **Publishing messages:** 1. Create publisher ```c# private IPublisher chatter_pub; ... if (ros2Unity.Ok()){ chatter_pub = ros2Node.CreatePublisher("chatter"); } ``` 1. Send messages ```c# std_msgs.msg.String msg = new std_msgs.msg.String(); msg.Data = "Hello Ros2ForUnity!"; chatter_pub.Publish(msg); ``` **Subscribing to a topic** 1. Create subscriber: ```c# private ISubscription chatter_sub; ... if (ros2Unity.Ok()) { chatter_sub = ros2Node.CreateSubscription( "chatter", msg => Debug.Log("Unity listener heard: [" + msg.Data + "]")); } ``` **Creating a service** 1. Create service body: ```c# public example_interfaces.srv.AddTwoInts_Response addTwoInts( example_interfaces.srv.AddTwoInts_Request msg) { example_interfaces.srv.AddTwoInts_Response response = new example_interfaces.srv.AddTwoInts_Response(); response.Sum = msg.A + msg.B; return response; } ``` 1. Create a service with a service name and callback: ```c# IService service = ros2Node.CreateService( "add_two_ints", addTwoInts); ``` **Calling a service** 1. Create a client: ```c# private IClient addTwoIntsClient; ... addTwoIntsClient = ros2Node.CreateClient( "add_two_ints"); ``` 1. Create a request and call a service: ```c# example_interfaces.srv.AddTwoInts_Request request = new example_interfaces.srv.AddTwoInts_Request(); request.A = 1; request.B = 2; var response = addTwoIntsClient.Call(request); ``` 1. You can also make an async call: ```c# Task asyncTask = addTwoIntsClient.CallAsync(request); ... asyncTask.ContinueWith((task) => { Debug.Log("Got answer " + task.Result.Sum); }); ``` ### Examples 1. Create a top-level object containing `ROS2UnityComponent.cs`. This is the central `Monobehavior` for `Ros2ForUnity` that manages all the nodes. Refer to class documentation for details. > **Note:** Each example script looks for `ROS2UnityComponent` in its own game object. However, this is not a requirement, just example implementation. **Topics** 1. Add `ROS2TalkerExample.cs` script to the very same game object. 1. Add `ROS2ListenerExample.cs` script to the very same game object. Once you start the project in Unity, you should be able to see two nodes talking with each other in Unity Editor's console or use `ros2 node list` and `ros2 topic echo /chatter` to verify ros2 communication. **Services** 1. Add `ROS2ServiceExample.cs` script to the very same game object. 1. Add `ROS2ClientExample.cs` script to the very same game object. Once you start the project in Unity, you should be able to see client node calling an example service. ## Acknowledgements Open-source release of ROS2 For Unity was made possible through cooperation with [TIER IV](https://tier4.jp). Thanks to encouragement, support and requirements driven by TIER IV the project was significantly improved in terms of portability, stability, core structure and user-friendliness. ================================================ FILE: build.ps1 ================================================ <# .SYNOPSIS Builds Ros2ForUnity asset .DESCRIPTION This script builds Ros2DorUnity asset .PARAMETER with_tests Build tests .PARAMETER standalone Add ros2 binaries. Currently standalone flag is fixed to true, so there is no way to build without standalone libs. Parameter kept for future releases .PARAMETER clean_install Makes a clean installation. Removes install dir before deploying #> Param ( [Parameter(Mandatory=$false)][switch]$with_tests=$false, [Parameter(Mandatory=$false)][switch]$standalone=$false, [Parameter(Mandatory=$false)][switch]$clean_install=$false ) $scriptPath = split-path -parent $MyInvocation.MyCommand.Definition if(-Not (Test-Path -Path "$scriptPath\src\ros2cs")) { Write-Host "Pull repositories with 'pull_repositories.ps1' first." -ForegroundColor Red exit 1 } Write-Host $msg -ForegroundColor Green $options = @{ with_tests = $with_tests standalone = $standalone } if($clean_install) { Write-Host "Cleaning install directory..." -ForegroundColor White Remove-Item -Path "$scriptPath\install" -Force -Recurse -ErrorAction Ignore } if($standalone) { & "python" $SCRIPTPATH\src\scripts\metadata_generator.py --standalone } else { & "python" $SCRIPTPATH\src\scripts\metadata_generator.py } & "$scriptPath\src\ros2cs\build.ps1" @options if($?) { md -Force $scriptPath\install\asset | Out-Null Copy-Item -Path $scriptPath\src\Ros2ForUnity -Destination $scriptPath\install\asset\ -Recurse -Force $plugin_path=Join-Path -Path $scriptPath -ChildPath "\install\asset\Ros2ForUnity\Plugins\" Write-Host "Deploying build to $plugin_path" -ForegroundColor Green & "$scriptPath\deploy_unity_plugins.ps1" $plugin_path Copy-Item -Path $scriptPath\src\Ros2ForUnity\metadata_ros2cs.xml -Destination $scriptPath\install\asset\Ros2ForUnity\Plugins\Windows\x86_64\ Copy-Item -Path $scriptPath\src\Ros2ForUnity\metadata_ros2cs.xml -Destination $scriptPath\install\asset\Ros2ForUnity\Plugins\ } else { Write-Host "Ros2cs build failed!" -ForegroundColor Red exit 1 } ================================================ FILE: build.sh ================================================ #!/bin/bash SCRIPT=$(readlink -f $0) SCRIPTPATH=`dirname $SCRIPT` display_usage() { echo "Usage: " echo "" echo "build.sh [--with-tests] [--standalone] [--clean-install]" echo "" echo "Options:" echo "--with-tests - build with tests" echo "--standalone - standalone version" echo "--clean-install - makes a clean installation, removes install directory before deploying" } if [ ! -d "$SCRIPTPATH/src/ros2cs" ]; then echo "Pull repositories with 'pull_repositories.sh' first." exit 1 fi OPTIONS="" STANDALONE=0 TESTS=0 CLEAN_INSTALL=0 while [[ $# -gt 0 ]]; do key="$1" case $key in -t|--with-tests) OPTIONS="$OPTIONS --with-tests" TESTS=1 shift # past argument ;; -s|--standalone) if ! hash patchelf 2>/dev/null ; then echo "Patchelf missing. Standalone build requires patchelf. Install it via apt 'sudo apt install patchelf'." exit 1 fi OPTIONS="$OPTIONS --standalone" STANDALONE=1 shift # past argument ;; -c|--clean-install) CLEAN_INSTALL=1 shift # past argument ;; -h|--help) display_usage exit 0 shift # past argument ;; *) # unknown option shift # past argument ;; esac done if [ $CLEAN_INSTALL == 1 ]; then echo "Cleaning install directory..." rm -rf $SCRIPTPATH/install/* fi if [ $STANDALONE == 1 ]; then python3 $SCRIPTPATH/src/scripts/metadata_generator.py --standalone else python3 $SCRIPTPATH/src/scripts/metadata_generator.py fi if $SCRIPTPATH/src/ros2cs/build.sh $OPTIONS; then mkdir -p $SCRIPTPATH/install/asset && cp -R $SCRIPTPATH/src/Ros2ForUnity $SCRIPTPATH/install/asset/ $SCRIPTPATH/deploy_unity_plugins.sh $SCRIPTPATH/install/asset/Ros2ForUnity/Plugins/ cp $SCRIPTPATH/src/Ros2ForUnity/metadata_ros2cs.xml $SCRIPTPATH/install/asset/Ros2ForUnity/Plugins/Linux/x86_64/metadata_ros2cs.xml cp $SCRIPTPATH/src/Ros2ForUnity/metadata_ros2cs.xml $SCRIPTPATH/install/asset/Ros2ForUnity/Plugins/metadata_ros2cs.xml else echo "Ros2cs build failed!" exit 1 fi ================================================ FILE: create_unity_package.ps1 ================================================ <# .SYNOPSIS Creates a 'unitypackage' from an input asset. .DESCRIPTION This script screates a temporary Unity project in "%USERPROFILE%\AppData\Local\Temp" directory, copy input asset and makes an unity package out of it. Valid Unity license is required. .PARAMETER unity_path Unity editor executable path .PARAMETER input_asset input asset to pack into unity package .PARAMETER package_name Unity package name .PARAMETER output_dir output file directory #> Param ( [Parameter(Mandatory=$true)][string]$unity_path, [Parameter(Mandatory=$false)][string]$input_asset, [Parameter(Mandatory=$false)][string]$package_name="Ros2ForUnity", [Parameter(Mandatory=$false)][string]$output_dir ) $scriptPath = split-path -parent $MyInvocation.MyCommand.Definition $temp_dir = $Env:TEMP if(-Not $PSBoundParameters.ContainsKey('input_asset')) { $input_asset= Join-Path -Path $scriptPath -ChildPath "\install\asset\Ros2ForUnity" } if(-Not $PSBoundParameters.ContainsKey('output_dir')) { $output_dir= Join-Path -Path $scriptPath -ChildPath "\install\unity_package" } if(-Not (Test-Path -Path "$input_asset")) { Write-Host "Input asset '$input_asset' doesn't exist! Use 'build.ps1' to build project first." -ForegroundColor Red exit 1 } if(-Not (Test-Path -Path "$output_dir")) { mkdir ${output_dir} | Out-Null } & "$unity_path" -version | Tee-Object -Variable unity_version | Out-Null if ($unity_version -match '^[0-9]{4}\.[0-9]*\.[0-9]*[f]?[0-9]*$') { Write-Host "Unity editor confirmed." } else { while (1) { $confirmation = Read-Host "Can't confirm Unity editor. Do you want to force $unity_path as an Unity editor executable? [y]es or [n]o" if ($confirmation -eq 'y' -or $confirmation -eq 'Y') { break; } elseif ( $confirmation -eq 'n' -or $confirmation -eq 'N' ) { exit 1; } else { Write-Host "Please answer [y]es or [n]o."; } } } Write-Host "Using ${unity_path} editor." $tmp_project_path = Join-Path -Path "$temp_dir" -ChildPath "\ros2cs_unity_project\$unity_version" # Create temp project if(Test-Path -Path "$tmp_project_path") { Write-Host "Found existing temporary project for Unity $unity_version." Remove-Item -Path "$tmp_project_path\Assets\*" -Force -Recurse -ErrorAction Ignore } else { Write-Host "Creating Unity temporary project for Unity $unity_version..." & "$unity_path" -createProject "$tmp_project_path" -batchmode -quit | Out-Null } # Copy asset Write-Host "Copying asset '$input_asset' to export..." Copy-Item -Path "$input_asset" -Destination "$tmp_project_path\Assets\$package_name" -Recurse # Creating asset Write-Host "Saving unitypackage '$output_dir\$package_name.unitypackage'..." & "$unity_path" -projectPath "$tmp_project_path" -exportPackage "Assets\$package_name" "$output_dir\$package_name.unitypackage" -batchmode -quit | Out-Null # Cleaning up Write-Host "Cleaning up temporary project..." Remove-Item -Path "$tmp_project_path\Assets\*" -Force -Recurse -ErrorAction Ignore Write-Host "Done!" -ForegroundColor Green ================================================ FILE: create_unity_package.sh ================================================ #!/bin/bash SCRIPT=$(readlink -f $0) SCRIPTPATH=`dirname $SCRIPT` display_usage() { echo "This script creates a temporary Unity project in '/tmp' directory, copy input asset and makes an unity package out of it. Valid Unity license is required." echo "" echo "Usage:" echo "create_unity_package.sh -u -i [INPUT_ASSET] -p [PACKAGE_NAME] -o [OUTPUT_DIR]" echo "" echo "UNITY_PATH - Unity editor executable path" echo "INPUT_ASSET - input asset to pack into unity package, default = 'install/asset/Ros2ForUnity'" echo "PACKAGE_NAME - unity package name, default = 'Ros2ForUnity'" echo "OUTPUT_DIR - output file directory, default = 'install/unity_package'" } UNITY_PATH="" INPUT_ASSET="install/asset/Ros2ForUnity" PACKAGE_NAME="Ros2ForUnity" OUTPUT_DIR="$SCRIPTPATH/install/unity_package" while [[ $# -gt 0 ]]; do key="$1" case $key in -u|--unity-path) UNITY_PATH="$2" shift # past argument shift # past value ;; -p|--package_name) PACKAGE_NAME="$2" shift # past argument shift # past value ;; -i|--input-directory) INPUT_ASSET="$2" shift # past argument shift # past value ;; -o|--output-directory) OUTPUT_DIR="$2" shift # past argument shift # past value ;; -h|--help) display_usage exit 0 shift # past argument ;; *) # unknown option shift # past argument ;; esac done if [ -z "$UNITY_PATH" ] || [ -z "$PACKAGE_NAME" ] || [ -z "$INPUT_ASSET" ] || [ -z "$OUTPUT_DIR" ]; then echo -e "\nMissing arguments!" echo "" display_usage exit 1 fi if [ ! -d "$INPUT_ASSET" ]; then echo "Input asset '$INPUT_ASSET' doesn't exist! Use 'build.sh' to build project first." exit 1 fi UNITY_VERSION=`$UNITY_PATH -version` # Test if unity editor is valid if [[ $UNITY_VERSION =~ ^[0-9]{4}\.[0-9]*\.[0-9]*[f]?[0-9]*$ ]]; then echo "Unity editor confirmed." else while true; do read -p "Can't confirm Unity editor. Do you want to force \"$UNITY_PATH\" as an Unity editor executable? [y]es or [N]o: " yn yn=${yn:-"n"} case $yn in [Yy]* ) break;; [Nn]* ) exit 1;; * ) echo "Please answer [y]es or [n]o.";; esac done fi echo "Using \"${UNITY_PATH}\" editor." TMP_PROJECT_PATH=/tmp/ros2cs_unity_project/$UNITY_VERSION # Create temp project if [ -d "$TMP_PROJECT_PATH" ]; then echo "Found existing temporary project for Unity $UNITY_VERSION." rm -rf $TMP_PROJECT_PATH/Assets/* else rm -rf $TMP_PROJECT_PATH echo "Creating Unity temporary project for Unity $UNITY_VERSION..." $UNITY_PATH -createProject $TMP_PROJECT_PATH -batchmode -quit fi # Copy asset echo "Copying asset to export..." cp -r "$INPUT_ASSET" "$TMP_PROJECT_PATH/Assets/$PACKAGE_NAME" # Creating asset echo "Saving unitypackage '$OUTPUT_DIR/$PACKAGE_NAME.unitypackage'..." mkdir -p $OUTPUT_DIR $UNITY_PATH -projectPath "$TMP_PROJECT_PATH" -exportPackage "Assets/$PACKAGE_NAME" "$OUTPUT_DIR/$PACKAGE_NAME.unitypackage" -batchmode -quit # Cleaning up echo "Cleaning up temporary project..." rm -rf $TMP_PROJECT_PATH/Assets/* echo "Done!" ================================================ FILE: deploy_unity_plugins.ps1 ================================================ $scriptPath = split-path -parent $MyInvocation.MyCommand.Definition $pluginDir=$args[0] function Print-Help { " Usage: deploy_unity_plugins.ps1 PLUGINS_DIR - Ros2ForUnity/Plugins. " } if (([string]::IsNullOrEmpty($pluginDir)) -Or $args[0] -eq "--help" -Or $args[0] -eq "-h") { Print-Help exit } if (Test-Path -Path $pluginDir) { Write-Host "Copying plugins to to: '$pluginDir' ..." Get-ChildItem $scriptPath\install\lib\dotnet\ -Recurse -Exclude @('*.pdb') | Copy-Item -Destination ${pluginDir} Write-Host "Plugins copied to: '$pluginDir'" -ForegroundColor Green if(-not (Test-Path -Path $pluginDir\Windows\x86_64\)) { mkdir ${pluginDir}\Windows\x86_64\ } Write-Host "Copying libraries to: '$pluginDir\Windows\x86_64\' ..." Get-ChildItem $scriptPath\install\bin\ -Recurse -Exclude @('*_py.dll', '*_python.dll') | Copy-Item -Destination ${pluginDir}\Windows\x86_64\ if(-not (Test-Path -Path $scriptPath\install\standalone\)) { mkdir $scriptPath\install\standalone } (Copy-Item -Path $scriptPath\install\standalone\*.dll -Destination ${pluginDir}\Windows\x86_64\ 4>&1).Message if(-not (Test-Path -Path $scriptPath\install\resources\)) { mkdir $scriptPath\install\resources } (Copy-Item -Path $scriptPath\install\resources\*.dll -Destination ${pluginDir}\Windows\x86_64\ 4>&1).Message Write-Host "Libraries copied to '${pluginDir}\Windows\x86_64\'" -ForegroundColor Green } else { Write-Host "Plugins directory: '$pluginDir' doesn't exist. Please create it first manually." -ForegroundColor Red } ================================================ FILE: deploy_unity_plugins.sh ================================================ #!/bin/bash SCRIPT=$(readlink -f $0) SCRIPTPATH=`dirname $SCRIPT` if [ $# -eq 0 ] || [ $1 = "-h" ] || [ $1 = "--help" ]; then echo "Usage:" echo "deploy_unity_plugins.sh " echo "" echo "PLUGINS_DIR - Ros2ForUnity/Plugins folder." exit 1 fi pluginDir=$1 mkdir -p ${pluginDir}/Linux/x86_64/ find install/lib/dotnet/ -maxdepth 1 -not -name "*.pdb" -type f -exec cp {} ${pluginDir} \; cp $SCRIPTPATH/install/standalone/* ${pluginDir}/Linux/x86_64/ 2>/dev/null find install/lib/ -maxdepth 1 -not -name "*_python.so" -type f -exec cp {} ${pluginDir}/Linux/x86_64/ \; cp $SCRIPTPATH/install/resources/*.so ${pluginDir}/Linux/x86_64/ 2>/dev/null ================================================ FILE: docker/Dockerfile ================================================ ARG ROS2_DISTRO=humble FROM ros:${ROS2_DISTRO}-ros-base RUN apt update && apt install -y ros-${ROS_DISTRO}-test-msgs ros-${ROS_DISTRO}-fastrtps ros-${ROS_DISTRO}-rmw-fastrtps-cpp ros-${ROS_DISTRO}-cyclonedds ros-${ROS_DISTRO}-rmw-cyclonedds-cpp RUN apt update && apt install -y curl wget git RUN curl -s https://packagecloud.io/install/repositories/dirk-thomas/vcstool/script.deb.sh | sudo bash RUN apt update && apt install -y python3-vcstool RUN apt update && apt install -y apt-transport-https patchelf dotnet-sdk-6.0 RUN apt update && apt install -y ffmpeg libsm6 libxext6 libgtk-3-0 ADD entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh RUN mkdir -p /workdir/ros2-for-unity RUN chmod -R 777 /workdir RUN chmod -R 777 /home ENTRYPOINT [ "/entrypoint.sh" ] ================================================ FILE: docker/README.md ================================================ Ros2 For Unity Docker =============== Currently only building asset on Ubuntu is supported. Build windows version is not supported. ## Build docker image 1. Source ROS2 (foxy or galactic): ```bash . /opt/ros//setup.bash ``` 2. Build image - image will be based on sourced ROS2 version: ```bash ./build_image.sh ``` ## Using docker container 1. Run docker container. Container will fetch `master` version of `ros2-for-unity`: ```bash ./run_container.sh ``` 2. Build asset. `./run_container.sh` script mounts `install` host directory inside docker, so you can find install results on host machine: ```bash ./build.sh --with-tests ``` ## Adding custom messages You can add custom messages by putting them inside `docker/custom_messages` folder or just simply `git clone` them inside docker containers `/workdir/ros2-for-unity/src/ros2cs/src/custom_messages` ================================================ FILE: docker/build_image.sh ================================================ #!/bin/bash if [ -z "$ROS_DISTRO" ]; then echo "Source your ros2 distro first." exit 1 fi docker build . --build-arg ROS2_DISTRO=$ROS_DISTRO --tag ros2-for-unity ================================================ FILE: docker/custom_messages/INSERT_CUSTOM_MESSAGES_HERE ================================================ ================================================ FILE: docker/entrypoint.sh ================================================ #!/bin/bash source "/opt/ros/$ROS_DISTRO/setup.bash" echo "######################################################################" echo "" echo "Cloning recent version of 'ros2-for-unity'" echo "" echo "######################################################################" echo "" git clone https://github.com/RobotecAI/ros2-for-unity.git /workdir/.ros2-for-unity shopt -s dotglob mkdir -p /workdir/ros2-for-unity mv /workdir/.ros2-for-unity/* /workdir/ros2-for-unity cd /workdir/ros2-for-unity/ && ./pull_repositories.sh mkdir -p /home/$(whoami) git config --global --add safe.directory /workdir/ros2-for-unity shopt -u dotglob ln -s /workdir/custom_messages /workdir/ros2-for-unity/src/ros2cs/src/custom_messages echo "" echo "######################################################################" echo "" echo "Welcome to 'ros2-for-unity' docker container. Your ROS2 distro is $ROS_DISTRO." echo "" echo "Type './build.sh' to build 'ros2-for-unity'. You will find installed libs on your host machine inside 'install' directory" echo "" echo "######################################################################" echo "" exec bash ================================================ FILE: docker/run_container.sh ================================================ #!/bin/bash SCRIPT=$(readlink -f $0) SCRIPTPATH=`dirname $SCRIPT` mkdir -p $SCRIPTPATH/../install docker run \ --rm \ -it \ --name ros2-for-unity \ --user $(id -u):$(id -g) \ -v /etc/passwd:/etc/passwd:ro \ -v /etc/group:/etc/group:ro \ -v /etc/shadow:/etc/shadow:ro \ -v $(pwd)/../install:/workdir/ros2-for-unity/install:rw \ -v $(pwd)/custom_messages:/workdir/custom_messages \ ros2-for-unity \ bash ================================================ FILE: pull_repositories.ps1 ================================================ $scriptPath = split-path -parent $MyInvocation.MyCommand.Definition if (([string]::IsNullOrEmpty($Env:ROS_DISTRO))) { Write-Host "Can't detect ROS2 version. Source your ros2 distro first. Foxy and Galactic are supported." -ForegroundColor Red exit } $ros2cs_repos = Join-Path -Path $scriptPath -ChildPath "\ros2cs.repos" $custom_repos = Join-Path -Path $scriptPath -ChildPath "\ros2_for_unity_custom_messages.repos" Write-Host "=========================================" Write-Host "* Pulling ros2cs repository:" vcs import --input $ros2cs_repos Write-Host "" Write-Host "=========================================" Write-Host "Pulling custom repositories:" vcs import --input $custom_repos Write-Host "" Write-Host "=========================================" Write-Host "Pulling ros2cs dependencies:" & "$scriptPath/src/ros2cs/get_repos.ps1" ================================================ FILE: pull_repositories.sh ================================================ #!/bin/bash SCRIPT=$(readlink -f $0) SCRIPTPATH=`dirname $SCRIPT` if [ -z "${ROS_DISTRO}" ]; then echo "Can't detect ROS2 version. Source your ros2 distro first. Foxy and Galactic are supported" exit 1 fi echo "=========================================" echo "* Pulling ros2cs repository:" vcs import < "ros2cs.repos" echo "" echo "=========================================" echo "Pulling custom repositories:" vcs import < "ros2_for_unity_custom_messages.repos" echo "" echo "=========================================" echo "Pulling ros2cs dependencies:" cd "$SCRIPTPATH/src/ros2cs" ./get_repos.sh cd - ================================================ FILE: ros2_for_unity_custom_messages.repos ================================================ # NOTE: Use this file if you want to build with custom messages that reside in a separate remote repo. # NOTE: use the following format repositories: # src/ros2cs/custom_messages/: # type: git # url: # version: # custom_messages/: # ... # ... ================================================ FILE: ros2cs.repos ================================================ repositories: src/ros2cs/: type: git url: https://github.com/RobotecAI/ros2cs.git version: 1.3.0 ================================================ FILE: src/Ros2ForUnity/COLCON_IGNORE ================================================ ================================================ FILE: src/Ros2ForUnity/Plugins.meta ================================================ fileFormatVersion: 2 guid: 79b46a636d96b67468a29e597cb7b06a folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: src/Ros2ForUnity/Scripts/PostInstall.cs ================================================ // Copyright 2019-2022 Robotec.ai. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #if UNITY_EDITOR using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; using UnityEngine; using UnityEditor; using UnityEditor.Build; using UnityEditor.Build.Reporting; namespace ROS2 { /// /// An internal class responsible for installing ros2-for-unity metadata files /// internal class PostInstall : IPostprocessBuildWithReport { public int callbackOrder { get { return 0; } } public void OnPostprocessBuild(BuildReport report) { var r2fuMetadataName = "metadata_ros2_for_unity.xml"; var r2csMetadataName = "metadata_ros2cs.xml"; // FileUtil.CopyFileOrDirectory: All file separators should be forward ones "/". var r2fuMeta = ROS2ForUnity.GetRos2ForUnityPath() + "/" + r2fuMetadataName; var r2csMeta = ROS2ForUnity.GetPluginPath() + "/" + r2csMetadataName; var outputDir = Directory.GetParent(report.summary.outputPath); var execFilename = Path.GetFileNameWithoutExtension(report.summary.outputPath); FileUtil.CopyFileOrDirectory( r2fuMeta, outputDir + "/" + execFilename + "_Data/" + r2fuMetadataName); if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.StandaloneLinux64) { FileUtil.CopyFileOrDirectory( r2csMeta, outputDir + "/" + execFilename + "_Data/Plugins/" + r2csMetadataName); // Copy versioned libraries (Unity skips them) Regex soWithVersionReg = new Regex(@".*\.so(\.[0-9])+$"); var versionedLibs = new List(Directory.GetFiles(ROS2ForUnity.GetPluginPath())) .Where(path => soWithVersionReg.IsMatch(path)) .ToList(); foreach (var libPath in versionedLibs) { FileUtil.CopyFileOrDirectory( libPath, outputDir + "/" + execFilename + "_Data/Plugins/" + Path.GetFileName(libPath)); } } else { FileUtil.CopyFileOrDirectory( r2csMeta, outputDir + "/" + execFilename + "_Data/Plugins/x86_64/" + r2csMetadataName); } } } } #endif ================================================ FILE: src/Ros2ForUnity/Scripts/ROS2ClientExample.cs ================================================ // Copyright 2019-2021 Robotec.ai. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. using System.Collections; using System.Collections.Generic; using System.Threading.Tasks; using UnityEngine; using ROS2; using addTwoIntsReq = example_interfaces.srv.AddTwoInts_Request; using addTwoIntsResp = example_interfaces.srv.AddTwoInts_Response; /// /// An example class provided for testing of basic ROS2 client /// public class ROS2ClientExample : MonoBehaviour { private ROS2UnityComponent ros2Unity; private ROS2Node ros2Node; private IClient addTwoIntsClient; private bool isRunning = false; private Task asyncTask; IEnumerator periodicAsyncCall() { while (ros2Unity.Ok()) { while (!addTwoIntsClient.IsServiceAvailable()) { yield return new WaitForSecondsRealtime(1); } addTwoIntsReq request = new addTwoIntsReq(); request.A = Random.Range(0, 100); request.B = Random.Range(0, 100); asyncTask = addTwoIntsClient.CallAsync(request); asyncTask.ContinueWith((task) => { Debug.Log("Got async answer " + task.Result.Sum); }); yield return new WaitForSecondsRealtime(1); } } IEnumerator periodicCall() { while (ros2Unity.Ok()) { while (!addTwoIntsClient.IsServiceAvailable()) { yield return new WaitForSecondsRealtime(1); } addTwoIntsReq request = new addTwoIntsReq(); request.A = Random.Range(0, 100); request.B = Random.Range(0, 100); var response = addTwoIntsClient.Call(request); Debug.Log("Got sync answer " + response.Sum); yield return new WaitForSecondsRealtime(1); } } void Start() { ros2Unity = GetComponent(); if (ros2Unity.Ok()) { if (ros2Node == null) { ros2Node = ros2Unity.CreateNode("ROS2UnityClient"); addTwoIntsClient = ros2Node.CreateClient( "add_two_ints"); } } } void Update() { if (!isRunning) { isRunning = true; // Async calls StartCoroutine(periodicAsyncCall()); // Sync calls StartCoroutine(periodicCall()); } } } ================================================ FILE: src/Ros2ForUnity/Scripts/ROS2ForUnity.cs ================================================ // Copyright 2019-2021 Robotec.ai. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. using System; using System.IO; using System.Collections.Generic; using UnityEngine; using UnityEditor; using System.Xml; namespace ROS2 { /// /// An internal class responsible for handling checking, proper initialization and shutdown of ROS2cs, /// internal class ROS2ForUnity { private static bool isInitialized = false; private static string ros2ForUnityAssetFolderName = "Ros2ForUnity"; private XmlDocument ros2csMetadata = new XmlDocument(); private XmlDocument ros2ForUnityMetadata = new XmlDocument(); public enum Platform { Windows, Linux } public static Platform GetOS() { if (Application.platform == RuntimePlatform.LinuxEditor || Application.platform == RuntimePlatform.LinuxPlayer) { return Platform.Linux; } else if (Application.platform == RuntimePlatform.WindowsEditor || Application.platform == RuntimePlatform.WindowsPlayer) { return Platform.Windows; } throw new System.NotSupportedException("Only Linux and Windows are supported"); } private static bool InEditor() { return Application.isEditor; } private static string GetOSName() { switch (GetOS()) { case Platform.Linux: return "Linux"; case Platform.Windows: return "Windows"; default: throw new System.NotSupportedException("Only Linux and Windows are supported"); } } private string GetEnvPathVariableName() { string envVariable = "LD_LIBRARY_PATH"; if (GetOS() == Platform.Windows) { envVariable = "PATH"; } return envVariable; } private string GetEnvPathVariableValue() { return Environment.GetEnvironmentVariable(GetEnvPathVariableName()); } public static string GetRos2ForUnityPath() { char separator = Path.DirectorySeparatorChar; string appDataPath = Application.dataPath; string pluginPath = appDataPath; if (InEditor()) { pluginPath += separator + ros2ForUnityAssetFolderName; } return pluginPath; } public static string GetPluginPath() { char separator = Path.DirectorySeparatorChar; string ros2ForUnityPath = GetRos2ForUnityPath(); string pluginPath = ros2ForUnityPath; pluginPath += separator + "Plugins"; if (InEditor()) { pluginPath += separator + GetOSName(); } if (InEditor() || GetOS() == Platform.Windows) { pluginPath += separator + "x86_64"; } if (GetOS() == Platform.Windows) { pluginPath = pluginPath.Replace("/", "\\"); } return pluginPath; } /// /// Function responsible for setting up of environment paths for standalone builds /// /// /// Note that on Linux, LD_LIBRARY_PATH as used for dlopen() is determined on process start and this change won't /// affect it. Ros2 looks for rmw implementation based on this variable (independently) and the change /// is effective for this process, however rmw implementation's dependencies itself are loaded by dynamic linker /// anyway so setting it for Linux is pointless. /// private void SetEnvPathVariable() { string currentPath = GetEnvPathVariableValue(); string pluginPath = GetPluginPath(); char envPathSep = ':'; if (GetOS() == Platform.Windows) { envPathSep = ';'; } Environment.SetEnvironmentVariable(GetEnvPathVariableName(), pluginPath + envPathSep + currentPath); } public bool IsStandalone() { return Convert.ToBoolean(Convert.ToInt16(GetMetadataValue(ros2csMetadata, "/ros2cs/standalone"))); } public string GetROSVersion() { string ros2SourcedCodename = GetROSVersionSourced(); string ros2FromRos4UMetadata = GetMetadataValue(ros2ForUnityMetadata, "/ros2_for_unity/ros2"); // Sourced ROS2 libs takes priority if (string.IsNullOrEmpty(ros2SourcedCodename)) { return ros2FromRos4UMetadata; } return ros2SourcedCodename; } /// /// Checks if both ros2cs and ros2-for-unity were build for the same ros version as well as /// the current sourced ros version matches ros2cs binaries. /// public void CheckIntegrity() { string ros2SourcedCodename = GetROSVersionSourced(); string ros2FromRos2csMetadata = GetMetadataValue(ros2csMetadata, "/ros2cs/ros2"); string ros2FromRos4UMetadata = GetMetadataValue(ros2ForUnityMetadata, "/ros2_for_unity/ros2"); if (ros2FromRos4UMetadata != ros2FromRos2csMetadata) { Debug.LogError( "ROS2 versions in 'ros2cs' and 'ros2-for-unity' metadata files are not the same. " + "This is caused by mixing versions/builds. Plugin might not work correctly." ); } if(!IsStandalone() && ros2SourcedCodename != ros2FromRos2csMetadata) { Debug.LogError( "ROS2 version in 'ros2cs' metadata doesn't match currently sourced version. " + "This is caused by mixing versions/builds. Plugin might not work correctly." ); } if (IsStandalone() && !string.IsNullOrEmpty(ros2SourcedCodename)) { Debug.LogError( "You should not source ROS2 in 'ros2-for-unity' standalone build. " + "Plugin might not work correctly." ); } } public string GetROSVersionSourced() { return Environment.GetEnvironmentVariable("ROS_DISTRO"); } /// /// Check if the ros version is supported, only applicable to non-standalone plugin versions /// (i. e. without ros2 libraries included in the plugin). /// private void CheckROSSupport(string ros2Codename) { List supportedVersions = new List() { "foxy", "galactic", "humble", "rolling" }; var supportedVersionsString = String.Join(", ", supportedVersions); if (string.IsNullOrEmpty(ros2Codename)) { string errMessage = "No ROS environment sourced. You need to source your ROS2 " + supportedVersionsString + " environment before launching Unity (ROS_DISTRO env variable not found)"; Debug.LogError(errMessage); #if UNITY_EDITOR EditorApplication.isPlaying = false; throw new System.InvalidOperationException(errMessage); #else const int ROS_NOT_SOURCED_ERROR_CODE = 33; Application.Quit(ROS_NOT_SOURCED_ERROR_CODE); #endif } if (!supportedVersions.Contains(ros2Codename)) { string errMessage = "Currently sourced ROS version differs from supported one. Sourced: " + ros2Codename + ", supported: " + supportedVersionsString + "."; Debug.LogError(errMessage); #if UNITY_EDITOR EditorApplication.isPlaying = false; throw new System.NotSupportedException(errMessage); #else const int ROS_BAD_VERSION_CODE = 34; Application.Quit(ROS_BAD_VERSION_CODE); #endif } else if (ros2Codename.Equals("rolling") ) { Debug.LogWarning("You are using ROS2 rolling version. Bleeding edge version might not work correctly."); } } private void RegisterCtrlCHandler() { #if ENABLE_MONO // Il2CPP build does not support Console.CancelKeyPress currently Console.CancelKeyPress += (sender, eventArgs) => { eventArgs.Cancel = true; DestroyROS2ForUnity(); }; #endif } private void ConnectLoggers() { Ros2csLogger.setCallback(LogLevel.ERROR, Debug.LogError); Ros2csLogger.setCallback(LogLevel.WARNING, Debug.LogWarning); Ros2csLogger.setCallback(LogLevel.INFO, Debug.Log); Ros2csLogger.setCallback(LogLevel.DEBUG, Debug.Log); Ros2csLogger.LogLevel = LogLevel.WARNING; } private string GetMetadataValue(XmlDocument doc, string valuePath) { return doc.DocumentElement.SelectSingleNode(valuePath).InnerText; } private void LoadMetadata() { char separator = Path.DirectorySeparatorChar; try { ros2csMetadata.Load(GetPluginPath() + separator + "metadata_ros2cs.xml"); ros2ForUnityMetadata.Load(GetRos2ForUnityPath() + separator + "metadata_ros2_for_unity.xml"); } catch (System.IO.FileNotFoundException) { #if UNITY_EDITOR var errMessage = "Could not find metadata files."; EditorApplication.isPlaying = false; throw new System.IO.FileNotFoundException(errMessage); #else const int NO_METADATA = 1; Application.Quit(NO_METADATA); #endif } } internal ROS2ForUnity() { // Load metadata LoadMetadata(); string currentRos2Version = GetROSVersion(); string standalone = IsStandalone() ? "standalone" : "non-standalone"; // Self checks CheckROSSupport(currentRos2Version); CheckIntegrity(); // Library loading if (GetOS() == Platform.Windows) { // Windows version can run standalone, modifies PATH to ensure all plugins visibility SetEnvPathVariable(); } else { // For foxy, it is necessary to use modified version of librcpputils to resolve custom msgs packages. ROS2.GlobalVariables.absolutePath = GetPluginPath() + "/"; if (currentRos2Version == "foxy") { ROS2.GlobalVariables.preloadLibrary = true; ROS2.GlobalVariables.preloadLibraryName = "librcpputils.so"; } } // Initialize ConnectLoggers(); Ros2cs.Init(); RegisterCtrlCHandler(); string rmwImpl = Ros2cs.GetRMWImplementation(); Debug.Log("ROS2 version: " + currentRos2Version + ". Build type: " + standalone + ". RMW: " + rmwImpl); #if UNITY_EDITOR EditorApplication.playModeStateChanged += this.EditorPlayStateChanged; EditorApplication.quitting += this.DestroyROS2ForUnity; #endif isInitialized = true; } private static void ThrowIfUninitialized(string callContext) { if (!isInitialized) { throw new InvalidOperationException("Ros2 For Unity is not initialized, can't " + callContext); } } /// /// Check if ROS2 module is properly initialized and no shutdown was called yet /// /// The state of ROS2 module. Should be checked before attempting to create or use pubs/subs public bool Ok() { if (!isInitialized) { return false; } return Ros2cs.Ok(); } internal void DestroyROS2ForUnity() { if (isInitialized) { Debug.Log("Shutting down Ros2 For Unity"); Ros2cs.Shutdown(); isInitialized = false; } } ~ROS2ForUnity() { DestroyROS2ForUnity(); } #if UNITY_EDITOR void EditorPlayStateChanged(PlayModeStateChange change) { if (change == PlayModeStateChange.ExitingPlayMode) { DestroyROS2ForUnity(); } } #endif } } // namespace ROS2 ================================================ FILE: src/Ros2ForUnity/Scripts/ROS2ForUnity.cs.meta ================================================ fileFormatVersion: 2 guid: 4cdb4e72fb0aa46c09e52778257ed142 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: src/Ros2ForUnity/Scripts/ROS2ListenerExample.cs ================================================ // Copyright 2019-2021 Robotec.ai. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. using System; using UnityEngine; namespace ROS2 { /// /// An example class provided for testing of basic ROS2 communication /// public class ROS2ListenerExample : MonoBehaviour { private ROS2UnityComponent ros2Unity; private ROS2Node ros2Node; private ISubscription chatter_sub; void Start() { ros2Unity = GetComponent(); } void Update() { if (ros2Node == null && ros2Unity.Ok()) { ros2Node = ros2Unity.CreateNode("ROS2UnityListenerNode"); chatter_sub = ros2Node.CreateSubscription( "chatter", msg => Debug.Log("Unity listener heard: [" + msg.Data + "]")); } } } } // namespace ROS2 ================================================ FILE: src/Ros2ForUnity/Scripts/ROS2ListenerExample.cs.meta ================================================ fileFormatVersion: 2 guid: 75a1bd43b302c4c578a744060319517e MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: src/Ros2ForUnity/Scripts/ROS2Node.cs ================================================ // Copyright 2019-2021 Robotec.ai. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. using System; using System.Collections.Generic; using UnityEngine; using UnityEditor; namespace ROS2 { /// /// A class representing a ros2 node. Multiple nodes can be used. Node can be removed by GC when not used anymore, /// but will also be removed properly with Ros2cs Shutdown, which ROS2 for Unity performs on application quit /// The node should be constructed through ROS2UnityComponent class, which also handles spinning /// public class ROS2Node { internal INode node; public ROS2Clock clock; public string name; // Use ROS2UnityComponent to create a node internal ROS2Node(string unityROS2NodeName = "unity_ros2_node") { name = unityROS2NodeName; node = Ros2cs.CreateNode(name); clock = new ROS2Clock(); } ~ROS2Node() { Ros2cs.RemoveNode(node); } private static void ThrowIfUninitialized(string callContext) { if (!Ros2cs.Ok()) { throw new InvalidOperationException("Ros2 For Unity is not initialized, can't " + callContext); } } /// /// Create a publisher with QoS suitable for sensor data /// /// The publisher /// topic that will be used for publishing public Publisher CreateSensorPublisher(string topicName) where T : Message, new() { QualityOfServiceProfile sensorProfile = new QualityOfServiceProfile(QosPresetProfile.SENSOR_DATA); return CreatePublisher(topicName, sensorProfile); } /// /// Create a publisher with indicated QoS. /// /// The publisher /// topic that will be used for publishing /// QoS for publishing. If no QoS is selected, it will default to reliable, keep 10 last public Publisher CreatePublisher(string topicName, QualityOfServiceProfile qos = null) where T : Message, new() { ThrowIfUninitialized("create publisher"); return node.CreatePublisher(topicName, qos); } /// /// Create a subscription /// /// The subscription /// topic to subscribe to /// QoS for subscription. If no QoS is selected, it will default to reliable, keep 10 last public Subscription CreateSubscription(string topicName, Action callback, QualityOfServiceProfile qos = null) where T : Message, new() { if (qos == null) { qos = new QualityOfServiceProfile(QosPresetProfile.DEFAULT); } ThrowIfUninitialized("create subscription"); return node.CreateSubscription(topicName, callback, qos); } /// /// Remove existing subscription (returned earlier with CreateSubscription) /// /// The whether subscription was found (e. g. false if removed earlier elsewhere) /// subscrition to remove, returned from CreateSubscription public bool RemoveSubscription(ISubscriptionBase subscription) { ThrowIfUninitialized("remove subscription"); return node.RemoveSubscription(subscription); } /// /// Remove existing publisher /// /// The whether publisher was found (e. g. false if removed earlier elsewhere) /// publisher to remove, returned from CreatePublisher or CreateSensorPublisher public bool RemovePublisher(IPublisherBase publisher) { ThrowIfUninitialized("remove publisher"); return node.RemovePublisher(publisher); } /// public Service CreateService(string topic, Func callback, QualityOfServiceProfile qos = null) where I : Message, new() where O : Message, new() { ThrowIfUninitialized("create service"); return node.CreateService(topic, callback, qos); } /// public bool RemoveService(IServiceBase service) { ThrowIfUninitialized("remove service"); return node.RemoveService(service); } /// public Client CreateClient(string topic, QualityOfServiceProfile qos = null) where I : Message, new() where O : Message, new() { ThrowIfUninitialized(callContext: "create client"); return node.CreateClient(topic, qos); } /// public bool RemoveClient(IClientBase client) { ThrowIfUninitialized(callContext: "remove client"); return node.RemoveClient(client); } } } // namespace ROS2 ================================================ FILE: src/Ros2ForUnity/Scripts/ROS2Node.cs.meta ================================================ fileFormatVersion: 2 guid: 3e21db77b82bbeb8693eabe308d76f45 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: src/Ros2ForUnity/Scripts/ROS2PerformanceTest.cs ================================================ // Copyright 2019-2021 Robotec.ai. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. using UnityEngine; using System.Threading; namespace ROS2 { /// /// An example class provided for performance testing of ROS2 communication /// public class ROS2PerformanceTest : MonoBehaviour { public int messageSize = 10000; public int rate = 10; private int interval_ms = 100; private ROS2UnityComponent ros2Unity; private ROS2Node ros2Node; private IPublisher perf_pub; sensor_msgs.msg.PointCloud2 msg; private bool initialized = false; void Start() { ros2Unity = GetComponent(); PrepMessage(); } void OnValidate() { if (rate < 1) { interval_ms = 0; } else { interval_ms = 1000 / rate; } PrepMessage(); } private void Publish() { while(true) { if (ros2Unity.Ok()) { if (ros2Node == null) { ros2Node = ros2Unity.CreateNode("ros2_unity_performance_test_node"); perf_pub = ros2Node.CreateSensorPublisher("perf_chatter"); } var msgWithHeader = msg as MessageWithHeader; ros2Node.clock.UpdateROSTimestamp(ref msgWithHeader); perf_pub.Publish(msg); if (interval_ms > 0) { Thread.Sleep(interval_ms); } } } } void FixedUpdate() { if (!initialized) { Thread publishThread = new Thread(() => Publish()); publishThread.Start(); initialized = true; } } private void AssignField(ref sensor_msgs.msg.PointField pf, string n, uint off, byte dt, uint count) { pf.Name = n; pf.Offset = off; pf.Datatype = dt; pf.Count = count; } private void PrepMessage() { uint count = (uint)messageSize; //point per message uint fieldsSize = 16; uint rowSize = count * fieldsSize; msg = new sensor_msgs.msg.PointCloud2() { Height = 1, Width = count, Is_bigendian = false, Is_dense = true, Point_step = fieldsSize, Row_step = rowSize, Data = new byte[rowSize * 1] }; uint pointFieldCount = 4; msg.Fields = new sensor_msgs.msg.PointField[pointFieldCount]; for (int i = 0; i < pointFieldCount; ++i) { msg.Fields[i] = new sensor_msgs.msg.PointField(); } AssignField(ref msg.Fields[0], "x", 0, 7, 1); AssignField(ref msg.Fields[1], "y", 4, 7, 1); AssignField(ref msg.Fields[2], "z", 8, 7, 1); AssignField(ref msg.Fields[3], "intensity", 12, 7, 1); float[] pointsArray = new float[count * msg.Fields.Length]; var floatIndex = 0; for (int i = 0; i < count; ++i) { float intensity = 100; pointsArray[floatIndex++] = 1; pointsArray[floatIndex++] = 2; pointsArray[floatIndex++] = 3; pointsArray[floatIndex++] = intensity; } System.Buffer.BlockCopy(pointsArray, 0, msg.Data, 0, msg.Data.Length); msg.SetHeaderFrame("pc"); } } } // namespace ROS2 ================================================ FILE: src/Ros2ForUnity/Scripts/ROS2PerformanceTest.cs.meta ================================================ fileFormatVersion: 2 guid: 387d300b788c9bd29b6e38808a481155 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: src/Ros2ForUnity/Scripts/ROS2ServiceExample.cs ================================================ // Copyright 2019-2021 Robotec.ai. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. using System.Collections; using System.Collections.Generic; using UnityEngine; using ROS2; using addTwoIntsReq = example_interfaces.srv.AddTwoInts_Request; using addTwoIntsResp = example_interfaces.srv.AddTwoInts_Response; /// /// An example class provided for testing of basic ROS2 service /// public class ROS2ServiceExample : MonoBehaviour { private ROS2UnityComponent ros2Unity; private ROS2Node ros2Node; private IService addTwoIntsService; void Start() { ros2Unity = GetComponent(); if (ros2Unity.Ok()) { if (ros2Node == null) { ros2Node = ros2Unity.CreateNode("ROS2UnityService"); addTwoIntsService = ros2Node.CreateService( "add_two_ints", addTwoInts); } } } public example_interfaces.srv.AddTwoInts_Response addTwoInts( example_interfaces.srv.AddTwoInts_Request msg) { Debug.Log("Incoming Service Request A=" + msg.A + " B=" + msg.B); example_interfaces.srv.AddTwoInts_Response response = new example_interfaces.srv.AddTwoInts_Response(); response.Sum = msg.A + msg.B; return response; } } ================================================ FILE: src/Ros2ForUnity/Scripts/ROS2TalkerExample.cs ================================================ // Copyright 2019-2021 Robotec.ai. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. using UnityEngine; namespace ROS2 { /// /// An example class provided for testing of basic ROS2 communication /// public class ROS2TalkerExample : MonoBehaviour { // Start is called before the first frame update private ROS2UnityComponent ros2Unity; private ROS2Node ros2Node; private IPublisher chatter_pub; private int i; void Start() { ros2Unity = GetComponent(); } void Update() { if (ros2Unity.Ok()) { if (ros2Node == null) { ros2Node = ros2Unity.CreateNode("ROS2UnityTalkerNode"); chatter_pub = ros2Node.CreatePublisher("chatter"); } i++; std_msgs.msg.String msg = new std_msgs.msg.String(); msg.Data = "Unity ROS2 sending: hello " + i; chatter_pub.Publish(msg); } } } } // namespace ROS2 ================================================ FILE: src/Ros2ForUnity/Scripts/ROS2TalkerExample.cs.meta ================================================ fileFormatVersion: 2 guid: 72620fb0a9290863f8643557405c48e3 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: src/Ros2ForUnity/Scripts/ROS2UnityComponent.cs ================================================ // Copyright 2019-2021 Robotec.ai. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. using UnityEngine; using System; using System.Collections.Generic; using System.Threading; using ROS2; namespace ROS2 { /// /// The principal MonoBehaviour class for handling ros2 nodes and executables. /// Use this to create ros2 node, check ros2 status. /// Spins and executes actions (e. g. clock, sensor publish triggers) in a dedicated thread /// TODO: this is meant to be used as a one-of (a singleton). Enforce. However, things should work /// anyway with more than one since the underlying library can handle multiple init and shutdown calls, /// and does node name uniqueness check independently. /// public class ROS2UnityComponent : MonoBehaviour { private ROS2ForUnity ros2forUnity; private List nodes; private List ros2csNodes; // For performance in spinning private List executableActions; private bool initialized = false; private bool quitting = false; private int interval = 2; // Spinning / executor interval in ms private object mutex = new object(); private double spinTimeout = 0.0001; public bool Ok() { lock (mutex) { if (ros2forUnity == null) LazyConstruct(); return (nodes != null && ros2forUnity.Ok()); } } private void LazyConstruct() { lock (mutex) { if (ros2forUnity != null) return; ros2forUnity = new ROS2ForUnity(); nodes = new List(); ros2csNodes = new List(); executableActions = new List(); } } void Start() { LazyConstruct(); } public ROS2Node CreateNode(string name) { LazyConstruct(); lock (mutex) { foreach (ROS2Node n in nodes) { // Assumed to be a rare operation on rather small (<1k) list if (n.name == name) { throw new InvalidOperationException("Cannot create node " + name + ". A node with this name already exists!"); } } ROS2Node node = new ROS2Node(name); nodes.Add(node); ros2csNodes.Add(node.node); return node; } } public void RemoveNode(ROS2Node node) { lock (mutex) { ros2csNodes.Remove(node.node); nodes.Remove(node); //Node will be later deleted if unused, by GC } } /// /// Works as a simple executor registration analogue. These functions will be called with each Tick() /// Actions need to take care of correct call resolution by checking in their body (TODO) /// Make sure actions are lightweight (TODO - separate out threads for spinning and executables?) /// public void RegisterExecutable(Action executable) { LazyConstruct(); lock (mutex) { executableActions.Add(executable); } } public void UnregisterExecutable(Action executable) { lock (mutex) { executableActions.Remove(executable); } } /// /// "Executor" thread will tick all clocks and spin the node /// private void Tick() { while (!quitting) { if (Ok()) { lock (mutex) { foreach (Action action in executableActions) { action(); } Ros2cs.SpinOnce(ros2csNodes, spinTimeout); } } Thread.Sleep(interval); } } void FixedUpdate() { if (!initialized) { Thread publishThread = new Thread(() => Tick()); publishThread.Start(); initialized = true; } } void OnApplicationQuit() { quitting = true; ros2forUnity.DestroyROS2ForUnity(); } } } // namespace ROS2 ================================================ FILE: src/Ros2ForUnity/Scripts/ROS2UnityComponent.cs.meta ================================================ fileFormatVersion: 2 guid: feab04ad06492965492b3edc6423aa53 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: src/Ros2ForUnity/Scripts/ROS2UnityCore.cs ================================================ // Copyright 2019-2022 Robotec.ai. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. using UnityEngine; using System; using System.Collections.Generic; using System.Threading; using ROS2; namespace ROS2 { /// /// The principal class for handling ros2 nodes and executables. /// Use this to create ros2 node, check ros2 status. /// Spins and executes actions (e. g. clock, sensor publish triggers) in a dedicated thread /// TODO: this is meant to be used as a one-of (a singleton). Enforce. However, things should work /// anyway with more than one since the underlying library can handle multiple init and shutdown calls, /// and does node name uniqueness check independently. /// public class ROS2UnityCore { private ROS2ForUnity ros2forUnity; private List nodes; private List ros2csNodes; // For performance in spinning private List executableActions; private bool quitting = false; private int interval = 2; // Spinning / executor interval in ms private object mutex = new object(); private double spinTimeout = 0.0001; public bool Ok() { lock (mutex) { return (nodes != null && ros2forUnity.Ok()); } } public ROS2UnityCore() { lock (mutex) { ros2forUnity = new ROS2ForUnity(); nodes = new List(); ros2csNodes = new List(); executableActions = new List(); Thread publishThread = new Thread(() => Tick()); publishThread.Start(); } } public ROS2Node CreateNode(string name) { lock (mutex) { foreach (ROS2Node n in nodes) { // Assumed to be a rare operation on rather small (<1k) list if (n.name == name) { throw new InvalidOperationException("Cannot create node " + name + ". A node with this name already exists!"); } } ROS2Node node = new ROS2Node(name); nodes.Add(node); ros2csNodes.Add(node.node); return node; } } public void RemoveNode(ROS2Node node) { lock (mutex) { ros2csNodes.Remove(node.node); nodes.Remove(node); //Node will be later deleted if unused, by GC } } /// /// Works as a simple executor registration analogue. These functions will be called with each Tick() /// Actions need to take care of correct call resolution by checking in their body (TODO) /// Make sure actions are lightweight (TODO - separate out threads for spinning and executables?) /// public void RegisterExecutable(Action executable) { lock (mutex) { executableActions.Add(executable); } } public void UnregisterExecutable(Action executable) { lock (mutex) { executableActions.Remove(executable); } } /// /// "Executor" thread will tick all clocks and spin the node /// private void Tick() { while (!quitting) { if (Ok()) { lock (mutex) { foreach (Action action in executableActions) { action(); } Ros2cs.SpinOnce(ros2csNodes, spinTimeout); } } Thread.Sleep(interval); } } public void DestroyNow() { quitting = true; ros2forUnity.DestroyROS2ForUnity(); } } } // namespace ROS2 ================================================ FILE: src/Ros2ForUnity/Scripts/ROS2UnityCore.cs.meta ================================================ fileFormatVersion: 2 guid: d80a8dd00d331ce458b98a7707d03bb3 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: src/Ros2ForUnity/Scripts/Sensor.cs ================================================ // Copyright 2019-2021 Robotec.ai. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. using UnityEngine; using UnityEngine.Profiling; using System; namespace ROS2 { /// /// An abstract base class for ROS2-enabled sensor. /// public abstract class ISensor : MonoBehaviour { /// /// The desired update frequency for the sensor. The maximum can be the rate with which FixedUpdate is called, /// which depends on the physics step (usually 50 or 100 times per second). /// public double desiredUpdateFreq = 25.0; /// /// The frameID corresponds to the ROS frame_id element of the header and is important /// for transformations /// public string frameID = "sensor"; /// /// A topic to which the sensor publishes. Only one per sensor. Don't add the namespace of /// the agent name, it is handled externally (i.e. sensor does not know to what object it belongs). /// public string topicName = ""; /// /// Controls whether sensor is publishing messages /// public bool publishing = false; /// /// Creates sensor publishers and registers it in the executor so that it publishes when new data is available /// /// Central ros2 monobehavior for Unity /// ros2 node that will publish sensor data /// name of the agent (vehicle) to be added to the sensor publish namespace public abstract void CreateROSParticipants(ROS2UnityComponent ros2Unity, ROS2Node node, string agentName); /// /// Returns the constructed frame name, taking in account the agent name(space) /// public abstract string frameName(); } /// /// A base template class for the sensor. The type is the message type of sensor data. /// public abstract class Sensor : ISensor where T : MessageWithHeader, new() { /// /// Acquires the value by performing sensor type characteristic computations (e.g. raycasts). /// Implemented in subclasses. /// /// The message which contains the sensor data. /// Mind that the header for message is handled in a generic way by this class. protected abstract T AcquireValue(); /// /// Returns true when there is a new data available from sensor. /// protected abstract bool HasNewData(); protected double desiredFrameTime = 0.0; private const double minimumFrequency = 0.001; private Publisher publisher; private Subscription clockSubscriber; private ROS2UnityComponent ros2UnityComponent; private ROS2Node ros2Node; private string ownerAgentName; private double lastTimestamp; private double timeSinceLastFixedUpdate; private T readings; private bool newReadings; public override string frameName() { return ownerAgentName + "/" + frameID; } /// /// Visualises the effects of the sensor. It doesn't make sense for some sensor and the /// default implementation is empty. /// protected virtual void VisualiseEffects() { } /// /// When parameters in editor change (i.e. frequency), /// this function is called to calculate new frame time. /// protected virtual void OnValidate() { CalculateFrameTime(); } /// /// An entry point for the per-frame processing done in subclass /// protected virtual void OnUpdate() {} /// /// See superclass definition /// public override void CreateROSParticipants(ROS2UnityComponent ros2Unity, ROS2Node node, string agentName) { if (!ros2Unity.Ok()) { throw new System.InvalidOperationException("Publisher for sensor can't be created when node is not OK"); } if (String.IsNullOrEmpty(topicName)) { throw new System.InvalidOperationException("Topic name not set for the sensor " + this); } ownerAgentName = agentName; ros2UnityComponent = ros2Unity; ros2Node = node; string nsName = agentName.Replace(" ", "_"); publisher = node.CreateSensorPublisher(nsName + "/" + topicName); ros2UnityComponent.RegisterExecutable(ExecutorThreadSensorPublishAction); publishing = true; } /// /// This is executed in an executor thread (through RegisterExecutable) /// Sensor fequency is indirectly handed through newReadings, which are acquired at a requested /// frequency if possible (e. g. due to simulation resource constraints) /// internal void ExecutorThreadSensorPublishAction() { if (!HasNewData()) return; if (publisher != null & publishing) { if (ros2UnityComponent.Ok()) { readings = AcquireValue(); readings.SetHeaderFrame(frameName()); if (readings != null) { MessageWithHeader readingsHeader = readings as MessageWithHeader; ros2Node.clock.UpdateROSTimestamp(ref readingsHeader); publisher.Publish(readings); } } } } /// /// Once each frame, visualise effects of the sensor (if any). Visualisation /// rate is independent of publishing/acquisition rate, which happen at the sensor /// frequency instead of the app frame rate. /// void Update() { VisualiseEffects(); OnUpdate(); } /// /// Initialize header and calculate frame time /// void Awake() { // turn on publishing on start publishing = true; CalculateFrameTime(); lastTimestamp = DateTime.UtcNow.Ticks / 1E7; } /// /// Sensor frequency is used to calculate frame time, based on desired frequency and the bounds. /// void CalculateFrameTime() { double maxFrameFreq = 1.0 / Time.fixedDeltaTime; if (desiredUpdateFreq > maxFrameFreq) { Debug.LogWarning("Desired frame rate of " + desiredUpdateFreq + " can't be met, " + "physics frequency is " + maxFrameFreq); desiredUpdateFreq = maxFrameFreq; //Can't go faster than physics } if (desiredUpdateFreq < minimumFrequency) { Debug.LogWarning("Minimum frequency of " + minimumFrequency + " applied instead of " + desiredUpdateFreq); desiredUpdateFreq = minimumFrequency; } desiredFrameTime = 1.0 / desiredUpdateFreq; } } } // namespace ROS2 ================================================ FILE: src/Ros2ForUnity/Scripts/Sensor.cs.meta ================================================ fileFormatVersion: 2 guid: d3ebd1e57dae48cca9c88c51f18cf510 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: src/Ros2ForUnity/Scripts/Time/DotnetTimeSource.cs ================================================ // Copyright 2022 Robotec.ai. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. using System; using System.Diagnostics; namespace ROS2 { /// /// DateTime based clock that has resolution increased using Stopwatch. /// DateTime is used to synchronize since Stopwatch tends to drift. /// public class DotnetTimeSource : ITimeSource { private readonly double maxUnsyncedSeconds = 10; private Stopwatch stopwatch = new Stopwatch(); private readonly object mutex = new object(); private double systemTimeIntervalStart = 0; private double stopwatchStartTimeStamp; private double TotalSystemTimeSeconds() { return TimeSpan.FromTicks(DateTime.UtcNow.Ticks).TotalSeconds; } private void UpdateSystemTime() { systemTimeIntervalStart = TotalSystemTimeSeconds(); stopwatchStartTimeStamp = Stopwatch.GetTimestamp(); } public DotnetTimeSource() { UpdateSystemTime(); } public void GetTime(out int seconds, out uint nanoseconds) { lock(mutex) // Threading { double endTimestamp = Stopwatch.GetTimestamp(); var durationInSeconds = endTimestamp - stopwatchStartTimeStamp; double timeOffset = 0; if (durationInSeconds >= maxUnsyncedSeconds) { // acquire DateTime to sync UpdateSystemTime(); } else { // use Stopwatch offset timeOffset = durationInSeconds; } TimeUtils.TimeFromTotalSeconds(systemTimeIntervalStart + timeOffset, out seconds, out nanoseconds); } } } } // namespace ROS2 ================================================ FILE: src/Ros2ForUnity/Scripts/Time/ITimeSource.cs ================================================ // Copyright 2022 Robotec.ai. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. namespace ROS2 { /// /// Interace for acquiring time /// public interface ITimeSource { public void GetTime(out int seconds, out uint nanoseconds); } } // namespace ROS2 ================================================ FILE: src/Ros2ForUnity/Scripts/Time/ROS2Clock.cs ================================================ // Copyright 2019-2022 Robotec.ai. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. using System; using UnityEngine; namespace ROS2 { /// /// A ros2 clock class that for interfacing between a time source (unity or ros2 system time) and ros2cs messages, structs. /// public class ROS2Clock { private ITimeSource _timeSource; public ROS2Clock() : this(new ROS2TimeSource()) { // By default, use ROS2TimeSource } public ROS2Clock(ITimeSource ts) { _timeSource = ts; } public void UpdateClockMessage(ref rosgraph_msgs.msg.Clock clockMessage) { int seconds; uint nanoseconds; _timeSource.GetTime(out seconds, out nanoseconds); clockMessage.Clock_.Sec = seconds; clockMessage.Clock_.Nanosec = nanoseconds; } public void UpdateROSClockTime(builtin_interfaces.msg.Time time) { int seconds; uint nanoseconds; _timeSource.GetTime(out seconds, out nanoseconds); time.Sec = seconds; time.Nanosec = nanoseconds; } public void UpdateROSTimestamp(ref ROS2.MessageWithHeader message) { int seconds; uint nanoseconds; _timeSource.GetTime(out seconds, out nanoseconds); message.UpdateHeaderTime(seconds, nanoseconds); } } } // namespace ROS2 ================================================ FILE: src/Ros2ForUnity/Scripts/Time/ROS2Clock.cs.meta ================================================ fileFormatVersion: 2 guid: 69e097a4a027d5a55b991c0b0b1bdf88 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: src/Ros2ForUnity/Scripts/Time/ROS2ScalableTimeSource.cs ================================================ // Copyright 2022 Robotec.ai. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. using System.Threading; using UnityEngine; namespace ROS2 { /// /// ros2 time source (system time by default). /// public class ROS2ScalableTimeSource : ITimeSource { private Thread mainThread; private double lastReadingSecs; private ROS2.Clock clock; private double initialTime = 0; private double initialTimeScale = 0; private bool initialTimeAcquired = false; private bool initialTimeScaleAcquired = false; private bool timeScaleChanged = false; public ROS2ScalableTimeSource() { mainThread = Thread.CurrentThread; } public void GetTime(out int seconds, out uint nanoseconds) { if (!ROS2.Ros2cs.Ok()) { seconds = 0; nanoseconds = 0; Debug.LogWarning("Cannot acquire valid ros time, ros either not initialized or shut down already"); return; } if (clock == null) { // Create clock which uses system time by default (unless use_sim_time is set in ros2) clock = new ROS2.Clock(); } if (!initialTimeScaleAcquired) { initialTimeScaleAcquired = true; initialTimeScale = Time.timeScale; } if (initialTimeScale != Time.timeScale) { timeScaleChanged = true; } lastReadingSecs = mainThread.Equals(Thread.CurrentThread) ? Time.timeAsDouble : lastReadingSecs; if (initialTimeScale == 1.0 && !timeScaleChanged) { TimeUtils.TimeFromTotalSeconds(clock.Now.Seconds, out seconds, out nanoseconds); } else { if (!initialTimeAcquired) { initialTimeAcquired = true; initialTime = clock.Now.Seconds - Time.timeAsDouble; } TimeUtils.TimeFromTotalSeconds(lastReadingSecs + initialTime, out seconds, out nanoseconds); } } ~ROS2ScalableTimeSource() { if (clock != null) { clock.Dispose(); } } } } // namespace ROS2 ================================================ FILE: src/Ros2ForUnity/Scripts/Time/ROS2TimeSource.cs ================================================ // Copyright 2022 Robotec.ai. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. using UnityEngine; namespace ROS2 { /// /// ros2 time source (system time by default). /// public class ROS2TimeSource : ITimeSource { private ROS2.Clock clock; public void GetTime(out int seconds, out uint nanoseconds) { if (!ROS2.Ros2cs.Ok()) { seconds = 0; nanoseconds = 0; Debug.LogWarning("Cannot acquire valid ros time, ros either not initialized or shut down already"); return; } if (clock == null) { // Create clock which uses system time by default (unless use_sim_time is set in ros2) clock = new ROS2.Clock(); } TimeUtils.TimeFromTotalSeconds(clock.Now.Seconds, out seconds, out nanoseconds); } ~ROS2TimeSource() { if (clock != null) { clock.Dispose(); } } } } // namespace ROS2 ================================================ FILE: src/Ros2ForUnity/Scripts/Time/TimeUtils.cs ================================================ // Copyright 2022 Robotec.ai. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. namespace ROS2 { /// /// Interace for acquiring time /// internal static class TimeUtils { public static void TimeFromTotalSeconds(in double secondsIn, out int seconds, out uint nanoseconds) { long nanosec = (long)(secondsIn * 1e9); seconds = (int)(nanosec / 1000000000); nanoseconds = (uint)(nanosec % 1000000000); } } } // namespace ROS2 ================================================ FILE: src/Ros2ForUnity/Scripts/Time/UnityTimeSource.cs ================================================ // Copyright 2022 Robotec.ai. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. using System; using System.Threading; using UnityEngine; namespace ROS2 { /// /// Acquires Unity time. Note that Time API only allows main thread access, /// but this class object also stores last acquired value for other threads. /// This is done without a warning, so the class will not behave as expected /// when not used by main thread. /// public class UnityTimeSource : ITimeSource { private Thread mainThread; private double lastReadingSecs; public UnityTimeSource() { mainThread = Thread.CurrentThread; } public void GetTime(out int seconds, out uint nanoseconds) { lastReadingSecs = mainThread.Equals(Thread.CurrentThread) ? Time.timeAsDouble : lastReadingSecs; TimeUtils.TimeFromTotalSeconds(lastReadingSecs, out seconds, out nanoseconds); } } } // namespace ROS2 ================================================ FILE: src/Ros2ForUnity/Scripts/Transformations.cs ================================================ // Copyright 2019-2021 Robotec.ai. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. using UnityEngine; namespace ROS2 { /// /// A set of transformation functions between coordinate systems of Unity and ROS /// public static class Transformations { public static Vector3 Ros2Unity(this Vector3 vector3) { return new Vector3(-vector3.y, vector3.z, vector3.x); } public static Vector3 Unity2Ros(this Vector3 vector3) { return new Vector3(vector3.z, -vector3.x, vector3.y); } public static Vector3 Ros2UnityScale(this Vector3 vector3) { return new Vector3(vector3.y, vector3.z, vector3.x); } public static Vector3 Unity2RosScale(this Vector3 vector3) { return new Vector3(vector3.z, vector3.x, vector3.y); } public static Quaternion Ros2Unity(this Quaternion quaternion) { return new Quaternion(quaternion.y, -quaternion.z, -quaternion.x, quaternion.w); } public static Quaternion Unity2Ros(this Quaternion quaternion) { return new Quaternion(-quaternion.z, quaternion.x, -quaternion.y, quaternion.w); } public static void Unity2Ros(ref Quaternion quaternion) { var z = quaternion.z; var x = quaternion.x; var y = quaternion.y; quaternion.x = -z; quaternion.y = x; quaternion.z = -y; } public static void Unity2Ros(ref Vector3 vector) { var z = vector.z; var x = vector.x; var y = vector.y; vector.x = z; vector.y = -x; vector.z = y; } public static Matrix4x4 Unity2RosMatrix4x4() { // Note: The matrix here is written as-if on paper, // but Unity's Matrix4x4 is constructed from column-vectors, hence the transpose. return new Matrix4x4( new Vector4( 0.0f, 0.0f, 1.0f, 0.0f), new Vector4(-1.0f, 0.0f, 0.0f, 0.0f), new Vector4( 0.0f, 1.0f, 0.0f, 0.0f), new Vector4( 0.0f, 0.0f, 0.0f, 1.0f) ).transpose; } } } // namespace ROS2 ================================================ FILE: src/Ros2ForUnity/Scripts/Transformations.cs.meta ================================================ fileFormatVersion: 2 guid: 5ab7c5f5cc85e9e3aa6134862d9c1cba MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: src/Ros2ForUnity/Scripts.meta ================================================ fileFormatVersion: 2 guid: f750980d49c8bcf39830e89365689d16 folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: src/scripts/metadata_generator.py ================================================ # Copyright 2019-2022 Robotec.ai. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import argparse import xml.etree.ElementTree as ET from xml.dom import minidom import subprocess import pathlib import os parser = argparse.ArgumentParser(description='Generate metadata file for ros2-for-unity.') parser.add_argument('--standalone', action='store_true', help='is a standalone build') args = parser.parse_args() def get_git_commit(working_directory) -> str: return subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=working_directory).decode('ascii').strip() def get_git_description(working_directory) -> str: return subprocess.check_output(['git', 'describe', '--tags', '--always'], cwd=working_directory).decode('ascii').strip() def get_commit_date(working_directory) -> str: return subprocess.check_output(['git', 'show', '-s', '--format=%ci'], cwd=working_directory).decode('ascii').strip() def get_git_abbrev(working_directory) -> str: return subprocess.check_output(['git', 'rev-parse', '--abbrev-ref', 'HEAD'], cwd=working_directory).decode('ascii').strip() def get_ros2_for_unity_root_path() -> pathlib.Path: return pathlib.Path(__file__).parents[2] def get_ros2_for_unity_path() -> pathlib.Path: return pathlib.Path(__file__).parents[1].joinpath("Ros2ForUnity") def get_ros2cs_path() -> pathlib.Path: return pathlib.Path(__file__).parents[1].joinpath("ros2cs") def get_ros2_path() -> pathlib.Path: return get_ros2cs_path().joinpath("src").joinpath("ros2").joinpath("rcl_interfaces") def get_ros2_version() -> str: return os.environ.get("ROS_DISTRO", "unknown") ros2_for_unity = ET.Element("ros2_for_unity") ET.SubElement(ros2_for_unity, "ros2").text = get_ros2_version() ros2_for_unity_version = ET.SubElement(ros2_for_unity, "version") ET.SubElement(ros2_for_unity_version, "sha").text = get_git_commit(get_ros2_for_unity_root_path()) ET.SubElement(ros2_for_unity_version, "desc").text = get_git_description(get_ros2_for_unity_root_path()) ET.SubElement(ros2_for_unity_version, "date").text = get_commit_date(get_ros2_for_unity_root_path()) ros2_cs = ET.Element("ros2cs") ET.SubElement(ros2_cs, "ros2").text = get_ros2_version() ros2_cs_version = ET.SubElement(ros2_cs, "version") ET.SubElement(ros2_cs_version, "sha").text = get_git_commit(get_ros2cs_path()) ET.SubElement(ros2_cs_version, "desc").text = get_git_description(get_ros2cs_path()) ET.SubElement(ros2_cs_version, "date").text = get_commit_date(get_ros2cs_path()) ET.SubElement(ros2_cs, "standalone").text = str(int(args.standalone)) rf2u_xmlstr = minidom.parseString(ET.tostring(ros2_for_unity)).toprettyxml(indent=" ") metadata_rf2u_file = get_ros2_for_unity_path().joinpath("metadata_ros2_for_unity.xml") with open(str(metadata_rf2u_file), "w") as f: f.write(rf2u_xmlstr) r2cs_xmlstr = minidom.parseString(ET.tostring(ros2_cs)).toprettyxml(indent=" ") metadata_r2cs_file = get_ros2_for_unity_path().joinpath("metadata_ros2cs.xml") with open(str(metadata_r2cs_file), "w") as f: f.write(r2cs_xmlstr)