Skip to content

Commit f978d94

Browse files
Merge v0.5.0 (#84)
1 parent 18612dc commit f978d94

13 files changed

+190
-112
lines changed

CHANGELOG.md

+18
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,24 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a
2020

2121
### Fixed
2222

23+
## [0.5.0] - 2021-07-15
24+
25+
### Upgrade Notes
26+
27+
Upgrade the ROS communication to support ROS2 with Unity
28+
29+
### Known Issues
30+
31+
### Added
32+
33+
### Changed
34+
35+
### Deprecated
36+
37+
### Removed
38+
39+
### Fixed
40+
2341
## [0.4.0] - 2021-05-27
2442

2543
Note: the logs only reflects the changes from version 0.3.0

CMakeLists.txt

+1-14
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,8 @@ project(ros_tcp_endpoint)
44
find_package(catkin REQUIRED COMPONENTS
55
rospy
66
std_msgs
7-
message_generation
87
)
98

109
catkin_python_setup()
1110

12-
add_message_files(FILES
13-
RosUnityError.msg
14-
RosUnitySrvMessage.msg
15-
RosUnitySysCommand.msg
16-
)
17-
18-
add_service_files(FILES
19-
RosUnityTopicList.srv
20-
)
21-
22-
generate_messages(DEPENDENCIES std_msgs)
23-
24-
catkin_package(CATKIN_DEPENDS message_runtime)
11+
catkin_package(CATKIN_DEPENDS message_runtime )

msg/RosUnityError.msg

-1
This file was deleted.

msg/RosUnitySrvMessage.msg

-4
This file was deleted.

msg/RosUnitySysCommand.msg

-2
This file was deleted.

package.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0"?>
22
<package format="2">
33
<name>ros_tcp_endpoint</name>
4-
<version>0.4.0</version>
4+
<version>0.5.0</version>
55
<description>Acts as the bridge between Unity messages sent via Websocket and ROS messages.</description>
66

77
<maintainer email="unity-robotics@unity3d.com">Unity Robotics</maintainer>

src/ros_tcp_endpoint/client.py

+56-42
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@
1818
from io import BytesIO
1919

2020
import threading
21+
import json
2122

2223
from .exceptions import TopicOrServiceNameDoesNotExistError
23-
from ros_tcp_endpoint.msg import RosUnitySrvMessage
2424

2525

2626
class ClientThread(threading.Thread):
@@ -145,6 +145,47 @@ def serialize_message(destination, message):
145145

146146
return serialized_message
147147

148+
@staticmethod
149+
def serialize_command(command, params):
150+
cmd_bytes = command.encode("utf-8")
151+
cmd_length = len(cmd_bytes)
152+
cmd_info = struct.pack("<I%ss" % cmd_length, cmd_length, cmd_bytes)
153+
154+
json_bytes = json.dumps(params.__dict__).encode("utf-8")
155+
json_length = len(json_bytes)
156+
json_info = struct.pack("<I%ss" % json_length, json_length, json_bytes)
157+
158+
return cmd_info + json_info
159+
160+
def send_ros_service_request(self, srv_id, destination, data):
161+
if destination not in self.tcp_server.source_destination_dict.keys():
162+
error_msg = "Service destination '{}' is not registered! Known topics are: {} ".format(
163+
destination, self.tcp_server.source_destination_dict.keys()
164+
)
165+
self.tcp_server.send_unity_error(error_msg)
166+
rospy.logerr(error_msg)
167+
# TODO: send a response to Unity anyway?
168+
return
169+
else:
170+
ros_communicator = self.tcp_server.source_destination_dict[destination]
171+
service_thread = threading.Thread(
172+
target=self.service_call_thread, args=(srv_id, destination, data, ros_communicator)
173+
)
174+
service_thread.daemon = True
175+
service_thread.start()
176+
177+
def service_call_thread(self, srv_id, destination, data, ros_communicator):
178+
response = ros_communicator.send(data)
179+
180+
if not response:
181+
error_msg = "No response data from service '{}'!".format(destination)
182+
self.tcp_server.send_unity_error(error_msg)
183+
rospy.logerr(error_msg)
184+
# TODO: send a response to Unity anyway?
185+
return
186+
187+
self.tcp_server.unity_tcp_sender.send_ros_service_response(srv_id, destination, response)
188+
148189
def run(self):
149190
"""
150191
Read a message and determine where to send it based on the source_destination_dict
@@ -167,50 +208,23 @@ def run(self):
167208
while not halt_event.is_set():
168209
destination, data = self.read_message(self.conn)
169210

170-
if destination == "":
211+
if self.tcp_server.pending_srv_id is not None:
212+
# if we've been told that the next message will be a service request/response, process it as such
213+
if self.tcp_server.pending_srv_is_request:
214+
self.send_ros_service_request(
215+
self.tcp_server.pending_srv_id, destination, data
216+
)
217+
else:
218+
self.tcp_server.send_unity_service_response(
219+
self.tcp_server.pending_srv_id, data
220+
)
221+
self.tcp_server.pending_srv_id = None
222+
elif destination == "":
171223
# ignore this keepalive message, listen for more
172224
pass
173-
elif destination == "__syscommand":
225+
elif destination.startswith("__"):
174226
# handle a system command, such as registering new topics
175-
self.tcp_server.handle_syscommand(data)
176-
elif destination == "__srv":
177-
# handle a ros service message request, or a unity service message response
178-
srv_message = RosUnitySrvMessage().deserialize(data)
179-
if not srv_message.is_request:
180-
self.tcp_server.send_unity_service_response(
181-
srv_message.srv_id, srv_message.payload
182-
)
183-
continue
184-
elif srv_message.topic == "__topic_list":
185-
response = self.tcp_server.topic_list(data)
186-
elif srv_message.topic not in self.tcp_server.source_destination_dict.keys():
187-
error_msg = "Service destination '{}' is not registered! Known topics are: {} ".format(
188-
srv_message.topic, self.tcp_server.source_destination_dict.keys()
189-
)
190-
self.tcp_server.send_unity_error(error_msg)
191-
rospy.logerr(error_msg)
192-
# TODO: send a response to Unity anyway?
193-
continue
194-
else:
195-
ros_communicator = self.tcp_server.source_destination_dict[
196-
srv_message.topic
197-
]
198-
response = ros_communicator.send(srv_message.payload)
199-
if not response:
200-
error_msg = "No response data from service '{}'!".format(
201-
srv_message.topic
202-
)
203-
self.tcp_server.send_unity_error(error_msg)
204-
rospy.logerr(error_msg)
205-
# TODO: send a response to Unity anyway?
206-
continue
207-
208-
serial_response = BytesIO()
209-
response.serialize(serial_response)
210-
response_message = RosUnitySrvMessage(
211-
srv_message.srv_id, False, "", serial_response.getvalue()
212-
)
213-
self.tcp_server.unity_tcp_sender.send_unity_message("__srv", response_message)
227+
self.tcp_server.handle_syscommand(destination, data)
214228
elif destination in self.tcp_server.source_destination_dict:
215229
ros_communicator = self.tcp_server.source_destination_dict[destination]
216230
response = ros_communicator.send(data)

src/ros_tcp_endpoint/default_server_endpoint.py

100644100755
File mode changed.

src/ros_tcp_endpoint/server.py

+20-14
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@
2626
from .publisher import RosPublisher
2727
from .service import RosService
2828
from .unity_service import UnityService
29-
from ros_tcp_endpoint.msg import RosUnitySysCommand
30-
from ros_tcp_endpoint.srv import RosUnityTopicListResponse
3129

3230

3331
class TcpServer:
@@ -61,6 +59,8 @@ def __init__(self, node_name, buffer_size=1024, connections=2, tcp_ip="", tcp_po
6159
self.buffer_size = buffer_size
6260
self.connections = connections
6361
self.syscommands = SysCommands(self)
62+
self.pending_srv_id = None
63+
self.pending_srv_is_request = False
6464

6565
def start(self, source_destination_dict=None):
6666
if source_destination_dict is not None:
@@ -96,24 +96,19 @@ def send_unity_message(self, topic, message):
9696
self.unity_tcp_sender.send_unity_message(topic, message)
9797

9898
def send_unity_service(self, topic, service_class, request):
99-
return self.unity_tcp_sender.send_unity_service(topic, service_class, request)
99+
return self.unity_tcp_sender.send_unity_service_request(topic, service_class, request)
100100

101101
def send_unity_service_response(self, srv_id, data):
102102
self.unity_tcp_sender.send_unity_service_response(srv_id, data)
103103

104-
def topic_list(self, data):
105-
return RosUnityTopicListResponse(self.source_destination_dict.keys())
106-
107-
def handle_syscommand(self, data):
108-
message = RosUnitySysCommand().deserialize(data)
109-
function = getattr(self.syscommands, message.command)
104+
def handle_syscommand(self, topic, data):
105+
function = getattr(self.syscommands, topic[2:])
110106
if function is None:
111-
self.send_unity_error(
112-
"Don't understand SysCommand.'{}'({})".format(message.command, message.params_json)
113-
)
107+
self.send_unity_error("Don't understand SysCommand.'{}'".format(topic))
114108
return
115109
else:
116-
params = json.loads(message.params_json)
110+
message_json = data.decode("utf-8")
111+
params = json.loads(message_json)
117112
function(**params)
118113

119114

@@ -220,9 +215,20 @@ def unity_service(self, topic, message_name):
220215
self.tcp_server.source_destination_dict[topic].unregister()
221216

222217
self.tcp_server.source_destination_dict[topic] = UnityService(
223-
topic.encode("ascii"), message_class, self.tcp_server
218+
topic, message_class, self.tcp_server
224219
)
225220

221+
def response(self, srv_id): # the next message is a service response
222+
self.tcp_server.pending_srv_id = srv_id
223+
self.tcp_server.pending_srv_is_request = False
224+
225+
def request(self, srv_id): # the next message is a service request
226+
self.tcp_server.pending_srv_id = srv_id
227+
self.tcp_server.pending_srv_is_request = True
228+
229+
def topic_list(self):
230+
self.tcp_server.unity_tcp_sender.send_topic_list()
231+
226232

227233
def resolve_message_name(name, extension="msg"):
228234
try:

src/ros_tcp_endpoint/tcp_sender.py

+61-15
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@
1919
import struct
2020
from .client import ClientThread
2121
from .thread_pauser import ThreadPauser
22-
from ros_tcp_endpoint.msg import RosUnityError
23-
from ros_tcp_endpoint.msg import RosUnitySrvMessage
2422
from io import BytesIO
2523

2624
# queue module was renamed between python 2 and 3
@@ -34,7 +32,7 @@
3432

3533
class UnityTcpSender:
3634
"""
37-
Connects and sends messages to the server on the Unity side.
35+
Sends messages to Unity.
3836
"""
3937

4038
def __init__(self):
@@ -51,29 +49,55 @@ def __init__(self):
5149
self.srv_lock = threading.Lock()
5250
self.services_waiting = {}
5351

54-
def send_unity_error(self, error):
55-
self.send_unity_message("__error", RosUnityError(error))
52+
def send_unity_info(self, text):
53+
if self.queue is not None:
54+
command = SysCommand_Log()
55+
command.text = text
56+
serialized_bytes = ClientThread.serialize_command("__log", command)
57+
self.queue.put(serialized_bytes)
58+
59+
def send_unity_warning(self, text):
60+
if self.queue is not None:
61+
command = SysCommand_Log()
62+
command.text = text
63+
serialized_bytes = ClientThread.serialize_command("__warn", command)
64+
self.queue.put(serialized_bytes)
65+
66+
def send_unity_error(self, text):
67+
if self.queue is not None:
68+
command = SysCommand_Log()
69+
command.text = text
70+
serialized_bytes = ClientThread.serialize_command("__error", command)
71+
self.queue.put(serialized_bytes)
72+
73+
def send_ros_service_response(self, srv_id, destination, response):
74+
if self.queue is not None:
75+
command = SysCommand_Service()
76+
command.srv_id = srv_id
77+
serialized_bytes = ClientThread.serialize_command("__response", command)
78+
self.queue.put(serialized_bytes)
79+
self.send_unity_message(destination, response)
5680

5781
def send_unity_message(self, topic, message):
58-
serialized_message = ClientThread.serialize_message(topic, message)
5982
if self.queue is not None:
83+
serialized_message = ClientThread.serialize_message(topic, message)
6084
self.queue.put(serialized_message)
6185

62-
def send_unity_service(self, topic, service_class, request):
63-
request_bytes = BytesIO()
64-
request.serialize(request_bytes)
65-
thread_pauser = ThreadPauser()
86+
def send_unity_service_request(self, topic, service_class, request):
87+
if self.queue is None:
88+
return None
6689

90+
thread_pauser = ThreadPauser()
6791
with self.srv_lock:
6892
srv_id = self.next_srv_id
6993
self.next_srv_id += 1
7094
self.services_waiting[srv_id] = thread_pauser
7195

72-
payload = request_bytes.getvalue()
73-
srv_message = RosUnitySrvMessage(srv_id, True, topic, payload)
74-
serialized_message = ClientThread.serialize_message("__srv", srv_message)
75-
if self.queue is not None:
76-
self.queue.put(serialized_message)
96+
command = SysCommand_Service()
97+
command.srv_id = srv_id
98+
serialized_bytes = ClientThread.serialize_command("__request", command)
99+
self.queue.put(serialized_bytes)
100+
self.send_unity_message(topic, request)
77101

78102
# rospy starts a new thread for each service request,
79103
# so it won't break anything if we sleep now while waiting for the response
@@ -90,6 +114,15 @@ def send_unity_service_response(self, srv_id, data):
90114

91115
thread_pauser.resume_with_result(data)
92116

117+
def send_topic_list(self):
118+
if self.queue is not None:
119+
topic_list = SysCommand_TopicsResponse()
120+
topics_and_types = rospy.get_published_topics()
121+
topic_list.topics = [item[0] for item in topics_and_types]
122+
topic_list.types = [item[1] for item in topics_and_types]
123+
serialized_bytes = ClientThread.serialize_command("__topic_list", topic_list)
124+
self.queue.put(serialized_bytes)
125+
93126
def start_sender(self, conn, halt_event):
94127
sender_thread = threading.Thread(
95128
target=self.sender_loop, args=(conn, self.sender_id, halt_event)
@@ -130,3 +163,16 @@ def sender_loop(self, conn, tid, halt_event):
130163
with self.queue_lock:
131164
if self.queue is local_queue:
132165
self.queue = None
166+
167+
168+
class SysCommand_Log:
169+
text = ""
170+
171+
172+
class SysCommand_Service:
173+
srv_id = 0
174+
175+
176+
class SysCommand_TopicsResponse:
177+
topics = []
178+
types = []

srv/RosUnityTopicList.srv

-2
This file was deleted.

0 commit comments

Comments
 (0)