arrow-left

All pages
gitbookPowered by GitBook
1 of 4

Loading...

Loading...

Loading...

Loading...

flask.md

hashtag
Creating a New Flask App

hashtag
Create the project's directory

mkdir MyNewFlaskApp cd MyNewFlaskApp

hashtag
Install Flask

pip install Flask

hashtag
Create & activate the project's virtual environment

python3 -m venv venv . venv/bin/activate

hashtag
Create the directory & file that will hold the app's main Python code

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.

hashtag
Basic layout in Python file (e.g. __init__.py)

  • See full tutorial .

hashtag
Set environment variables & run the application\

  • Make these are run from the project's directory, not within the flaskr directory.

hashtag
About Flask

  • 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.

  • 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.

hashtag
About Virtual Environments

  • 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.

  • the Flask documentation on virtual environments.

  • To add a virtual environment to a project, cd into the project's directory and run python3 -m venv venv

hashtag
About SQLite

  • 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).

  • 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

hashtag
Adding a UI

hashtag
Bootstrap

  • Bootstrap provides a large .

  • 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">

  • The full set-up instructions can also be found .

  • 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>

hashtag
Icons

  • 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">

  • 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.

hashtag
Brad Nesbitt 10/26/2018

Whenever you work on your project, activate its virtual environment first, by running . venv/bin/activate

.
  • A tutorial on using Flask with SQLAlchemy to interface with a SQLite database in a more object-oriented way can be found .

  • Paste these in order near the very of your HTML document, right bofore the </body> closing tag:

    Bootstrap uses the role attribute to ensure .

    herearrow-up-right
    Flask tutorialarrow-up-right
    Here'sarrow-up-right
    selection of pre-made UI componentsarrow-up-right
    herearrow-up-right
    FontAwesome galleryarrow-up-right
    import flask from Flask
    
    def create_app( test_config=None ):
        app = Flask( __name__, instance_relative_config=True )
        ...
        return app
    export FLASK_APP=flaskr
    export FLASK_ENV=development
    flask run
        <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
        <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
    herearrow-up-right
    herearrow-up-right
    accessabilityarrow-up-right

    web-application

    Integrating using Flask and ROS

    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:

    1. Initialize a node and a publisher to a teleop topic.

    2. Define a Flask route as an endpoint for receiving POST requests containing data representing teleop commands (e.g. 'forward', 'back', 'left', 'right', 'stop').

    3. 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:

    1. Establish a websocket connection between the web client and the machine running ROS, often via a 3rd party Python package called "rosbridge."

    2. 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.

    3. 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 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."

    hashtag
    Solution

    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.

    hashtag
    Brad Nesbitt & Huaigu Lin 10/31/2018

    Example 1arrow-up-right
    Example 2arrow-up-right
    Example 3arrow-up-right
    Flask.run()arrow-up-right
        # ROS node, publisher, and parameter.
        # The node is started in a separate thread to avoid conflicts with Flask.
        # The parameter *disable_signals* must be set if node is not initialized in the main thread.
    
        threading.Thread(target=lambda: rospy.init_node('test_node', disable_signals=True)).start()
        pub = rospy.Publisher('test_pub', String, queue_size=1)
    if __name__ == '__main__':
        if NGROK:
            print 'NGROK mode'
            app.run(host=os.environ['ROS_IP'], port=5000)
        else:
            print 'Manual tunneling mode'
            dirpath = os.path.dirname(__file__)
            cert_file = os.path.join(dirpath, '../config/ssl_keys/certificate.pem')
            pkey_file = os.path.join(dirpath, '../config/ssl_keys/private-key.pem')
            app.run(host=os.environ['ROS_IP'], port=5000,
                    ssl_context=(cert_file, pkey_file))
        flask run --no-reload

    livemap.md

    hashtag
    Campus Rover Live Map

    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

    hashtag
    First Iteration

    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.

    hashtag
    Current Iteration

    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.

    hashtag
    Next Steps

    Support for:

    • Multiple floorplans/maps

    • Switching between different floorplans

    • Adjusting the size and scale of a map (for zooming in/out, resizing, etc.)

    hashtag
    Follow-up Iteration

    Brad Nesbitt 11/18/2018

    hashtag
    Overview of the LiveMap class

    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:

    --

    hashtag
    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.

    --

    hashtag
    live_map.py

    Initializing a LiveMap object requires 2 parameters:

    1. The name/String corresponding to a map in all_maps.json, such as "Gerstenzang Basement"

    2. The desired centimeters per pixel ratio to be used when displaying the map.

    3. 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:

    1. 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.

    2. 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.

    hashtag
    Brad Nesbitt & Huaigu Lin 11/10/2018

  • Generating the map and loading the 4 JavaScript libraries mentioned above on every page load created noticeable performance issues, limiting any additional page content.

  • 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.

  • tutorialarrow-up-right
    RosBridgearrow-up-right
    2Djsarrow-up-right
    RosLibJsarrow-up-right
    repo herearrow-up-right
    herearrow-up-right
    tutorialarrow-up-right
    "Gerstenzang Basement": {
        "files": {
            "path": "rover_app/static/map_files/basement/",
            "png_file": {
                "file_name": "basement.png",
                "cm_per_pixel": 1
            },
            "waypoint_file": "basement_waypoints.json"
        },
        "yaml_parameters": {
            "resolution":  0.01,
            "origin": [0.0, 0.0, 0.0]
        }
        self.map_state = {
            "map_parameters": {
                "map_name": map_name_string,
                "files": {
                    "path": path,
                    "png": {
                        "file_name": map_json["files"]["png_file"]["file_name"],
                        "cm_per_pixel": map_json["files"]["png_file"]["cm_per_pixel"],
                        "pixel_width": png_height,
                        "pixel_height": png_width,
                    },
                    "yaml": map_json["yaml_parameters"]
                },
                "bot_radius": bot_cm_diameter/2,
                "cm_per_pixel": scale_cm_per_pixel, # Desired scale
                "waypoints": waypoints,
                "current_pose": {},
                "goal_pose": {}
            },
            "scaled_pixel_values": {
                "bot_radius": (bot_cm_diameter / 2) * png_cm_per_pixel / scale_cm_per_pixel,
                "cm_per_pixel": scale_cm_per_pixel,
                "png_pixel_width": png_width * png_cm_per_pixel / scale_cm_per_pixel,
                "png_pixel_height": png_height * png_cm_per_pixel / scale_cm_per_pixel,
                "current_pose": {},
                "goal_pose": {}
            },
            "subscribers": {
                "current_pose_sub": rospy.Subscriber('/amcl_pose', PoseWithCovarianceStamped, self.update_current_pose),
                "goal_pose_sub": rospy.Subscriber('/move_base/current_goal', PoseStamped, self.update_goal_pose),
                "rviz_goal_pose_sub": rospy.Subscriber('/move_base_simple/goal', PoseStamped, self.update_goal_pose)
            }
    }
    EaselJsarrow-up-right
    EventEmitter2arrow-up-right