0
$\begingroup$

Rosanswers logo

Hi all,

I am trying to set up a ROS2 rclpy node that functions as a Flask socket server. This node needs to have a thread or timer-based process running (which handles a socket broadcast). The issue I am running into is that when I create a ROS2 timer, it seems that it never executes, and when I use a python thread, it seems to block the main process. The minimal code samples are below:

ROS2 Timer:

class MyNode(Node):

    def __init__(self):
        super(MyNode,self).__init__('my_node')
        self.get_logger().info('Initializing My Node!') # Works fine
        self.app = Flask(__name__) # Flask Stuff
        self.my_ns = NS(self)     # Custom Flask Socket Namespace
        self.socketio = SocketIO(self.app, cors_allowed_origins="*") # Create Socket
        self.socketio.on_namespace(self.my_ns) # Pair the namespace
        self.process_timer = self.create_timer(.1,self.process)
        self.get_logger().info('Initialized!') # Executes

    def process(self):
        self.get_logger().info('Running!') # Never Executes

def main(args=None):
    rclpy.init(args=args)
    node = MyNode()
    node.socketio.run(node.app)

if __name__ == '__main__':
    main()

Python Thread:

class MyNode(Node):

    def __init__(self):
        super(MyNode,self).__init__('my_node')
        self.get_logger().info('Initializing My Node!') # Works fine
        self.app = Flask(__name__) # Flask Stuff
        self.my_ns = NS(self)     # Custom Flask Socket Namespace
        self.socketio = SocketIO(self.app, cors_allowed_origins="*") # Create Socket
        self.socketio.on_namespace(self.my_ns) # Pair the namespace
        self.process_thread = Thread(target=self.process,daemon=True)
        self.process_thread.start()
        self.get_logger().info('Initialized!') # Never Executes

    def process(self):
        alive = True
        while alive:
           try:
              self.assert_liveliness()
              self.get_logger().info('Running!') # Executes
            except:
              alive = False
        

def main(args=None):
    rclpy.init(args=args)
    node = MyNode()
    node.socketio.run(node.app)

if __name__ == '__main__':
    main()

Thanks for the suggestions, and I will try out those changes. I should share the original code I had, which is the following:

class WizardNode(Node):

    def __init__(self):
        super(WizardNode,self).__init__('wizard')
        self.get_logger().info('Initializing Wizard Node!')
        self.app = Flask(__name__)
        self.cors = CORS(self.app)
        self.wizard = Wizard(self)
        self.socketio = SocketIO(self.app, cors_allowed_origins="*")
        self.socketio.on_namespace(self.wizard)
        self.socket_thread = Thread(target=lambda:self.socketio.run(self.app),daemon=True)
        self.socket_thread.start()
        self.process_timer = self.create_timer(.1,self.process)
        self.get_logger().info('Initialized!')
        self.printer = PrettyPrinter()

    def process(self):
        time = self.get_clock().now()
        self.wizard.timestep()



def main(args=None):
    rclpy.init(args=args)

    node = WizardNode()
    try:
        rclpy.spin(node)
    except KeyboardInterrupt:
        pass

    node.destroy_node()
    rclpy.shutdown() 

In this version, it seems that the socket.io thread fails to do any sort of broadcasting/emitting, which was why I tried restructuring in the ways above. I should also say that in this example, using eventlet's monkey_patch() basically broke everything.


Originally posted by AndrewJSchoen on ROS Answers with karma: 50 on 2020-02-27

Post score: 0


Original comments

Comment by MCornelis on 2020-03-04:
Due to my very limited experience with python+ROS2 I'm not sure if this will help you, or if this will just be a stupid comment. I also have no clue what Flask threading is.

Anyways, simply creating a ROS2 node and adding a timer, does not make that timer do anything. In C++ you need to spin() the node for anything to happen. Spinning is basically adding the node to an executor which, when the timer triggers, will execute the callback function. I'm not sure how this is setup in python, but could it be that you are missing this?

This is solely based on my c++ experience and the fact that I don't see a spin() anywhere :P so be gentle.

$\endgroup$

1 Answer 1

0
$\begingroup$

Rosanswers logo

This turned out to be a bit of a fun puzzle to get ROS2 and Eventlet working together. In the end, the solution makes a lot of sense, but it just isn't super intuitive. Basically, the solution is first to import eventlet and run monkey_patch() before any other imports.


import eventlet
eventlet.monkey_patch()
from flask import Flask, request, jsonify, make_response
from flask_socketio import SocketIO, emit, Namespace
...

The issue is that when monkey-patching, it basically turns all the threads into blocking threads, which need to be explicitly slept so that other threads can process (i.e. the rest of the code). So the trick is to combine rclpy's spin_once() with the eventlet.greenthread.sleep()

class MyNode(Node):

    def __init__(self):
        super(MyNode,self).__init__('my_node')
        self.get_logger().info('Initializing My Node!') # Works fine
        self.app = Flask(__name__) # Flask Stuff
        self.my_ns = NS(self)     # Custom Flask Socket Namespace
        self.socketio = SocketIO(self.app, cors_allowed_origins="*") # Create Socket
        self.socketio.on_namespace(self.my_ns) # Pair the namespace
        self.process_timer = self.create_timer(.1,self.process)
        self.ros_thread = Thread(target=self.process,daemon=True)
        self.ros_thread.start()
        self.get_logger().info('Initialized!')

    def process(self):
        alive = True
        while alive:
            try:
                # Check that the ROS node is still alive
                self.assert_liveliness()
                # Spin the ROS node once...
                rclpy.spin_once(self,timeout_sec=0.0)
                # ...and then momentarily sleep so the other process (socket.io) can run.
                eventlet.greenthread.sleep()
            except:
                alive = False

def main(args=None):
    rclpy.init(args=args)

    node = MyNode()
    
    # SocketIO's run has eventlet sleeps incorporated when eventlet is imported
    node.socketio.run(node.app)

    node.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

Originally posted by AndrewJSchoen with karma: 50 on 2020-03-05

This answer was ACCEPTED on the original site

Post score: 1


Original comments

Comment by aniemi on 2021-01-03:
How do you assert the liveliness of the ROS node?

Comment by AndrewJSchoen on 2021-01-03:
assert_liveliness is a method that Node objects have. Since MyNode inherits from it, I use it there.

Comment by aniemi on 2021-01-04:
Thanks, looks like it's not in foxy yet.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.