diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..c53b622 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,37 @@ +--- +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 + +**Console logs / stack traces** +Please wrap in [triple backticks (```)](https://help.github.com/en/articles/creating-and-highlighting-code-blocks) to make it easier to read. + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots or videos to help explain your problem. + +**Environment (please complete the following information, where applicable):** +- Unity Version: [e.g. Unity 2020.2.0f1] +- Unity machine OS + version: [e.g. Windows 10] +- ROS machine OS + version: [e.g. Ubuntu 18.04, ROS Noetic] +- ROS–Unity communication: [e.g. Docker] +- Branch or version: [e.g. v0.2.0] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..bbcbbe7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +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. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..6c88fde --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,34 @@ +## Proposed change(s) + +Describe the changes made in this PR. + +### Useful links (GitHub issues, JIRA tickets, forum threads, etc.) + +Provide any relevant links here. + +### Types of change(s) + +- [ ] Bug fix +- [ ] New feature +- [ ] Code refactor +- [ ] Documentation update +- [ ] Other (please describe) + +## Testing and Verification + +Please describe the tests that you ran to verify your changes. Please also provide instructions, ROS packages, and Unity project files as appropriate so we can reproduce the test environment. + +### Test Configuration: +- Unity Version: [e.g. Unity 2020.2.0f1] +- Unity machine OS + version: [e.g. Windows 10] +- ROS machine OS + version: [e.g. Ubuntu 18.04, ROS Noetic] +- ROS–Unity communication: [e.g. Docker] + +## Checklist +- [ ] Ensured this PR is up-to-date with the `dev` branch +- [ ] Created this PR to target the `dev` branch +- [ ] Followed the style guidelines as described in the [Contribution Guidelines](../CONTRIBUTING.md) +- [ ] Added tests that prove my fix is effective or that my feature works +- [ ] Updated the documentation as appropriate + +## Other comments \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..a7a4c57 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,74 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers 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, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at [unity-robotics@unity3d.com](mailto:unity-robotics@unity3d.com). All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 1.4, available at +https://www.contributor-covenant.org/version/1/4/code-of-conduct/ + +[homepage]: https://www.contributor-covenant.org \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..0f7253f --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,64 @@ +# Contribution Guidelines + +Thank you for your interest in contributing to Unity Robotics! To facilitate your +contributions, we've outlined a brief set of guidelines to ensure that your extensions +can be easily integrated. + +## Communication + +First, please read through our +[code of conduct](CODE_OF_CONDUCT.md), +as we expect all our contributors to follow it. + +Second, before starting on a project that you intend to contribute to any of our +Unity Robotics packages or tutorials, we **strongly** recommend posting on the repository's +[Issues page](https://github.com/Unity-Technologies/ROS-TCP-Endpoint/issues) and +briefly outlining the changes you plan to make. This will enable us to provide +some context that may be helpful for you. This could range from advice and +feedback on how to optimally perform your changes or reasons for not doing it. + +## Git Branches + +The `main` branch corresponds to the most recent stable version of the project. The `dev` branch +contains changes that are staged to be merged into `main` as the team sees fit. + +When contributing to the project, please make sure that your Pull Request (PR) +does the following: + +- Is up-to-date with and targets the `dev` branch +- Contains a detailed description of the changes performed +- Has corresponding changes to documentation, unit tests and sample environments (if + applicable) +- Contains a summary of the tests performed to validate your changes +- Links to issue numbers that the PR resolves (if any) + + + +## Code style + +All Python code should follow the [PEP 8 style guidelines](https://pep8.org/). + +All C# code should follow the [Microsoft C# Coding Conventions](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/inside-a-program/coding-conventions). +Additionally, the [Unity Coding package](https://docs.unity3d.com/Packages/com.unity.coding@0.1/manual/index.html) +can be used to format, encode, and lint your code according to the standard Unity +development conventions. Be aware that these Unity conventions will supersede the +Microsoft C# Coding Conventions where applicable. + +Please note that even if the code you are changing does not adhere to these guidelines, +we expect your submissions to follow these conventions. + +## Contributor License Agreements + +When you open a pull request, you will be asked to acknowledge our Contributor +License Agreement. We allow both individual contributions and contributions made +on behalf of companies. We use an open source tool called CLA assistant. If you +have any questions on our CLA, please +[submit an issue](https://github.com/Unity-Technologies/ROS-TCP-Endpoint/issues) or +email us at [unity-robotics@unity3d.com](mailto:unity-robotics@unity3d.com). + +## Contribution review + +Once you have a change ready following the above ground rules, simply make a +pull request in GitHub. \ No newline at end of file diff --git a/LICENSE b/LICENSE index d9a10c0..7507bc9 100644 --- a/LICENSE +++ b/LICENSE @@ -174,3 +174,17 @@ of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS + + Copyright 2020 Unity Technologies + + 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. diff --git a/README.md b/README.md index 2e44191..6af6ac0 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,25 @@ # ROS TCP Endpoint +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) + +## Introduction + [ROS](https://www.ros.org/) package used to create an endpoint to accept ROS messages sent from a Unity scene using the [ROS TCP Connector](https://github.com/Unity-Technologies/ROS-TCP-Connector) scripts. -Instructions and examples on how to use this ROS package can be found on the [Unity Robotics Hub](https://github.com/Unity-Technologies/Unity-Robotics-Hub/blob/master/tutorials/ros_unity_integration/README.md) repository. \ No newline at end of file +Instructions and examples on how to use this ROS package can be found on the [Unity Robotics Hub](https://github.com/Unity-Technologies/Unity-Robotics-Hub/blob/master/tutorials/ros_unity_integration/README.md) repository. + +## Community and Feedback + +The Unity Robotics projects are open-source and we encourage and welcome contributions. +If you wish to contribute, be sure to review our [contribution guidelines](CONTRIBUTING.md) +and [code of conduct](CODE_OF_CONDUCT.md). + +## Support +For general questions, feedback, or feature requests, connect directly with the +Robotics team at [unity-robotics@unity3d.com](mailto:unity-robotics@unity3d.com). + +For bugs or other issues, please file a GitHub issue and the Robotics team will +investigate the issue as soon as possible. + +## License +[Apache License 2.0](LICENSE) \ No newline at end of file diff --git a/config/params.yaml b/config/params.yaml new file mode 100644 index 0000000..50aaa22 --- /dev/null +++ b/config/params.yaml @@ -0,0 +1 @@ +ROS_IP: 127.0.0.1 \ No newline at end of file diff --git a/launch/endpoint.launch b/launch/endpoint.launch new file mode 100644 index 0000000..f5d7788 --- /dev/null +++ b/launch/endpoint.launch @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/package.xml b/package.xml index d2045bc..919cad9 100644 --- a/package.xml +++ b/package.xml @@ -1,7 +1,7 @@ ros_tcp_endpoint - 0.2.0 + 0.3.0 Acts as the bridge between Unity messages sent via Websocket and ROS messages. Unity Robotics diff --git a/src/ros_tcp_endpoint/__init__.py b/src/ros_tcp_endpoint/__init__.py index 356575b..adb5b02 100644 --- a/src/ros_tcp_endpoint/__init__.py +++ b/src/ros_tcp_endpoint/__init__.py @@ -16,4 +16,4 @@ from .subscriber import RosSubscriber from .service import RosService from .server import TcpServer - +from .unity_service import UnityService diff --git a/src/ros_tcp_endpoint/client.py b/src/ros_tcp_endpoint/client.py index 7d19048..c54acb0 100644 --- a/src/ros_tcp_endpoint/client.py +++ b/src/ros_tcp_endpoint/client.py @@ -102,7 +102,7 @@ def read_message(conn): data += packet - if not data: + if full_message_size > 0 and not data: print("No data for a message size of {}, breaking!".format(full_message_size)) return @@ -164,6 +164,11 @@ def run(self): response_message = self.serialize_message(destination, response) self.conn.send(response_message) return + elif destination == '__topic_list': + response = self.tcp_server.topic_list(data) + response_message = self.serialize_message(destination, response) + self.conn.send(response_message) + return elif destination not in self.tcp_server.source_destination_dict.keys(): error_msg = "Topic/service destination '{}' is not defined! Known topics are: {} "\ .format(destination, self.tcp_server.source_destination_dict.keys()) diff --git a/src/ros_tcp_endpoint/server.py b/src/ros_tcp_endpoint/server.py index b1b7d2a..ca45bf0 100644 --- a/src/ros_tcp_endpoint/server.py +++ b/src/ros_tcp_endpoint/server.py @@ -25,13 +25,14 @@ from .subscriber import RosSubscriber from .publisher import RosPublisher from ros_tcp_endpoint.msg import RosUnitySysCommand +from ros_tcp_endpoint.srv import RosUnityTopicListResponse class TcpServer: """ Initializes ROS node and TCP server. """ - def __init__(self, node_name, buffer_size=1024, connections=10, tcp_ip="", tcp_port=-1, timeout=10): + def __init__(self, node_name, buffer_size=1024, connections=10, tcp_ip="", tcp_port=-1, timeout_on_connect=10, timeout_on_send=0.8, timeout_on_idle=10): """ Initializes ROS node and class variables. @@ -52,7 +53,7 @@ def __init__(self, node_name, buffer_size=1024, connections=10, tcp_ip="", tcp_p unity_machine_ip = rospy.get_param("/UNITY_IP", '') unity_machine_port = rospy.get_param("/UNITY_SERVER_PORT", 5005) - self.unity_tcp_sender = UnityTcpSender(unity_machine_ip, unity_machine_port, timeout) + self.unity_tcp_sender = UnityTcpSender(unity_machine_ip, unity_machine_port, timeout_on_connect, timeout_on_send, timeout_on_idle) self.node_name = node_name self.source_destination_dict = {} @@ -96,6 +97,9 @@ def send_unity_message(self, topic, message): def send_unity_service(self, topic, service_class, request): return self.unity_tcp_sender.send_unity_service(topic, service_class, request) + def topic_list(self, data): + return RosUnityTopicListResponse(self.source_destination_dict.keys()) + def handle_syscommand(self, data): message = RosUnitySysCommand().deserialize(data) function = getattr(self.syscommands, message.command) diff --git a/src/ros_tcp_endpoint/tcp_sender.py b/src/ros_tcp_endpoint/tcp_sender.py index de6ad3b..018d1e2 100644 --- a/src/ros_tcp_endpoint/tcp_sender.py +++ b/src/ros_tcp_endpoint/tcp_sender.py @@ -14,21 +14,36 @@ import rospy import socket +import time +import threading +import struct from .client import ClientThread from ros_tcp_endpoint.msg import RosUnityError from ros_tcp_endpoint.srv import UnityHandshake, UnityHandshakeResponse +# queue module was renamed between python 2 and 3 +try: + from queue import Queue +except: + from Queue import Queue class UnityTcpSender: """ Connects and sends messages to the server on the Unity side. """ - def __init__(self, unity_ip, unity_port, timeout): + def __init__(self, unity_ip, unity_port, timeout_on_connect, timeout_on_send, timeout_on_idle): self.unity_ip = unity_ip self.unity_port = unity_port # if we have a valid IP at this point, it was overridden locally so always use that self.ip_is_overridden = (self.unity_ip != '') - self.timeout = timeout + self.timeout_on_connect = timeout_on_connect + self.timeout_on_send = timeout_on_send + self.timeout_on_idle = timeout_on_idle + self.queue = Queue() + sender_thread = threading.Thread(target=self.sender_loop) + # Exit the server thread when the main thread terminates + sender_thread.daemon = True + sender_thread.start() def handshake(self, incoming_ip, data): message = UnityHandshake._request_class().deserialize(data) @@ -53,16 +68,7 @@ def send_unity_message(self, topic, message): return serialized_message = ClientThread.serialize_message(topic, message) - - try: - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.settimeout(self.timeout) - s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - s.connect((self.unity_ip, self.unity_port)) - s.sendall(serialized_message) - s.close() - except Exception as e: - rospy.loginfo("Exception {}".format(e)) + self.queue.put(serialized_message) def send_unity_service(self, topic, service_class, request): if self.unity_ip == '': @@ -70,12 +76,13 @@ def send_unity_service(self, topic, service_class, request): return serialized_message = ClientThread.serialize_message(topic, request) - + try: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.settimeout(self.timeout) + s.settimeout(self.timeout_on_connect) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.connect((self.unity_ip, self.unity_port)) + s.settimeout(self.timeout_on_send) s.sendall(serialized_message) destination, data = ClientThread.read_message(s) @@ -86,4 +93,34 @@ def send_unity_service(self, topic, service_class, request): return response except Exception as e: rospy.loginfo("Exception {}".format(e)) - \ No newline at end of file + + def sender_loop(self): + s = None + idletimeout = 0 + + while True: + item = self.queue.get() + + retries = 0 + while retries < 3: + retries+=1 + + try: + if time.time() > idletimeout: + if s != None: + s.close() + + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.settimeout(self.timeout_on_connect) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.connect((self.unity_ip, self.unity_port)) + s.settimeout(self.timeout_on_send) + + s.sendall(item) + idletimeout = time.time() + self.timeout_on_idle + break # sent ok. break the retries loop + except socket.timeout: + idletimeout = 0 # assume the connection has been closed, force a reconnect + except Exception as e: + rospy.loginfo("Exception {}".format(e)) + idletimeout = 0 diff --git a/srv/RosUnityTopicList.srv b/srv/RosUnityTopicList.srv new file mode 100644 index 0000000..895f4a6 --- /dev/null +++ b/srv/RosUnityTopicList.srv @@ -0,0 +1,2 @@ +--- +string[] topics \ No newline at end of file