Flask and Flask-Admin

Flask is a package that can be downloaded onto the Pi along with Flask-Admin. Flask ties in your .py scripts with .html and .css scripts for you to interpret the database generated by Peewee and add it to a web server hosted locally by the Pi. Flask-Admin is an extension of Flask that makes it easier to format and add functionality to your web page using built in functions (that are demonstrated in code below) that will automatically generate blocks of html code for you. It is particularly helpful with databases  because it lets you group together all of the usual Create, Read, Update, Delete (CRUD) view logic into a single, self-contained class for each of your models.

Further information on Flask and Flask-Admin can be found by clicking on the respective links.

upv_app.py

from flask import Flask , render_template
from flask_admin import Admin
from flask_admin.contrib.peewee import ModelView

import model

app = Flask(__name__)
admin = Admin(app, name = 'UPV Wind Data',template_mode='bootstrap3',url='/')

app.config['SECRET_KEY'] = 'DIT123' # Have to set secret key or else you won't be able to save things later on.
app.config['MODEL'] = model.SensorAccess()

if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0')

The code above in ‘upv_app.py’ is the working script that runs your server, it has to be running to access the server. Within the script you can edit the site name and the site template, Bootstrap3 was used as our template style as it has the icest look to it in our opinion. It also points flask in the direction of the SensorAccess object from ‘model.py‘ so that we can include our database on the web page that is generated.

index.html


{% extends 'admin/master.html'%}

{% block body %}
<h1> UPV Wind/Power Station and Wireless Sensor Network</h1>
Using a SQLite database to configure and store the data from wireless sensors

{% endblock %}
<span id="mce_SELREST_start" style="overflow:hidden;line-height:0;"></span>

Above is the index.html‘ script that uses a mixture of jinja(a language that flask-admin uses) and html language to enable a simple way of formatting the web page. Our web page is very simple so there is only a heading 1 and paragraph included.

The output of the ‘upv_app.py‘ and ‘index.html‘ comes out with the basic web page below;

2018-06-26-131830_1920x1080_scrot

Flask-Admin helped generate interactive tabs such as the Turbine_Weather tab which is a page that displays a table containing our database data as can be seen below;

2018-06-26-132648_1920x1080_scrot

Using the Peewee ORM in practice

As Discussed in the post Using an ORM Tool for SQLite , we used the Peewee ORM to act as an intermediate layer between our SQLite database and our Python code.

Model.py

When starting off with SQLite you would create,access and edit your database as shown in the SQLite Database post. With Peewee this process is made a lot easier. A ‘model.py’ file was created. The code from within this file is shown and explained below;


from peewee import * # Imports all the libraries from the Peewee

db = SqliteDatabase('UPVSite.db')# Creates a database(db) called UPVSite.db
''' Standard convention would be to have a Plural as the DB name and singular for the class name(s)'''<span id="mce_SELREST_start" style="overflow:hidden;line-height:0;"></span>

class Turbine_Weather(Model):
      time = DateTimeField()
      incoming_data = TextField()

class Meta:
      database = db

The above code is the start of the ‘model.py‘ script. Apart from what is already commented within the code, it is creating a Model type class named ‘Turbine_Weather‘ that contains the characteristics  time and incoming_data, that are declared as a DateTime field and Text field respectively. DateTimeField() and TextField() are two out of a long list of built in field type specifiers within Peewee, a full list can be found at Peewee Field Types . The class named Meta is declaring that the Turbine_Weather Model will have Peewee’s database characteristics which will be important for use with Flask and Flask-Admin later on.


class SensorAccess(object):

# Initialises access to the database
def __init__(self): ''' Self stops confusion between SQLite syntax that is similar to Python syntax '''
    db.connect() # Connects to database
    db.create_tables([Turbine_Weather], safe=True) # Checks if table is made

# Function to add data readings to db table
def add_reading(self,time, incoming_data):
    Turbine_Weather.create(time = time, incoming_data = incoming_data )

# Function to return data correlating to a specific time passed to in
def get_(self, time ):
    data_at_time = Turbine_Weather.get(Turbine_Weather.time == time)
    return data_at_time 

# This is a function to return all rows of data
def get_readings(self):
    return Turbine_Weather.select()

# Function to return the latest data readings up to a certain limit in
# descending order
def get_recent_readings (self , limit = 30):
    return Turbine_Weather.select() \
                      .order_by(Turbine_Weather.time.desc()) \
                      .limit(limit)

# Closes the connection to the database
def close(self):
    db.close()

The above code is the final part of the ‘model.py‘ script. Here an Object type class is being made named ‘SensorAccess’. This class contains all functions that will relate to the sensor and related databases, although in this case we only have one db associated with the sensor object. All database query’s are made as functions e.g.  get_recent_readings(self, limit = 30). 

SerialRead.py

In our case to use Peewee to add the data coming in through the antennas connected to the serial port of the PI a second .py script was needed, we named it as ‘SerialRead.py‘. This script is very similar to the code that was implemented in the Saving Data to a .CSV post. It updates on that by using the functionality within the ‘model.py‘ to add the incoming data sent from the STM32L4 chip to the UPVSite database.


import sqlite3
import time
import datetime
import serial
import string

import model 

print ("Starting... ")

ser_in = serial.Serial(port = '/dev/ttyS0', # ttyS0 is for the RPi 3
                    baudrate = 19200,
                    parity = serial.PARITY_NONE,
                    stopbits = serial.STOPBITS_ONE,
                    bytesize = serial.EIGHTBITS,
                    timeout = 100)
print ("Connecting... ")
data = model.SensorAccess()

try:
    while True :            

        si = ser_in.readline().strip() # si = Serial in<span id="mce_SELREST_start" style="overflow:hidden;line-height:0;"></span>
        read_time = datetime.datetime.now()#reads in current time into read_time 

        print(si)
        data.add_reading(read_time, si)# Adds the current time and incoming data

        time.sleep(300)# Sleeps for 5 minutes before updating its readings 

In the code above the data is read in as usual.  The major difference is the ‘data.add_reading(read_time, si)’ that uses the ‘add_reading(self, time)’ function within our ‘model.py‘ to add read in data to the database.

Using an ORM Tool for SQLite

We’re using an ORM because it allows the database to be accessed far easier via Python. The Python ORM we’re using is peewee (you read that correctly), it adds a level of abstraction to the process making it far easier to add columns to the database. This is all done by setting up a Python class with the columns being set up just like private variables in a normal class.

Image result for orm
General Function of an ORM

To begin using peewee I followed a YouTube tutorial which detailed the general set up of everything: https://www.youtube.com/watch?v=bLO8G21z8bY

When writing the script the classes were initialized in one Python file and the code was implemented in a second.

 

Basic Web Page

To upload the data received by the Pi, an HTML server must be set up to pass the weather station information.

Image result for html server
from flask import Flask # imports the flask library
from flask import render_template # imports the flask render template
app = Flask(__name__) # sets to default name
app.debug = True

@app.route("/")
def hello(): # making a function called hello
 return render_template('hello.html', message="Hello World!") # passes to hello.html

if __name__ == "__main__": # if name is correct 
 app.run(host='0.0.0.0', port=8080) # initiates server at 0.0.0.0 on port 8080