Suppose you want to train a reinforcement learning agent with ROS and need ROS to shutdown once the training is finished. Automatically. How can this be accomplished?
One way to do this is by starting your ROS application via a Python script (which we did here). Once the training is done, we send a kill signal from within the ROS node running the RL algorithm to the startup script. We will implement this with Python's socket module which allows us to create a socket in the startup script that will listen to incoming commands. This socket will be used by our ROS node to communicate when to shutdown ROS.
Setup from the last blog post.
Code is available on bitbucket: https://bitbucket.org/reinka/blog/src/master/ros_sockets/
Continuing where we left off last time, all we need to do is extend start_ros.py by a helper function start_socket(host, port): it will start a socket that binds to a specified host and port. For an incoming connection, it first prints out its address and listens to it until it receives a b'killall' byte. Additionally, any received data point will be printed out.
def start_socket(host, port):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind((host, port))
s.listen()
conn, addr = s.accept()
with conn:
print('Connected by', addr)
while True:
data = conn.recv(1024)
if data:
print(data)
if data == b'killall':
# Send the signal to all the process groups
print('Received killall signal.')
break
We now need to extend our main function by adding the following lines before the time.sleep(5) command:
def main(args)
# code from the last blog post
...
# add our new helper function
# create socket and listen on the port
HOST = socket.gethostbyname("localhost")
PORT = 65432
start_socket(HOST, PORT)
# code from the last blog post
time.sleep(5)
print('Killing ROS and talker node.')
os.killpg(os.getpgid(p_ros_core.pid), signal.SIGTERM)
os.killpg(os.getpgid(session_talker_node.pid), signal.SIGTERM)
...
As we can see, once the while loop breaks and the start_socket function gets escaped, all processes get killed after 5 seconds.
In a last step, we need to adapt the talker node:
New code parts are marked with # NEW:
def talker():
pub = rospy.Publisher('chatter', String, queue_size=10)
rospy.init_node('talker', anonymous=True)
rate = rospy.Rate(10) # 10hz
# NEW: connect to socket and initialize counter vars
HOST = socket.gethostbyname("localhost")
PORT = 65432
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
iteration = 0
iteration_max = 15
with tf.Session() as sess:
while not rospy.is_shutdown():
hello_str = "hello world %s" % sess.run(
tf.random.normal(shape=(1,))) # sample from standard normal
rospy.loginfo(hello_str)
pub.publish(hello_str)
# NEW: send kill signal once iteration_max is reached
iteration += 1
if iteration == iteration_max:
with s:
s.sendall(b'killall')
rate.sleep()
We are now ready to test our new setup.
Finally, we can execute our new Python script:
root@ae3bbca49d56:/notebooks/workspace $ python start_ros.py
Starting ROS
Starting TALKER_NODE
PGID ROS: 852
PGID TALKER NODE: 853
Connected by ('127.0.0.1', 50348)
b'killall'
Received killall signal.
Killing ROS and talker node.
That's it.
You can now communicate with your startup script from within any ROS node by just connecting to the socket at the specified host and port.