Our objective is to initialize ROS nodes within a Flask app, enabling publishing and subscribing to ROS topics via API calls or UI interactions.
For example, when run/served, the Flask app would:
Initialize a node and a publisher to a teleop topic.
Define a Flask route as an endpoint for receiving POST requests containing data representing teleop commands (e.g. 'forward', 'back', 'left', 'right', 'stop').
Handle POST requests to the endpoint by executing a Python function in which the ROS publisher publishes the appropriate cmd_vel message to a teleop topic.
It's important to note that this differs from a more common approach to Web-ROS interfacing, which involves the following:
Establish a websocket connection between the web client and the machine running ROS, often via a 3rd party Python package called "rosbridge."
Within the web client's JavaScript, import a 3rd party library called "roslibjs," which provides ROS-like classes and actions for subscribing and publishing to ROS topics.
Unlike publishers and subscribers implemented in ROS, roslibjs sends JSON to rosbridge which, in turn, publishes and subscribes to actual ROS messages. In short, rosbridge is required as an intermediary between a web client and a machine running ROS.
This has the advantage of providing a standard way for any web client to interface with ROS via JSON. However, this not only makes running rosbridge a necessity, but it also requires ROS developers to implement ROS-like programming in JavaScript. Flask, on the other hand, seems to offer a way to implement ROS patterns purely in Python on both client and server, without rosbridge and roslibjs as dependencies.
There is an apparent obstacle to implementing ROS within Flask, though. It seems to involve the way Flask serves an app and the way ROS nodes need to be initialized. More specifically, the issue might arise from initializing a ROS node in a thread other than the main thread, which seems to be the case for some of the ways Flask apps can be run/served. Others in the ROS community seem to have encountered this issue:
Note that the 3rd example proposes a solution; their Flask app's main thread initializes and starts a new thread in which a ROS node is initialized:
However, to actually serve the app, they call Flask.run()
:
Flask's documentation on Flask.run() advises against using it in a production environment:
"Do not use run() in a production setting. It is not intended to meet security and performance requirements for a production server. Instead, see Deployment Options for WSGI server recommendations."
"It is not recommended to use this function for development with automatic reloading as this is badly supported. Instead you should be using the flask command line script’s run support."
"The alternative way to start the application is through the Flask.run() method. This will immediately launch a local server exactly the same way the flask script does. This works well for the common case but it does not work well for development which is why from Flask 0.11 onwards the flask method is recommended. The reason for this is that due to how the reload mechanism works there are some bizarre side-effects (like executing certain code twice, sometimes crashing without message or dying when a syntax or import error happens). It is however still a perfectly valid method for invoking a non automatic reloading application."
Instead of using Flask.run()
within a Flask app's main method/script, we've had success with using the following via Flask's command line interface:
Without the --no-reload
argument, the lines in which your ROS node is initialized will be executed twice, resulting in a ROS error stating that the node was shut down because another with the same name was initialized.
The objective is to implement a 2D map in the CR_Web application that depicts:
The floorplan Campus Rover is currently using to navigate
Campus Rover's "real-time" location as it navigates
The goal destination, toward which Campus Rover is navigating
Our first implementation was based on a that relied on a websocket connection between the robot and web client, and had the following dependencies on 3rd party libraries:
This initial implementation () was successful, but presented several issues:
Building upon 3rd party dependencies risked future breaks and maintenance.
As discussed , it entailed "ROS-like" programming in JavaScript instead of Python.
The implementation described in the generates a 2D map image from an amcl occupancy grid. This is unecessary for our purposes, because Campus Rover uses a pre-generated floorplan image; re-generating it is redundant and thus computationally wasteful.
Generating the map and loading the 4 JavaScript libraries mentioned above on every page load created noticeable performance issues, limiting any additional page content.
The current iteration resolves the issues identified through the first iteration and enables additional map features:
Instead of generating a map image from an occupancy grid, an existing floorplan image file is rendered.
Instead of using 3rd-party JavaScript libraries, the map is rendered using HTML5's Canvas element.
Instead of writing "ROS-like" JavaScript in the front end as before, all ROS code is implemented with regular ROS Python programming in the Flask layer of the application.
Unlike the initial iteration, the current map includes the option to "track" the robot as it traverses the map, automatically scrolling to keep up with the robot as it moves.
The current iteration now displays the robot's goal location, too.
Support for:
Multiple floorplans/maps
Switching between different floorplans
Adjusting the size and scale of a map (for zooming in/out, resizing, etc.)
Brad Nesbitt 11/18/2018
After several preceding iterations of "live" 2D maps, it became clear that a single abstraction for such mapping would be appropriate. An instance of the LiveMap
class maps waypoints, the robot's current pose, and its goal poses onto 2D floorplan for display within a web application.
The static
directory in rover_app
now contains map_files
, which contains the local files needed to generate a given map, including a JSON file with parameters specific to each map. For example:
--
all_maps.json
The JSON object for a map includes references to local files comprising the map's floorplan .png
file, a JSON file of the map's waypoint data, and a copy of the yaml parameters used for amcl navigation of the .png
-based map.
--
live_map.py
Initializing a LiveMap object requires 2 parameters:
The name/String corresponding to a map in all_maps.json
, such as "Gerstenzang Basement"
The desired centimeters per pixel ratio to be used when displaying the map.
An optional parameter is the centimeter diameter of the robot, which is the Turtlebot2's spec of 35.4 by default.
For example, live_map = LiveMap("Gerstenzang Basement", 2)
initializes a LiveMap object of the Gerstenzang Basement floorplan with a 2cm/pixel scale. The object maintains the following abstraction representing the state of the map, including the robot's current place within it and it's goal destination:
Note that a nested dictionary of ROS subscribers continually updates the scaled pixel value equivalents of the current and goal poses.
Implementing 2D mapping in this way aims to achieve two main advantages:
The LiveMap class allows the initialization of multiple, differing maps, with custom scales in the web application. For instance, a small, "thumbnail" map could be implemented on one page, while large map could be displayed somewhere else. This also makes switching between maps is also possible.
Representing a map_state
as a Python dictionary (shown above) makes it easy to send the data needed to work with a live 2D map as JSON. For instance, a map route or endpoint could be implemented to return a map_state
JSON object which could, in turn, be used to render or update a map in the UI.
mkdir MyNewFlaskApp
cd MyNewFlaskApp
pip install Flask
python3 -m venv venv
. venv/bin/activate
mkdir flaskr
touch __init__.py
Adding an __init__.py
file to a directory tells Python to treat it as a package rather than a module. Though it can be left empty, it usually contains code used to initialize a package. We can use it to house our main application code.
__init__.py
)Make these are run from the project's directory, not within the flaskr directory.
Note that the create_app()
function takes the name of a configuration file, which contains the names and values of environmental variables to be used by the Flask application.
Using a python virtual environment in a project is a way to ensure all of project's dependencies (e.g. python version, python packages, etc.) "accompany" it and are met wherever it happens to be run.
To add a virtual environment to a project, cd into the project's directory and run python3 -m venv venv
Whenever you work on your project, activate its virtual environment first, by running . venv/bin/activate
SQLite is a serverless relational database. Simply put, it allows you to implement a database in your project without having to run/connect to a separate database server.
It's intended for light use, ideal for a development environment (or in production with light activity).
To enable using Bootstrap via CDN: 1. Paste this into your HTML document's header, before any other links to css: <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
Paste these in order near the very of your HTML document, right bofore the </body>
closing tag:
Most Bootstrap elements are added by creating a <div>
with the class
attribute set to one of Bootstrap's predefined classes. For example, adding a Boostrap alert element consists of the following: <div class="alert alert-primary" role="alert">A simple primary alert—check it out!</div>
To enable use of free FontAwesome icons via CDN, add the following link tag to the header of your HTML document: <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.4.1/css/all.css" integrity="sha384-5sAR7xN1Nv6T6+dT2mhtzEpVJvfS3NScPQTrOxhwjIuvcA67KV2R5Jz6kr4abQsz" crossorigin="anonymous">
See full tutorial .
A Flask app is an instance of the Flask class. However, instead of using just one, global Flask object, a Flask app is best implemented by defining a function (e.g. create_app()
) that creates and returns an instance of the Flask class whenever called. This function is often referred to as the "Application Factory." See the for further details.
the Flask documentation on virtual environments.
Python also has built-in support for SQLite3, so there's no need to install it. Adding it to a project is a simple as import sqlite3
. Further Flask-specific documentation is available .
A tutorial on using Flask with SQLAlchemy to interface with a SQLite database in a more object-oriented way can be found .
Bootstrap provides a large .
The full set-up instructions can also be found .
Bootstrap uses the role
attribute to ensure .
To add a specific icon, pick the one you want from the , then simply copy its html tag (e.g. <i class="fas fa-arrow-alt-circle-up"></i>
) and paste it into the desired section of your HTML document.