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?

Idea

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.

Prerequisites

Setup from the last blog post.

Solution

Adapting the main function

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.

Adapting the talker node

In a last step, we need to adapt the talker node:

  • it connects to the socket at the same host and port as in main
  • we add an iteration and iteration_max variable, which will be used for a counter to send the b'killall' command once iteration_max is reached

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.

Running the script

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.

Getagged mit:
Docker ROS Python3
blog comments powered by Disqus