Skip to content

Commit 68ade1c

Browse files
Unity server update (#8)
ROSConnection runs a single listen server, auto determine IP
1 parent 1b40170 commit 68ade1c

File tree

9 files changed

+135
-40
lines changed

9 files changed

+135
-40
lines changed

tcp_endpoint/CMakeLists.txt

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ find_package(catkin REQUIRED COMPONENTS
1111
roscpp
1212
rospy
1313
std_msgs
14+
message_generation
1415
)
1516

1617
## System dependencies are found with CMake's conventions
@@ -47,18 +48,16 @@ catkin_python_setup()
4748
## * add every package in MSG_DEP_SET to generate_messages(DEPENDENCIES ...)
4849

4950
## Generate messages in the 'msg' folder
50-
# add_message_files(
51-
# FILES
52-
# Message1.msg
53-
# Message2.msg
54-
# )
51+
add_message_files(
52+
FILES
53+
RosUnityError.msg
54+
)
5555

5656
## Generate services in the 'srv' folder
57-
# add_service_files(
58-
# FILES
59-
# Service1.srv
60-
# Service2.srv
61-
# )
57+
add_service_files(
58+
FILES
59+
RosUnityHandshake.srv
60+
)
6261

6362
## Generate actions in the 'action' folder
6463
# add_action_files(
@@ -68,10 +67,10 @@ catkin_python_setup()
6867
# )
6968

7069
## Generate added messages and services with any dependencies listed here
71-
# generate_messages(
72-
# DEPENDENCIES
73-
# std_msgs
74-
# )
70+
generate_messages(
71+
DEPENDENCIES
72+
std_msgs
73+
)
7574

7675
################################################
7776
## Declare ROS dynamic reconfigure parameters ##
@@ -106,6 +105,7 @@ catkin_package(
106105
# INCLUDE_DIRS include
107106
# LIBRARIES tcp_endpoint
108107
# CATKIN_DEPENDS roscpp rospy std_msgs
108+
CATKIN_DEPENDS message_runtime
109109
# DEPENDS system_lib
110110
)
111111

tcp_endpoint/msg/RosUnityError.msg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
string message

tcp_endpoint/package.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
<!-- Use buildtool_depend for build tool packages: -->
4444
<!-- <buildtool_depend>catkin</buildtool_depend> -->
4545
<!-- Use exec_depend for packages you need at runtime: -->
46-
<!-- <exec_depend>message_runtime</exec_depend> -->
46+
<exec_depend>message_runtime</exec_depend>
4747
<!-- Use test_depend for packages you need only for testing: -->
4848
<!-- <test_depend>gtest</test_depend> -->
4949
<!-- Use doc_depend for packages you need only for building documentation: -->

tcp_endpoint/src/tcp_endpoint/RosSubscriber.py

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class RosSubscriber(RosReceiver):
1212
Class to send messages outside of ROS network
1313
"""
1414

15-
def __init__(self, topic, message_class, server_ip, server_port, queue_size=10):
15+
def __init__(self, topic, message_class, tcp_server, queue_size=10):
1616
"""
1717
1818
Args:
@@ -23,8 +23,7 @@ def __init__(self, topic, message_class, server_ip, server_port, queue_size=10):
2323
self.topic = topic
2424
self.node_name = "{}_subsciber".format(topic)
2525
self.msg = message_class
26-
self.tcp_id = server_ip
27-
self.tcp_port = server_port
26+
self.tcp_server = tcp_server
2827
self.queue_size = queue_size
2928

3029
# Start Subscriber listener function
@@ -41,16 +40,7 @@ def send(self, data):
4140
4241
"""
4342

44-
serialized_message = ClientThread.serialize_message(self.topic, data)
45-
46-
try:
47-
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
48-
s.connect((self.tcp_id, self.tcp_port))
49-
s.send(serialized_message)
50-
s.close()
51-
except Exception as e:
52-
rospy.loginfo("Exception {}".format(e))
53-
43+
self.tcp_server.send_unity_message(self.topic, data)
5444
return self.msg
5545

5646
def listener(self):

tcp_endpoint/src/tcp_endpoint/RosTCPClientThread.py

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
#!/usr/bin/env python
22

33
import struct
4+
import socket
5+
import rospy
46
from io import BytesIO
57

68
from threading import Thread
79

810
from tcp_endpoint.TCPEndpointExceptions import TopicOrServiceNameDoesNotExistError
911

10-
1112
class ClientThread(Thread):
1213
"""
1314
Thread class to read all data from a connection and pass along the data to the
1415
desired source.
1516
"""
16-
def __init__(self, conn, source_destination_dict):
17+
def __init__(self, conn, tcp_server, incoming_ip, incoming_port):
1718
"""
1819
Set class variables
1920
Args:
@@ -22,7 +23,9 @@ def __init__(self, conn, source_destination_dict):
2223
"""
2324
Thread.__init__(self)
2425
self.conn = conn
25-
self.source_destination_dict = source_destination_dict
26+
self.tcp_server = tcp_server
27+
self.incoming_ip = incoming_ip
28+
self.incoming_port = incoming_port
2629

2730
def read_int32(self):
2831
"""
@@ -130,14 +133,26 @@ def run(self):
130133
print("No data for a message size of {}, breaking!".format(full_message_size))
131134
return
132135

133-
if destination not in self.source_destination_dict.keys():
134-
error_msg = "Topic/Service destination '{}' does not exist in source destination dictionary {} "\
135-
.format(destination, self.source_destination_dict.keys())
136+
if destination.startswith('__'):
137+
if destination not in self.tcp_server.special_destination_dict.keys():
138+
error_msg = "System message '{}' is undefined! Known system calls are: {}"\
139+
.format(destination, self.tcp_server.special_destination_dict.keys())
140+
self.conn.close()
141+
self.tcp_server.send_unity_error(error_msg)
142+
raise TopicOrServiceNameDoesNotExistError(error_msg)
143+
else:
144+
ros_communicator = self.tcp_server.special_destination_dict[destination]
145+
ros_communicator.set_incoming_ip(self.incoming_ip)
146+
elif destination not in self.tcp_server.source_destination_dict.keys():
147+
error_msg = "Topic/service destination '{}' is not defined! Known topics are: {} "\
148+
.format(destination, self.tcp_server.source_destination_dict.keys())
136149
self.conn.close()
150+
self.tcp_server.send_unity_error(error_msg)
137151
raise TopicOrServiceNameDoesNotExistError(error_msg)
152+
else:
153+
ros_communicator = self.tcp_server.source_destination_dict[destination]
138154

139155
try:
140-
ros_communicator = self.source_destination_dict[destination]
141156
response = ros_communicator.send(data)
142157

143158
# Responses only exist for services

tcp_endpoint/src/tcp_endpoint/RosTCPServer.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@
33
import rospy
44
import socket
55

6+
from tcp_endpoint.UnityTCPSender import UnityTCPSender
67
from tcp_endpoint.RosTCPClientThread import ClientThread
8+
from tcp_endpoint.RosUnityHandshakeService import RosUnityHandshakeService
79

810
class TCPServer:
911
"""
1012
Initializes ROS node and TCP server.
1113
"""
1214

13-
def __init__(self, tcp_ip, tcp_port, node_name, source_destination_dict, buffer_size=1024, connections=10):
15+
def __init__(self, node_name, buffer_size=1024, connections=10):
1416
"""
1517
Initializes ROS node and class variables.
1618
@@ -22,10 +24,18 @@ def __init__(self, tcp_ip, tcp_port, node_name, source_destination_dict, buffer_
2224
buffer_size: The read buffer size used when reading from a socket
2325
connections: Max number of queued connections. See Python Socket documentation
2426
"""
25-
self.tcp_ip = tcp_ip
26-
self.tcp_port = tcp_port
27+
self.tcp_ip = rospy.get_param("/ROS_IP")
28+
self.tcp_port = rospy.get_param("/ROS_TCP_PORT", 10000)
29+
30+
unity_machine_ip = rospy.get_param("/UNITY_IP", '')
31+
unity_machine_port = rospy.get_param("/UNITY_SERVER_PORT", 5005)
32+
self.unity_tcp_sender = UnityTCPSender(unity_machine_ip, unity_machine_port)
33+
2734
self.node_name = node_name
28-
self.source_destination_dict = source_destination_dict
35+
self.source_destination_dict = {}
36+
self.special_destination_dict = {
37+
'__handshake': RosUnityHandshakeService(self.unity_tcp_sender)
38+
}
2939
self.buffer_size = buffer_size
3040
self.connections = connections
3141

@@ -44,9 +54,15 @@ def start(self):
4454
tcp_server.listen(self.connections)
4555

4656
(conn, (ip, port)) = tcp_server.accept()
47-
new_thread = ClientThread(conn, self.source_destination_dict)
57+
new_thread = ClientThread(conn, self, ip, port)
4858
new_thread.start()
4959
threads.append(new_thread)
5060

5161
for t in threads:
5262
t.join()
63+
64+
def send_unity_error(self, error):
65+
self.unity_tcp_sender.send_unity_error(error)
66+
67+
def send_unity_message(self, topic, message):
68+
self.unity_tcp_sender.send_unity_message(topic, message)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/usr/bin/env python
2+
3+
import rospy
4+
from tcp_endpoint.srv import RosUnityHandshake, RosUnityHandshakeResponse
5+
6+
class RosUnityHandshakeService:
7+
"""
8+
Class to auto-detect Unity IP.
9+
"""
10+
def __init__(self, tcp_sender):
11+
"""
12+
Args:
13+
tcp_sender: sends messages to Unity
14+
"""
15+
self.srv_class = RosUnityHandshake._request_class()
16+
self.tcp_sender = tcp_sender
17+
self.incoming_ip = ''
18+
19+
# The client thread lets us know what the incoming IP is, so we can use it later
20+
def set_incoming_ip(self, ip):
21+
self.incoming_ip = ip
22+
23+
def send(self, data):
24+
message = self.srv_class.deserialize(data)
25+
if message.ip == '': # if the message specifies an IP, Unity has set an IP override, so use it
26+
self.tcp_sender.process_handshake(self.incoming_ip, message.port)
27+
else: # if not, just talk back to the incoming IP
28+
self.tcp_sender.process_handshake(message.ip, message.port)
29+
return RosUnityHandshakeResponse(self.tcp_sender.unity_ip)
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import rospy
2+
import socket
3+
from tcp_endpoint.RosTCPClientThread import ClientThread
4+
from tcp_endpoint.msg import RosUnityError
5+
6+
class UnityTCPSender:
7+
"""
8+
Connects and sends messages to the server on the Unity side.
9+
"""
10+
def __init__(self, unity_ip, unity_port):
11+
self.unity_ip = unity_ip
12+
self.unity_port = unity_port
13+
# if we have a valid IP at this point, it was overridden locally so always use that
14+
self.ip_is_overridden = (self.unity_ip != '')
15+
16+
def process_handshake(self, ip, port):
17+
self.unity_port = port
18+
if ip != '' and not self.ip_is_overridden:
19+
self.unity_ip = ip # hello Unity, we'll talk to you from now on
20+
print("ROS-Unity Handshake received, will connect to {}:{}".format(self.unity_ip, self.unity_port))
21+
22+
def send_unity_error(self, error):
23+
self.send_unity_message("__error", RosUnityError(error))
24+
25+
def send_unity_message(self, topic, message):
26+
if self.unity_ip == '':
27+
print("Can't send a message, no defined unity IP!".format(topic, message))
28+
return
29+
30+
serialized_message = ClientThread.serialize_message(topic, message)
31+
32+
try:
33+
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
34+
s.settimeout(2)
35+
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
36+
s.connect((self.unity_ip, self.unity_port))
37+
s.send(serialized_message)
38+
s.close()
39+
except Exception as e:
40+
rospy.loginfo("Exception {}".format(e))
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
string ip
2+
uint16 port
3+
---
4+
string ip

0 commit comments

Comments
 (0)