Post

Building a Python Flask Web UI For Raspberry Pi Sure Elec LCD

In an earlier post I outlined how I setup a Sure Electronics LCD screen with my Raspberry Pi 3 using a Python driver. Whilst updating the LCD via command line is immensely useful I decided to build a UI to control the LCD send messages too it. By using a browser based UI I could update the LCD screen from anywhere. Essentially this was a chance to play with a Python web framework and write some code!

 

I’ve passed the UI’s URL round my family’s devices at home and they now send me messages whilst I’m in my study working/playing.

The end result can be found in my GitHub repo.

As my driver was in Python and I’m enjoying coding in Python at the moment I decided to use a Python Web framework to serve the HTML/JavaScript UI and host RESTful services on the server side to accept LCD commands. After some reading I went with Flask which  seemed perfect for my needs. I could have gone with Django but Flask seemed for appropriate for my needs. For a good comparison see this CodeMentor.io post. For a great tutorial on Flask checkout this series by Miguel Grinberg and this great post by Scotch.io.  

Building the server side web framework was easy and logical in Flask and I was able to get something setup in one file which served my needs. However after reading some Flask best practices I spread my solution out into a more appropriate structure. Flask will seem familiar to web developers with experience of ASP.net MVC, Web API, Node/Express etc. You define routes to handle incoming requests. The key aspects my solution are outlined below. I am running the Flask server directly on my Raspberry Pi and using it to serve the pages and host the services for commanding the LCD screen.

To install Flask (on a Pi) first install Python Pip (a popular Python Package Manager) via “apt-get install python-pip” or “apt-get install python3-pip” (for a Python v3 specific Pip) and then install Flask via “pip install flask”.

Flask comes with a small lightweight development server which runs your app in Dev mode and also auto restarts after code changes. I found this fast and robust enough for my needs. 

Lets check out the main parts of the code:

run.py:  This is the entry point for the app. When run it calls run in the app file and here I have optionally passed IP/Port I want the app to run on which enables the app to be exposed to the internal network so I can connect from other machines on the network. 

1
2
3
from app import app 
if __name__ == '__main__':
    app.run(host="192.100.100.100", port=5000)

app/__inti_.py & config.py: This is the app initialisation code and where it points to the config.py file where config settings can be set for the app.

app/views.py: This is the heart of the app. After importing the relevant python components and instantiating the Smartie LCD driver (from previous post), the routes for the app are defined.

1
2
3
4
5
6
7
8
app.route("/")
def show_homepage():
    return "Home Page!"
 
@app.route("/lcd")
def show_lcdpage():
    name="Jeff"
    return render_template("lcd.html", name=name) 

The route for root will just return the text “Home Page” whereas the route for /lcd will call render_template to return a templated HTML page (lcd.html) and passes any relevant data (e.g. “Jeff” which is irrelevant in this example”). Templates will be covered shortly below.

1
2
3
4
5
6
7
8
9
10
11
12
13
@app.route("/lcd/clear", methods=["POST","GET"])
def display_clear():
    smartie1.clear_screen()
    return "Success"
 
@app.route("/lcd/displaymessage", methods=["POST"])
def display_message():
    if not request.json:
        abort(400)
    smartie1.clear_screen()
    smartie1.backlight_on()
    smartie1.write_lines_scroll(request.json['Lines'])
    return "Success"

Any POST or GET on http://SERVERADDRESS:PORT/lcd/clear will result in the smartie drivers clear screen method being called. A POST to “/lcd/displaymessage” will be validated to ensure that the request contains JSON data and then the data will be passed to the driver for display.

/app/templates/lcd.html: This is the main HTML page that enables the user to type the messages to display.

The CSS and JavaScript used by this page is found in the static folder and referenced in the usual way………….

1
2
3
4
5
<!-- CSS for our app -->        
 <link rel="stylesheet" href="/static/lcd.css"/>
 
<!-- JS for our app -->
<script type="text/javascript" src="/static/lcd.js" charset="utf-8"></script>

So we need to ensure that the flask server returns these static files, but we don’t want to have to define  a specific app.route for each one so instead we use this one in our views.py :

1
2
3
@app.route('/<path:filename>')  
def send_file(filename):  
      return send_from_directory('/static', filename)

This basically states that any requests for a file path are sourced from the /static folder directly. So we can just place any files in the static folder that we want to be served directly (the CSS and JavaScript files in our case).

/app/static/lcd.js:  From this JavaScript code we can consume the services hosted by Flask for our application. It’s using the XMLHttpRequest object to make AJAX requests to the Flask server. The SendCommand function takes callback methods which will be called on success or error.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
function SendCommand(url, httpVerb, data, successCallback, errorCallback){
                 
  var dataToSend;
  if(data!=null){
      var dataToSend = JSON.stringify(data);          
  }
   var request = new XMLHttpRequest();
  request.open(httpVerb, url, true);
  request.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
   request.onload = function() {
      if(this.status >= 200 && this.status < 400){
          // success here
          var returnedData; 
          if (this.response != null){
              successCallback(returnedData, this.status);
          }                                                                        
      }
      else{
          //error returned from server
          errorCallback("Error response returned from server", this.status);
      }
  }
   request.onerror = function() {
          errorCallback("Error contacting server", this.status);
  }
 
  if (dataToSend != null){
      request.send(dataToSend);
  }
  else{
      request.send();
  }
}

That’s mostly it. Run the app by running the run.py module (e.g in the Python IDLE or terminal) and direct your browser to http://SERVERADDRESS:5000/lcd.

The code for my Python driver and this web app is available on GitHub here https://github.com/RichHewlett/smartie and https://github.com/RichHewlett/LCD-Smartie-Web.

This post is licensed under CC BY 4.0 by the author.