Sei sulla pagina 1di 136

Raspberry

Pi: Full Stack


A whirlwind tour of full-stack web application
development on the Raspberry Pi

Peter Dalmaris, PhD


Published by Tech Explorations

Copyright Peter Dalmaris, 2015


Ebook Formatting by Guido Henkel

All rights reserved. Except as permitted under the U.S. Copyright Act of 1976, no part of this publication may be
reproduced, distributed or transmitted in any form or by any means, or stored in a database or retrieval system, without
the prior written permission of the publisher.

About this book


This book is a project. Using a step-by-step approach, it will help you explore your
Raspberry Pi in a way that will help you appreciate both its hardware and its software
capabilities.
In writing this book, I have tried to break away from the classic text book format of
chapters, sections and long paragraphs, and instead present this highly technical topic as
an engineer would approach it: with a lot of iterations, each delivering a gradual
improvement to the overall functionality of the system, with each iteration comprising of
several step.
Each step is marked with a number so that you can refer to it in your own notes, or in your
communication with me or other readers of this book.
All of the code that I describe in this project is hosted on Github, from where you can
download it on your computer. It is much better to copy code from the Github repository
rather than trying to manually copy it from this book. I provide links to the file that I am
discussing at different parts of the project; simply click to a link and copy the code from
the screen.
In writing this project, I make a few assumptions about you:
1. You can program a computer in at least one programming language, not necessarily
Python
2. You are not afraid of learning a new programming language
3. You are comfortable working with electronics. You will need some basic beginnerlevel skills.
4. You are not easily frustrated. What you are about to do in this project requires
patience!
5. You like to explore different technologies. The modern maker must be good in
multiple technologies, hardware, software and different sub-categories of each one.
The goal is to show you how to setup a Raspberry Pi computer so that:
It measures temperature and humidity.
It reports the values in real time via a web browser.
It record these values in a database.
It retrieves these records and displays them in tabular format and in graphical format
in a web browser.
It sends the values to a graphical analysis cloud service.
You will learn:
How to setup the minimal Raspbian operating system to the RPi.
Install the a Python virtual environment
Install and use Flask, a Python-based web micro-framework
Install and use uWSGI as the application server for Flask
Install and use Nginx light-weight web server

Use the RPi GPIOs as digital input and outputs


Use a DHT22 humidity and temperature sensor
Install and use the SQLite database
Use the Google Chart API to create visual representations of the sensor data
Use JQuery to add interactivity to web pages
Use Plotly for graphical analysis of sensor data

Video course companion


A detailed video-based version of this project is also available. It contains the exact same
instructions as this book, but in full HD video. You can see the project coming to life.
Hardware wiring, blinking LEDs, sensors, code walkthroughs, everything happens on
screen.
This video course presents this project in 57 lectures and 7 hours of content. See below the
image for a list of sections and lectures.
I invite you to watch a few sample video lectures. If you wish to purchase this video
course, you can take advantage of the special e-book reader pricing of $9 by using coupon
code ebook at checkout, or click on this link.

Video course, list of sections and lectures


Section: 1 - Introduction to the course
Lecture 1: Introduction 02:17
Lecture 2: About the Raspberry Pi 04:45
Lecture 3: Components 04:22
Lecture 4: Detailed List of Components you will need
Lecture 5: Please read this before continuing!

Section: 2 - The Operating System


Lecture 6: Section Intro 00:25
Lecture 7: Installing mini Raspbian using Mac OS X 16:35
Lecture 8: Installing mini Raspbian using Windows 14:42
Quiz 1: Section Quiz 3 questions
Lecture 9: Section Conclusion 00:14

Section: 3 - Python and GPIOs


Lecture 10: Section Intro 00:25
Lecture 11: Set up Python 13:55
Lecture 12: GPIO basics 08:36
Lecture 13: Make an LED blink 17:57
Lecture 14: Read the status of a button 09:52
Lecture 15: Read temperature and humidity from a digital sensor 15:53
Quiz 2: Section Quiz 4 questions
Lecture 16: Section Conclusion 00:21

Section: 4 - Setup the Web application stack


Lecture 17: Section Intro 00:24
Lecture 18: What is the Web application stack? 07:03
Lecture 19: Install Nginx, the Web server 02:49
Lecture 20: Install Flask and venv 09:17
Lecture 21: Install uWSGI 18:40
Lecture 22: Setup Upstart 10:19
Lecture 23: About log files 08:49
Lecture 24: Serving static assets and Skeleton 11:55
Lecture 25: Styling our Web application with Skeleton 09:12
Lecture 26: Debugging a Flask application 05:46
Quiz 3: Section Quiz 6 questions
Lecture 27: Section Conclusion 00:19

Section: 5 - Building a simple Flask application on the Raspberry Pi


Lecture 28: Section Intro 00:31
Lecture 29: Show DHT22 sensor data in the browser 14:07
Lecture 30: Install the SQLite3 database 14:05
Lecture 31: Use a Python script to store sensor reading to the database 07:34
Lecture 32: Automate sensor data logging with cron and SQLite3 12:57
Lecture 33: Show historical sensor data in the browser 12:48
Quiz 4: Section Quiz 3 questions
Lecture 34: Section Conclusion 00:22

Section: 6 - Improving our application with date-time range record selector


Lecture 35: Section Intro 00:26
Lecture 36: Selecting historical sensor data records with a time-date range 07:12
Lecture 37: Define a date-time range in the URL 10:49
Lecture 38: Timezones in Rasbian 02:03
Lecture 39: Validating timestamps 06:24
Lecture 40: Tidying up: refactor our application code 03:32
Quiz 5: Section Quiz 3 questions
Lecture 41: Section Conclusion 00:31

Section: 7 - Improving the user interface


Lecture 42: Section Intro 00:29
Lecture 43: Adding date range radio buttons 19:17
Lecture 44: Visualise sensor data with Google Charts 18:35
Lecture 45: Install a datetime picker widgets 07:11
Lecture 46: Setting up the datetime picker widget 07:57
Lecture 47: Setting up time zones on the client side 08:31
Lecture 48: Setting up time zones on the server side 14:00
Lecture 49: Link the two pages of the application 05:29
Quiz 6: Section Quiz 3 questions
Lecture 50: Section Conclusion 00:28

Section: 8 - Setup cloud charting and analysis with Plotly


Lecture 51: Section Intro 00:15
Lecture 52: Setup Plotly 10:19
Lecture 53: Add Plotly links 10:05
Lecture 54: Add Plotly support to the Flask application script 11:11
Quiz 7: Section Quiz 3 questions
Lecture 55: Section Conclusion 00:28

Section: 9 - Other useful things to know


Lecture 56: Install and configure a Wifi USB dongle for wireless networking 22:22

Section: 10 - Conclusion
Lecture 57: Conclusion

Discussion forum and email list


This book has a web page! In that page, I am posting updates and errata, as well as host
the books discussion forum.
If you wish to be notified automatically of updates and corrections, please consider
signing up to the books email list at http://txplore.com/rpi-full-stack-book/.
Here are some more details about the books discussion forum. In fact, there are two
discussion forums:
1. If you have purchased access to the video course version of this book at txplore.tv
(described earlier), there is a discussion forum for each lecture. You can use these
dedicated forums to ask questions or discuss the specific topic of a lecture.

2. If you have not purchased the video course version of this book, you can use the
general discussion forum at http://txplore.com/rpi-full-stack-book/.

To make the most of this book


All code discussed in this book is available on Github at
https://github.com/futureshocked/RaspberryPi-FullStack/. Links that take you directly to
individual files from where you can copy this code are given throughout the text. You can
also download the entire repository with a single click so that you can have this code on
your computer.

To complete the project, you will need these materials:


A Raspberry Pi, any version
A Windows, Mac or Linux computer
A DHT11 or DHT22 sensor
An 5mm LED
Resistors
A breadboard and jumper wires
Access to the Internet
A USB Wifi dongle, if available

Purchase the parts from the vendor of your choice. You can also purchase a bundle with

everything you need from my Amazon affiliate shop, at http://txplore.com/raspberry-pifull-stack-parts-bundle/.


If you are not in the USA, you can use a forwarding service like Shipito to have your
bundle components shipped anywhere in the world.

CHAPTER ONE
Introduction
The Raspberry Pi is a low cost computer, popular with people who want direct access to
its hardware. It has revolutionised computer education by combining low price with
accessibility.

The Raspberry Pi Model A

The student, for the first time since the early days of the PC revolution, has direct physical
access to the hardware. The Raspberry Pi is made for learning. To make the most of it, you
must spend time to gain an understanding of the basics of its hardware, its operating
system, programming, and the peripherals that you can connect to it.
This project is designed for that purpose. Its objective is to take you to a whirlwind tour
of the Raspberry Pi, and introduce you to everything that is great about it.
We will start with installing and configuring the operating system, the hard way. No point
and click, no graphical user interface. Just a keyboard and a command line on the screen
will separate you from the computer.
Then, we will look into the Raspberry Pis General Purpose Input/Output pins and interact
with LEDs and switches using Python. Python is one of the many programming languages
you can use on the RPi.
Well move on to learn how to connect an environment sensor, and add to our Python

skills the ability to install third-party libraries.


Next, well setup a web server so that we can access our sensor data via a web browser.
This will expose you to a variety of web development skills, like the web application
development framework, Flask, the uWSGI application server, a simple single user
database, SQLite, and Javascript/JQuery for web client-side programming.
You will also learn how to take advantage of cloud computing resources, and in particular
you will use Google Charts to create charts of the sensor data, and Plotly for more
advanced processing.
Lets start!

CHAPTER TWO
About the Raspberry Pi
The Raspberry Pi, at the time I am creating this project, is available in several different
models.
Model A, Model A+, Model B and Model B+. In this project I am using the Model B.

Some of the available Raspberry Pi models

All Raspberry Pis share some common features. Looking at the circuit board, you can
see:
1. The Processor and RAM chip
2. The LAN controller chip
3. A HDMI video output connector
4. A composite analog video connector (on models A and B)
5. An SD card connector
6. A micro-USB power connector
7. A USB port
8. An Ethernet port (on model Bs)
9. A camera connector

10. And the very important GPIO headers

The Raspberry Pi, common components

The Raspberry Pi comes as a single PCB. No keyboard and mouse, no screen, not even a
power supply. You have to provide all that. In this project, we will be using the Raspberry
Pi in so called headless mode. This means that we will not be connecting it to a
keyboard or mouse. Instead, we will work with the Raspberry Pi via an SSH network
connection. Dont worry if you dont quite understand what this means, I will show you
everything you need to know, step by step.
As a computer, and unlike micro-controllers like the Arduino, the Raspberry Pi needs an
operating system. There are several options to choose from:
Rasbian, the Raspberry Pi Foundations preferred operating system distribution
Ubuntu,
Openelec
OSMC
Pidora
RISC OS
And Minibian, my preferred distribution.
All of them except for RISC OS are flavours of Linux.
Minibian is a minimalist version of Raspbian. It keeps everything that is important and
throws away the graphical user interface and a few other things that are not really needed
for our purposes. In return, we get a small disk footprint so we can use even small 4GByte
SD Cards.
Although there is an distribution designed for absolute beginners, NOOBS, I will show
you how to install Minibian in the next section.

The Raspberry Pi comes with 512 MBytes or 1GByte of RAM, depending on the model.
Video memory is shared with general purpose memory. In our project, we will not be
using any video output, so we will configure our Pi to not use any video memory.
Although this amount of RAM may seem too little at a time when computers come with
multiple of gigabytes, for an embedded computer it is more than enough. People run
multiplayer game servers like Minecraft on it, and small production web servers and
database server. Others have even used the RPi as a node for small supercomputers. With
a bit of planning, the Raspberry Pi can do amazing things.

Raspberry Pi running a Minecraft server

To make something useful with the Raspberry Pi, just like with any computer, you need
application software. You can either download ready made software, or write your own. In
this project, I will show you both. You will download, install and configure various types
of servers, and you will write your own application in Python.
You will not become an expert Python programmer, but you will become familiar with it
enough to be both useful and dangerous. Thats a great start!
Finally, the Raspberry Pi rarely works in isolation. It has a fast Ethernet communications
socket through which you can connect it to the Internet. You can also attached a Wifi USB
module and go wireless. We will take advantage of this capability and make it possible for
our application to interact with Internet based web services. You will also be able to access
your application via a web browser, potentially making it possible to access your
Raspberry Pi from anywhere in the world.
Ok, enough with this general introduction to the Raspberry Pi. In the next lecture, I will
talk about the components that you will need for this project.

CHAPTER THREE
Install an Operating System
We will use MiniRasbian, available at minibianpi.wordpress.com
The decision to use MiniRaspbian instead of Rasbian is purely practical. In this project,
we only need some of the many features and capabilities of the full Rasbian distribution.
MiniRaspbian is a minimal version of Rasbian. With MiniRasbian, we get a fully
Raspberry Pi compatible operating system with a very small disk footprint and without
any features that are not strictly necessary for our purposes.
We can install it on a very small SD card, 4GBytes are enough. And once it is on this
small SD card, we can mould it to support the exact capabilities we need.
Lets get started!

Step 1.1
Download the OS image from minibianpi.wordpress.com. In this project, we will be using
the 4th release of Minibian

Step 1.2
On a Mac, use ApplePi-Baker (www.tweaking4all.com/news/applepi-baker-v1-6-update)
to install the OS image on an SD card.

Step 1.3
Once the tool completes the process, eject the SD card and insert it into the RPi.

Step 1.4
Plug in an Ethernet cable to the RPi and turn it on.

Step 1.5
Plug in an Ethernet cable to the RPi and turn it on.

Step 1.6
Using a terminal window, like iTerm on the Mac or Putty on Windows, log on to your RPi
for the first time:
> ssh root@192.168.111.63
The authenticity of host 192.168.111.63 (192.168.111.63) cant be established.
RSA key fingerprint is 54:ce:1f:28:34:87:65:48:1f:e6:fd:bb:e0:d9:a8:27.
Are you sure you want to continue connecting (yes/no)?

Say Yes to the prompt.

Step 1.7
Well update the installation, and then install raspi-config so that we can use this utility to
do some basic configuration on the RPi:
>apt-get update #To update the package repository lists
>apt-get upgrade # To upgrade the OS with the latest files
>apt-get install raspi-config

Step 1.8
Well run raspi-config twice. First, to expand the file system on the SD card to use all of
the available space. Second, to enable some of the RPis subsystems that will be useful
later.
>raspi-config

Choose option 1 Expand Filesystem.

When done, you will get a confirmation screen:

Hit Enter to close this window. Then Tab twice to highlight Finish and Enter again to
close raspi-config. Choose Yes to make the RPi to reboot.

This will start the process of expanding the file system. Give your RPi around 10 minutes
to finish (this depends on the size of your SD Card and the speed of your RPi).

Step 1.9
A while later, try to logon again so that we can finish the basic configuration. Remember,
the default password is raspberry:
> ssh root@192.168.111.63

Step 1.10
Use raspi-config again to change some of the settings:
>raspi-config

First, set up a new password:

You will see this, just hit Ok:

If this worked for you, go to the next step.


At the time of writing this, this didnt work for me. I received this error:

Go back, well change the password on the command line later. Lets move on with the
Advanced Options.
Choose 8 - Advanced Options

We want to enable SPI, I2C and Serial. These are communications channels that we will
find useful later. Dont worry about anything else, although you can feel free to look
around.
First, SPI:

Select Yes, then Ok, Yes and Ok at the prompts:

The last Ok will take you back to the first window of the tool. Again, select 8 Advanced Options and then A7 I2C:

Choose Yes, Ok, Yes, and Ok. Then go back to Advanced Options and choose
Serial:

Select Yes to enable Serial. Reboot to make these changes effective: Hit Tab twice to
highlight the Finish option, and hit Enter. Hit Yes to reboot.
Again, it will take around a minute for the RPi to be rebooted.

Step 1.11
Finally, lets change the root default password:
> ssh root@192.168.111.63
> passwd root
Enter new UNIX password:
Retype new UNIX password:
passwd: password updated successfully

Choose a good password. If you forget it, theres no way to reset it.

CHAPTER FOUR
Setup Python and try out the GPIOs
Although Raspbian comes with Python, we will not use the default version. Instead, we
will use the Python Virtual Environment, or virtualenv. With virtualenv, we can isolate
multiple Python environments, each with its own interpreter version and library set.
This is great for doing experiments without having to modify our main production
Python environment. If you make a mistake, you can just delete your test virtual
environment. Needless to say, virtualenv if perfect for experimenting, which is what we
are doing right now.
Well install virtualenv and test it by making an LED blink.

Step 2.1
Start by installing the necessary header files and a static library for Python.
>apt-get install python-dev

Step 2.2
Install the Python virtualenv
>apt-get install python-virtualenv

Step 2.3
Now we will create a new working folder and activate a new Python virtual environment
in it.
>cd /var
>mkdir working
>cd working
>virtualenv venv #venv is the name of the folder that will contain the virtual environment
New python executable in venv/bin/python
Installing distribute.done.
Installing pipdone.

Step 2.4
Notice that now you have a new folder in the working directory, called venv. In that
folder, you have a complete copy of the Python environment.
Lets activate it:
>. venv/bin/activate
(venv)root@raspberrypi:/var/working#

You execute the activate script in the venv/bin directory. The prompt is updated to
include the (venv) indicator, showing that your virtual environment with that name is
activated. If you have multiple environments, the one that is actually activated will always
be named in the command line prompt.
Play around with Python on the command interpreter to show that it works.
To deactivate, you can just do this (dont it now though!):
>deactivate

Step 2.5
Lets install the Python RPi GPIO package using the virtual environments package
manager:
>pip install rpi.gpio

Step 2.6
Well make an LED blink. You will need two male-female jumper wires, a 5mm red LED
and a ~150Ohm resistor. Use this diagram to find out the pins to use. Remember that pin 1
is at the bottom of the board closest to the power red LED.
Take a wire and connect the long pin of the LED to pin 7 (GPIO4).
Connect the resistor from the short pin of the LED to a whole in a vacant column on the
breadboard.
Take a wire and connect the free pin of the resistor to pin 6 (Ground) on the RPi.

Step 2.7
Use this program to make the LED blink. First, lets install a text editor, then copy the
program to the editor (dont worry, later on we will use your favourite text editor on your
Mac or Windows machine):
>apt-get install vim

While in your working directory, open up a new vim editor:


>vim blinky.py

Hit the i key to get vim into insert mode, and copy this program in the file:
import RPi.GPIO as GPIO ## Import GPIO Library
import time ## Import time library (for sleep)
pin = 7 ## Were working with pin 7
GPIO.setmode(GPIO.BOARD) ## Use BOARD pin numbering
GPIO.setup(pin, GPIO.OUT) ## Set pin 7 to OUTPUT
for i in range (0, 20): ## Repeat 20 times
GPIO.output(pin, GPIO.HIGH)## Turn on GPIO pin (HIGH)
time.sleep(1) ## Wait 1 second
GPIO.output(pin, GPIO.LOW) ## Turn off GPIO pin (LOW)
time.sleep(1) ## Wait 1 second
GPIO.cleanup()

In Python, it is very important that you keep the indentation correct. Inside the for loop,
the code you be intent by exactly one level, either space(s) or tab(s).
Make sure the program was copied correctly. Then hit the escape key to get vim into
command mode. Then, save and quit vim by typing :wq (w for write, q for quit).
Now run the program:
>python blinky.py

and witness this brilliant LED blinking 20 times!

Step 2.8
Spend a bit of time looking at the program. Notice how we import the GPIO library at the
first time, and the time library in the second so that we can use the sleep function.
Then, we set the board pin number to 7. We can refer to RPi pin in two ways, physical
numbering or GPIO numbering. Physical numbering are the actual number of each pin
that we can count, starting with 1 from the pin that is the closest to the red power LED, 2
the one next to it, etc. GPIO numbering uses the references that the RPi itself uses
internally. As most of us are humans, I think we should use the physical numbering
system.
That is why in line 5 we set GPIO mode to BOARD.
To use GPIO numbering, you would write GPIO.BCM.
Next, we tell the RPi that we will be using the pin 7 as a digital output pin.
Inside the loop, we turn the LED on and of using the GPIO.output method.
When the loop finishes, we clean up the RPis GPIO registers, and we are done! You
should always finish your RPi programs with this instruction so that the pins are left in a
known and safe state.

Step 2.9
Now lets do the opposite: Use a GPIO pin and an input, to read the state of a button.
Take a button or switch and place it on your breadboard. A button usually has 4 pins,
connected in pairs. Look for the pins that point away from the button, on either side of the
button. Take a jumper wire and connect one of the two to ground.
On the second pin, connect a jumper wire to the physical pin 8 on the RPi, and a 10KOhm
resistor. The other side of the resistor should be connected to pin 1 on the RPi, which
provides 3V of power. The resistor creates a pull-up, so that when the button is not
pressed, it will convey the 3V to the input pin 8.
This is what your circuit should look like now:

Step 2.10
Just like in the LED experiment, open up vim like this (download button.py):
>vim button.py

This will create a new file with the file name button.py. Hit the i key to go into text
insert mode, and copy this text in it:
import RPi.GPIO as GPIO ## Import GPIO Library

inPin = 8 ## Switch connected to pin 8


GPIO.setmode(GPIO.BOARD) ## Use BOARD pin numbering
GPIO.setup(inPin, GPIO.IN) ## Set pin 8 to INPUT
while True: ## Do this forever
value = GPIO.input(inPin) ## Read input from switch
if value: ## If switch is released
print Not Pressed
else: ## Else switch is pressed
print Pressed
GPIO.cleanup()

Be careful, the indentations are important!


When you have finished copying the program, type Esc-: to go into command mode, and
wq to save the changes to the file and quit.
In this program, in the second line we are declaring the variable inPin to hold the pin
number where the button is connected. In the next two lines, we set this pin to be an input,
and asking the RPi to use physical pin references.
Inside the while loop, we take a reading from the inPin using the GPIO.input method. If
the value is true, we will print Not Pressed, otherwise we will print Pressed.
When the GPIO.input reads a HIGH (anything around 3V), then it will return a TRUE
value, otherwise FALSE.
On the command line, run the program:
>python button.py

Hint: if the RPi complains like this:


Traceback (most recent call last):
File button.py, line 1, in <module>
import RPi.GPIO as GPIO ## Import GPIO Library
ImportError: No module named RPi.GPIO

Then you have probably forgotten to start your virtual environment. Do that now (from
your working directory):
>. venv/bin/activate

Your running program will look like this in the terminal:


Now press the button, and see the new message scrolling up:

There you have it! You now know how to use GPIO pins as inputs and outputs!
To end the execution of the program, type Control-C.

Step 2.11
A little exercise: can you add an LED to the breadboard and get it to light up when you are
pressing the button?

CHAPTER FIVE
Use a DHT22 sensor
In this section we will connect a DHT22 temperature and humidity sensor. Do do this, we
will install the DHT library from its Github repository. The library is written and
maintained by Adafruit, and is available at this location:
github.com/adafruit/Adafruit_Python_DHT
The process involves installing Git on your RPi. Git is a version control system that
Github uses to manage the repositories that are stored on it.
Lets begin!

Step 3.1
Log on to your RPi if you are not already, go to your working folder, and activate your
Python virtual environment:
>ssh root@192.168.111.63
>cd /var/working
>. venv/bin/activate

Your command prompt now has the (venv) flag showing:


(venv)root@raspberrypi:/var/working#

Step 3.2
Install Git:
>apt-get install git-core

This is a large package, so installation will take a while.

Step 3.3
Configure Git by telling it who you are:
>git config global user.name Peter
>git config global user.email peter@txplore.com

To confirm your settings, type this:


>git config list
user.name=Peter
user.email=peter@txplore.com

Step 3.4
We can now go ahead and clone the Adafruit DHT library from Github. Use your browser
to visit the library page: github.com/adafruit/Adafruit_Python_DHT
On the right side of the page, notice the clone URL text box. Click on the clipboard icon to
copy the URL into your clipboard.

Step 3.5
On your RPi, type this to clone the library repository to your computer:
>git clone https://github.com/adafruit/Adafruit_Python_DHT.git
Cloning into Adafruit_Python_DHT
remote: Counting objects: 112, done.
remote: Total 112 (delta 0), reused 0 (delta 0), pack-reused 112
Receiving objects: 100% (112/112), 38.30 KiB, done.
Resolving deltas: 100% (66/66), done.

Note: I used Command-V on the Mac or Ctr-V on Windows/Linux to past the URL from
the clipboard into the command line.

Step 3.6
You now have a new directory in your working folder, named Adafruit_Python_DHT.
Change in it, and run the setup script to install the library.
>cd Adafruit_Python_DHT
>python setup.py install

Step 3.7

The library repository comes with examples, lets use them to make sure that our sensor
works. First though, we need to connect our sensor. Plug in the sensor to your breadboard.
In my example, I have left the circuit from the earlier LED experiment intact in case I
want to use it later. Dont worry if it looks a bit messy, there are only 4 connections to be
made.

Step 3.8
Look at the DHT22 sensor from the front:

Here is the purpose of each pin:


1. 3.3V
2. Data
3. Leave unconnected
4. GND
Start by taking a 10KOhm resistor and connecting it between pins 1 and 2. This resistor
will be a pull up for the data pin.
Next, connect pin 2 to 3.3V. If you already have a jumper wire connected to pin 1 on the
RPi, then follow this wire to the breadboard and connect your sensor power wire to a free
hole in the same column as the wire that is coming from the RPi.
Similarly, connect a wire from the sensor pin 4 to an available ground pin on the RPi, or to
a ground column on the breadboard.
Finally, lets connect the data pin. I would normally connect the data pin to the RPi GPIO
4, which is the physical pin 7. However, pin 7 is already connected to the LED, so I will
use GPIO pin 17 instead, which is physical pin 11.
Double-check the connections, and lets move on the the Python program so that we can
take a measurement.

This interactive pinout diagram credit Gadgetoid, pi.gadgetoid.com/pinout

Step 3.9
Change into the examples folder of the Adafruit Python DHT library. For me, this is done
like this:
>cd /var/working/Adafruit_Python_DHT/examples

The example I want to use is titled AdafruitDHT.py. Lets have a look inside out of
curiosity, to see how it works. Well use cat to do this:
>cat AdafruitDHT.py

Notice how there is an array that contains the valid sensor names. The program also reads
the sensor data pin from the command line.
The reading is done with the instruction Adafruit_DHT.read_retry(sensor,pin). This
returns the humidity and temperature values, which are assigned to local variables through
multiple assignment.
Lets run the program!
>python AdafruitDHT.py 2302 17
Temp=25.9*C Humidity=57.1%

It worked! The model of my sensor is 2302, and I had it connected to GPIO 17. Take care
not to confuse the GPIO number with the physical pin number because they are different.
Use the pin map above to figure out your GPIO.
Curiously, this program worked with my sensor both when I use 2302 or 22 as the name
argument. The readings I received in either case were almost identical, so I guess that the
two sensor types are directly compatible. But the program didnt work when I used 11 as
the sensor name.
Awesome! We have setup our RPi, and we can use its GPIOs. We even have a library to
make it easy to work with the DHT sensor.
Lets move on to setting up a web application server so that we can multiply our RPis
functionality and get our data to the cloud!

CHAPTER SIX
Create a web application stack on the RPi
At this point, we have setup the Raspberry Pi with an operating system and Python. We
can connect devices to its GPIOs and read the status of a button, control an LED, and take
temperature and humidity readings from a sensor.
Next, you will learn how to use the Web as an interface for the RPi. This way, you will be
able to control functionality on the Raspberry Pi via a web browser.

The Web application stack. Each layer implements a function on which software on the layer above it depends. This
diagram also shows the specific software that we will use inside each layer (the blue boxes), the hardware (the red box)
and the client components (the green boxes).

To do this, we will create something called a web application stack. The stack contains
software that:
allows a web browser to interact with the RPi (this is the web server software),
allows for Python scripts to be executed in response to web browser requests (an
application server)
makes it easy for us to create a web application, that is, an application that we can
interact with via our web browser (a web application framework).
Theres other software components we will need, like a database to store and retrieve data
from our sensors, and related libraries.
Lets get started with the web server. We will use Nginx, which is a very popular server,
fast and with a small memory and disk footprint that is very popular for running on small
computers like the RPi or on virtual machines.
Then, we will install Flask, a Python web development micro-framework that makes it
easy to build great web applications quickly.

Next, well install a Python application server, uWSGI, which will execute our Python
programs on behalf of the Nginx web server.

Step 4.1
Log on to your RPi if you are not already, go to your working folder, and activate your
Python virtual environment:
>ssh root@192.168.111.63

Step 4.2
Install Nginx:
>apt-get install nginx

Step 4.3
Installation will take a while. Once its done, you can test your new web server.
First, remember what your IP address is (in case you forgot since you logged on via
SSH!). Hint: use the ifconfig command.
Start Nginx:
>/etc/init.d/nginx start
Starting nginx: nginx.

Step 4.4
Go to your web browser, and navigate to your RPis IP address: http://192.168.111.63
You should see Nginxs default home page:

Step 4.5
We have a working web server! Lets move on to the rest of the stack. Before we install
Flask and uWSGI, well create a new directory that will be the home of our web
application. In it, we will install a dedicated Python virtual environment, just like we did
earlier with the simple LED and DHT examples.
Well setup this directory in the /var directory. Within /var lets create a subdirectory
called www, and within that one called lab_app. If you decide to create more web
applications later, you can put them in the www too. If you dont like the name I chose,
lab_app, choose one that you do.
>cd /var
>mkdir www
>cd /www
>mkdir lab_app

Step 4.6
Now well setup a Python virtual environment in lab_app.
>cd lab_app
>virtualenv venv
New python executable in venv/bin/python
Installing distribute.done.
Installing pipdone.

Step 4.7
Activate the new virtual environment:
>. venv/bin/activate
(venv)root@raspberrypi:/var/www/lab_app#

Once activated, your command prompt will contain the name of your virtual environment
at the beginning of the prompt (venv).

Step 4.8
On to Flask! Remember, Flask is a micro-framework that makes it easy to create web
application using Python. Itself is a Python package, so we will install it using the pip
package manager:
>pip install flask

Eventually, you will see this success message:


Successfully installed flask Werkzeug Jinja2 itsdangerous markupsafe
Cleaning up

Step 4.9
Lets test Flask to make sure it is really working.
Use vim and copy this program in it (here it is on Github):
>vim hello.py

then type i to get into insert mode.


from flask import Flask
app = Flask(__name__)
@app.route(/)
def hello():
return Hello World!
if __name__ == __main__:
app.run(host=0.0.0.0, port=8080)

Save it as hello.py using ESC-wq and ENTER, and then run it:
>python hello.py
* Running on http://0.0.0.0:8080/ (Press CTRL+C to quit)

Flask is now waiting for a browser to make a request. It will output log messages to the
console so that you can see what is happening.
Go to your browser and navigate to this URL: http://192.168.111.63:8080 (change the IP
address to the actuall IP address of your RPi, but keep the 8080 port number the same).
You should see this:

To exit Flask and continue with our setup, type Ctrl-C in the command line.

Step 4.10
Next, its the turn of the uWSGI application server. This will execute Python scripts on
behalf of the web server. It is also a Python package, so install it like this:
>pip install uwsgi

This takes a while because the uWSGI is actually compiler from source on your RPi.
Eventually, it will say this and finish:
Successfully installed uwsgi
Cleaning up

Step 4.11
Now we need to configure Nginx and uWSGI to work together.
Well start with Nginx. First remove the configuration file for the default application (the
one that produced the Hello World page in our test earlier), then create a new application
configuration file for our new application, and enable it.
Then, go to uWSGI. Create a configuration file for it, and make it so that its service starts
automatically when the RPi starts.This may seem a bit complex, but I assure you it
nothing you cant handle.

Step 4.12
Nginx: remove the default application configuration file. This file is in /etc/nginx/sitesenabled/default. The file default is actually a symbolic link (like a Windows shortcut),
pointing to /etc/nginx/sites-available/default.
Just remove this link and we are done:
>rm /etc/nginx/sites-enabled/default

Then create a new configuration. Create a file called lab_app_nginx.conf:


>cd /var/www/lab_app/
>vim lab_app_nginx.conf

Press i to go into input mode, and copy this code:


server {
listen 80;
server_name localhost;
charset utf-8;
client_max_body_size 75M;
location /static {
root /var/www/lab_app/;
}
location / { try_files $uri @labapp; }
location @labapp {
include uwsgi_params;
uwsgi_pass unix:/var/www/lab_app/lab_app_uwsgi.sock;
}
}

You are configuring an application that listens for requests on port 80. The location of its
static assets (things like HTML files, CSS files and images) are in a directory called
static, which is inside the root folder. The root folder is at /var/www/lab_app.
The connection to the uWSGI application server is done via a socket file. This will be
generated later by uWSGI, but now we can define its location at
/var/www/lab_app/labapp_uwsgi.sock.

Step 4.13
To enable this new application we first create a symbolic link from its real location to
/etc/nginx/conf.d/. , and then we will restart Nginx to make the changes effective. Nginx
also looks in /etc/nginx/conf.d/ to find configuration files for applications, in addition to
/etc/nginx/sites-enabled.
Create the symbolic link:
>ln -s /var/www/lab_app/lab_app_nginx.conf /etc/nginx/conf.d/

Step 4.14
Restart Nginx to make the changes effective:
>/etc/init.d/nginx restart
Restarting nginx: nginx.

Step 4.15
Lets switch to uWSGI. Create a new file:
>vim /var/www/lab_app/lab_app_uwsgi.ini

and copy this code in it:


[uwsgi]
#applications base folder
base = /var/www/lab_app
#python module to import
app = hello
module = %(app)
home = %(base)/venv
pythonpath = %(base)
#socket files location
socket = /var/www/lab_app/%n.sock
#permissions for the socket file
chmod-socket = 666
#the variable that holds a flask application inside the module imported at line #6
callable = app
#location of log files
logto = /var/log/uwsgi/%n.log

There are two things to notice.


First, the name of our app is hello, which matches the name of our text hello.py program
that we created earlier.
Second, the last line is for the log file, which will be stored in /var/log/uwsgi. This
directory does not exist, so we need to create it:
>mkdir -p /var/log/uwsgi

Step 4.16
Lets try to start the uWSGI daemon now:
>uwsgi ini /var/www/lab_app/lab_app_uwsgi.ini
[uWSGI] getting INI configuration from /var/www/lab_app/lab_app_uwsgi.ini

The daemon will start, and just like our test of the Flask server earlier, it will output log
message to the console.
You can verify that uWSGI via Nginx and Flask is responding to your web browser
requests by pointing your browser to http://192.168.111.63/ (change my IP address to
yours):

You can see that the page at port 80 is now returning from our hello.py program.
At this point, since we didnt get any error messages, we can be confident that uWSGI
works fine. To make it useful, we must make the daemon run as a background service.
Hit Ctrl-C to do this next.

Step 4.17
Debian comes with a service management system called Upstart. Upstart can be used to do
things like start up a service when the Raspberry Pi restarts, or to restart it when it crushes.
uWSGI works well with Upstart. In uWSGI, there is a special mode called Emperor,
which manages application instances. Emperor will look for configuration files that exist
in a folder called, appropriately, vassals. Emperor then will spawn instances of the
application server based on those files.
In short, Upstart will start Emperor, and Emperor will start our uWSGI application server
based on a vassal configuration file.
First install Upstart:
>apt-get install upstart

and reboot:
>reboot

Step 4.18
Once the reboot is complete and you have logged back into the root account of your RPi,
create the Upstart configuration file. This should go in /etc/init, since this is where Upstart
looks for configuration files:
>vim /etc/init/uwsgi.conf

Copy the following code to this file:


description uWSGI
start on runlevel [2345]
stop on runlevel [06]
respawn
env UWSGI=/var/www/lab_app/venv/bin/uwsgi
env LOGTO=/var/log/uwsgi/emperor.log
exec $UWSGI master emperor /etc/uwsgi/vassals die-on-term uid root gid root logto $LOGTO

Notice that the last line, starting with exec is looking for Emperor configuration files in
/etc/uwsgi/vassals. So lets create a configuration file for our application in there now.

Step 4.19
Create the vassals directory:
>mkdir -p /etc/uwsgi/vassals

Notice that the p switch instructs the mkdir command to create any directories below
vassals that dont exist. So uwsgi and vassals will be created from a single
command.

Step 4.20
Now, we will link the uWSGI configuration file we created in step 15 inside
/var/www/lab_app/lab_app_uwsgi.ini to the vassals directory via a symbolic link:
>ln -s /var/www/lab_app/lab_app_uwsgi.ini /etc/uwsgi/vassals/

Step 4.21
Lets try to start uWSGI via Upstart now:
>start uWSGI

Step 4.22
Verify. Direct your browser to your RPi, and you should see the familiar Hello World
message:

Step 4.23
Trouble? Problems? Log files are here to help. Whenever something is not quite right,
start by examining the log files. You have two:
1. Nginx access at /var/log/nginx/access.log
2. Nginx error at /var/log/nginx/error.log
3. uWSGI at /var/log/uwsgi/lab_app_uwsgi.log

Step 4.24
Your application should also be able to start automatically after a RPi reboot. Try
rebooting your RPi, wait for a couple of minutes for the process to complete, and without
logging on, request http://192.168.111.63 in your browser. The page should be loading
without any issues.

CHAPTER SEVEN
Serving static assets
In this section I will show you how to serve static web assets from your RPi. Static assets
are files like images, CSS and Javascript, or anything that is not executable code.
In this section, we will set up Nginx to serve static assets, and make our humble Hello
World! message a little bit nicer to look at using some CSS.

Step 5.1
We will create a new directory in the /var/www/lab_app directory in which we will store
static assets. Lets call this new directory static:
(Assuming you have logged on to your RPi and are now in the /var/www/lab_app
directory):
>mkdir static
>cd static
>mkdir css
>mkdir images

Step 5.2
Lets see an example of a static file. First, lets create a simple HTML file, and store it in
the static directory with the name a_static_file.html. Give it this content:
<html>
<head>
<title>Static page</title>
</head>
<body>
<h1>This is an example of a static page</h1>
<p>Neat, isnt it?</p>
</body>
</html>

Step 5.3
Navigate your browser to http://192.168.111.63/static/a_static_file.html.
You should see this:

Step 5.4
Lets add a CSS file to make this page look a bit better. If you are not familiar with CSS,
for now it is enough to know that CSS is a language for styling web pages. It contains
information that the browser uses to change the default way it renders the various web
page elements.
In this project, I have selected a simple and popular boilerplate design called Skeleton.
You can find it and download it at http://getskeleton.com. The downloaded archive
contains two CSS files and several images to match the styles.
Download the Skeleton ZIP file and expand it. You will have something like this:

The archive contains an example index file in its root, the two CSS files inside the css
directory, and a favicon image file in images.

Step 5.5
You now need to transfer these files to the appropriate locations on your RPi. The easiest
way to do this is to use an SFT/SSH utility. My favourite utility is Cyberduck
(http://cyberduck.en.softonic.com/mac) on the Mac, and Filezilla (http://filezillaproject.org) on Windows.
Lets work with Cyberduck. Start it up, and click on the Open Connection icon. Choose
SFTP from the protocol drop-down menu. Type in the RPi address and credentials, like in
the image below:

Click Connect. You will get a window asking you to accept the server fingerprint, to
which you should click Allow.
The process of copying files using SFTP is very similar across different graphical FTP
clients.

Step 5.6

Navigate to the static directory of your application, like in this image:

Now, copy the files from the css directory in the Skeleton archive (which is on your local
computer) to the css directory on your RPi (accept any request for confirmation) :

Do the same for the image file. Dont worry about the HTML file.

Step 5.7
On the RPi, you should now have these files in your static directory:

Step 5.8
Now you need to connect your HTML file to the CSS. Add the following text to
a_static_file.html (additions are in red):
<html>
<head>
<meta charset=utf-8>
<title>Static page</title>
<! Mobile Specific Metas
>
<meta name=viewport content=width=device-width, initial-scale=1>
<! FONT
>
<link href=//fonts.googleapis.com/css?family=Raleway:400,300,600 rel=stylesheet
type=text/css>
<! CSS
>
<link rel=stylesheet href=css/normalize.css>
<link rel=stylesheet href=css/skeleton.css>
<! Favicon
>
<link rel=icon type=image/png href=images/favicon.png>
</head>
<body>
<h1>This is an example of a static page</h1>
<p>Neat, isnt it?</p>
</body>
</html>

The new content provides links to the two CSS files, to the favicon image, and the the font
type that we want to use in our HTML page.

Step 5.9
Assuming you have saved the updated HTML file, refresh your browser. This is what you
should see now:

Much nicer looking text! This template contains many more useful features that we will
explore later.
Ok, we have a style static page. How about we create the same styling for our Flask
Hello World! app? Lets do this in the next section.

CHAPTER EIGHT
Flask templates and CSS styling
I really dont like working with ugly looking web pages, so before going any further Id
like to apply the Skeleton style to the pages that are served by Flask. At the moment,
theres only one, implemented by the hello method in the hello.py file.
In Flask, you can style pages in two steps. First, you create a template for your page or
pages (one template per page, or you can share templates between multiple pages).
Second, you link CSS files and images with the templates.
Lets try this out.

Step 6.1
Go to your app directory and create a new directory, called templates.
>cd /var/www/lab_app
>mkdir templates

Step 6.2
Add some text to the hello.py program. The added text is in red:
from flask import Flask
from flask import render_template
app = Flask(__name__)
app.debug = True
@app.route(/)
def hello():
return render_template(hello.html, message=Hello World!)
if __name__ == __main__:
app.run(host=0.0.0.0, port=8080)

First, we are importing the package that handles templates. In the second red insert, we are
enabling the Flask verbose mode so that Flask produces more debug output to help us
troubleshoot problems later on.
Then, in the third red line, we are connecting a template contained in file hello.html
(which we will create in a minute inside the templates directory), and passing a variable
named message with some text to it.

Step 6.3
Time to create the actual template. Change in the templates directory, and create a new file
named hello.html with the following code:
<html>
<head>
<meta charset=utf-8>
<title>Static page</title>
<! Mobile Specific Metas
>
<meta name=viewport content=width=device-width, initial-scale=1>
<! FONT
>
<link href=//fonts.googleapis.com/css?family=Raleway:400,300,600 rel=stylesheet
type=text/css>
<! CSS
>
<link rel=stylesheet href=/static/css/normalize.css>
<link rel=stylesheet href=/static/css/skeleton.css>
<! Favicon
>
<link rel=icon type=/static/image/png href=images/favicon.png>
</head>
<body>
<h1>{{ message }}</h1>
</body>
</html>

This looks similar to the HTML page in the static directory. It is. The only difference is
that:
I have replaced the content of the h1 tag with a variable placeholder, using the curly
brackets. Inside the curly brackets I have placed the name of the variable
(message), that I have passed to it from the Python hello.py program.
I have adjusted the path of the CSS and image files to match the web location of
these files.
A template language called Jinja2 will process the content of the curly brackets, and in this
example will replace it with the actual value of the variable.

Step 6.4
Because we have made changes to the Python program, we need to restart the application
server, uWSGI. Do that like this:
>restart uwsgi

Step 6.5
Refresh your browser:

There you go, a much nicer hello world!


You now have a template connected to a nice stylesheet.
If you ran into problems, things like Internal Server errors and the like, keep these points
in mind:
A quick way to troubleshoot is to run your Flask application directly, instead via
uWSGI. While at the root of your application, use python hello.py to start it. You
will need to point your browser to http://192.168.111.63:8080/ , since this is where
your Flask application is served (notice the port ID, in red). If there is a problem with
your application, the comprehensive error messages will help you to find out the
problem.
Whenever you make a change to the Python or template files, you must restart the
server in order for the changes to become effective. Do this with restart uwsgi if
you are using uWSGI, or by hitting Ctrl-C and running python hello.py if you are
using Flask directly.
Awesome! Next up, lets get the sensor data accessible through the web browser!

CHAPTER NINE
View sensor readings via a web browser
We already know how to work with the DHT sensor using a stand-alone Python script. In
this section, you will learn how to work with this sensor from within a Flask application.
As you will see, this is relatively simple. You have to install the DHT library in the Python
virtual environment (which is what you had to do with the stand-alone Python script
anyway), get readings from the sensor and pass them to the template file.

Step 7.1
First, lets install the DHT sensor Python library to the Python virtual environment that we
use for our Flask application.
I assume that you are logged in.
A while ago, we downloaded the Adafruit DHT library, and saved it in
/var/working/Adafruit_Python_DHT. We are going to use that package again, but this time
well install it in the /var/www/lab_app/venv virtual environment.
>cd /var/working/Adafruit_Python_DHT
>/var/www/lab_app/venv/bin/python setup.py install

With the second command, we are using the Python interpreter in our target virtual
environment to execute the setup script. As a result, the library is installed in the target
virtual environment.

Step 7.2
Switch back to our Flask application directory. Well replace the hello.py script with a new
one. The new script, called lab_app.py, will contain a single method. This method will get
readings from the sensor and return a HTML page to the user.
>cd /var/www/lab_app
>vim lab_app.py

Step 7.3
Copy the following script in the editor (file name on Github: lab_app_v1.py):
from flask import Flask, request, render_template
app = Flask(__name__)
app.debug = True # Make this False if you are no longer debugging
@app.route(/)
def hello():
return Hello World!
@app.route(/lab_temp)
def lab_temp():
import sys
import Adafruit_DHT
humidity, temperature = Adafruit_DHT.read_retry(Adafruit_DHT.AM2302, 17)
if humidity is not None and temperature is not None:
return render_template(lab_temp.html,temp=temperature,hum=humidity)
else:
return render_template(no_sensor.html)
if __name__ == __main__:
app.run(host=0.0.0.0, port=8080)

The web root of our web application still returns the Hello World message. I have added
a new method, lab_temp, which is revoked when the browser makes a get request to the
lab_temp resource. So, this method will respond to this address in your browser:
http://192.168.111.63/lab_temp

(of project, the exact IP address could be different for your RPi).
Inside this method, note that we are importing the necessary libraries, and get the values
from the sensor. If values are retrieved successfully, they are passed to the lab_temp.html
template, but if not the script will return the no_sensor.html template.

Step 7.4
Go in the templates directory and create the first template, lab_temp.html. It should
contain this code (on Github, this file is named lab_temp_v1.html):
<html>
<meta charset=utf-8>
<title>Lab Conditions by RPi</title>
<meta http-equiv=refresh content=10>
<meta name=description content=Lab conditions - RPi>
<meta name=author content=Peter Dalmaris>
<! Mobile Specific Metas
>
<meta name=viewport content=width=device-width, initial-scale=1>
<! FONT
>
<link href=//fonts.googleapis.com/css?family=Raleway:400,300,600 rel=stylesheet
type=text/css>
<! CSS
>
<link rel=stylesheet href=/static/css/normalize.css>
<link rel=stylesheet href=/static/css/skeleton.css>
<! Favicon
>
<link rel=icon type=image/png href=/static/images/favicon.png>
</head>
<body>
<div class=container>
<div class=row>
<div class=two-third column style=margin-top: 5%>
<h2>Real time lab conditions</h2>
<h1>Temperature: {{{0:0.1f}.format(temp) }}C</h1>
<h1>Humidity: {{{0:0.1f}.format(hum)}}%</h1>
<p>This page refreshes every 10 seconds</p>
</div>
</div>
</div>
</body>
</html>

In red, I mark that locations where the variables that are passed by the Python script are
interpreted and replaced with their actual values. The rest of this HTML code is almost
identical to the code from the hello.py template we saw earlier.
At the top of the page, I have added a meta tag that instructs the browser to refresh the
page every 10 seconds. This is probably the easiest way to cause a page to self-refresh,
therefore keeping the readings current on our screen.

Step 7.5
Still in the templates directory, create the no_sensor.html file with this content (on Github,
this file is named no_sensor_v1.html):
<html>
<head>
<link rel=stylesheet type=text/css href=/static/style/eureka_style.css>
</head>
<div class=test_container>
<h1>Sorry, cant access the sensor!</h1>
</div>
</html>

Step 7.6
We have to update the uWSGI configuration file to point to the new Python application
file, lab_app.py (on Github, this file is named lab_app_uwsgi_v2.ini).
Open it and make the update (updated text in red):
>vim lab_app_uwsgi.ini

Content:
[uwsgi]
#applications base folder
base = /var/www/lab_app
#python module to import
app = lab_app
module = %(app)
home = %(base)/venv
pythonpath = %(base)
#socket files location
socket = /var/www/lab_app/%n.sock
#permissions for the socket file
chmod-socket = 666
#the variable that holds a flask application inside the module imported at line #6
callable = app
#location of log files
logto = /var/log/uwsgi/%n.log

Step 7.7
Restart uWSGI to make the change effective:
>restart uwsgi

Step 7.8
Go to your browser and type in this URL: http://192.168.111.63/lab_temp
You should see this result:

Nice, isnt it?


Theres no time for rest for the wicked, so what are we going to work on next?
How about installing a database so that we can store our readings in it? We can use a
database to log data, then retrieve it and create tables and graphs. Thats coming up next!

CHAPTER TEN
Setup a simple database and use it to log sensor data
In this section, we will install a simple database and use it to store sensor readings. The
database we will use is SQlite, a very popular open-source relational database that was
developed for use in embedded devices. Most smartphone applications use SQlite for at
least part of their data management requirements. On the RPi, although you can install a
more heavy duty database like MySQL or Postgresql, for our requirements of a low
traffic database driven application, SQLite is just perfect.
In this lecture, I will show you how to install SQLite3, and interact with it on the
command line.
Later, we are going to create a Python script that takes a reading from the sensor and
stores the values in the database. We will also create a scheduler (Cron) task that will
invoke the script every 10 minutes automatically. This way, our RPi will take temperature
and humidity measurements every 10 minutes, forever (or at least until it runs out of disk
space or we turn off power or we stop the Cron task).

Step 8.1
Assuming you are logged on to your RPi, install SQLite:
>apt-get install sqlite3

Step 8.2
Test it on the command line. From /var/www/lab_app:
>cd /var/www/lab_app
>sqlite3 lab_app.db
sqlite> .help

The first line starts the SQLite command line interface with the name of the database we
want to use. This database file is empty at the moment, but we will add a couple of tables
and records in it.
The second line will cause a list with the available commands will appear. In SQLite, you
can input multi-line statements by starting with the BEGIN; command and ending with the
COMMIT; command. Every command should end with a semicolon (;).

Step 8.3
Lets create the database table where we will store the temperature data, and manually
create a couple of records in it (continuing from 2, still on the SQLite command prompt):
sqlite> begin;
sqlite> create table temperatures (rDatetime datetime, sensorID text, temp numeric);
sqlite> insert into temperatures values (datetime(now),1,25);
sqlite> insert into temperatures values (datetime(now),1,25.10);
sqlite> commit;

Step 8.4
Lets retrieve the two records we just created:
sqlite> select * from temperatures;
2015-04-07 04:47:00|1|25
2015-04-07 04:47:25|1|25.1

Step 8.5
Do the same for the humidity table:
sqlite> begin;
sqlite> create table humidities (rDatetime datetime, sensorID text, temp numeric);
sqlite> insert into humidities values (datetime(now),1,51);
sqlite> insert into humidities values (datetime(now),1,51.10);
sqlite> commit;

Step 8.6
You can use the .tables command to verify that you have created two tables:
sqlite> .tables
humidities temperatures

Note that these tables are not yet stored on the disk, only in a buffer. You can also use
.schema so see the properties of a table:
sqlite> .schema temperatures
CREATE TABLE temperatures (rDatetime datetime, sensorID text, temp numeric);

Step 8.7
Exit and check that the new database file exists in your /var/www/lab_app directory:
sqlite> .exit
>ls
hello.py lab_app.db lab_app.py lab_app_uwsgi.ini static venv
hello.pyc lab_app_nginx.conf lab_app.pyc lab_app_uwsgi.sock templates

In red I mark the new database file.

Step 8.8
Lets use a Python script to get a reading from the sensor and store the values in a new
database record.
Create a new file called env_log.py with this content:
#!/usr/bin/env python
import sqlite3
import sys
import Adafruit_DHT
def log_values(sensor_id, temp, hum):
conn=sqlite3.connect(/var/www/lab_app/lab_app.db) #It is important to provide an
#absolute path to the database
#file, otherwise Cron wont be
#able to find it!
curs=conn.cursor()
curs.execute(INSERT INTO temperatures values(datetime(now),
(?), (?)), (sensor_id,temp))
curs.execute(INSERT INTO humidities values(datetime(now),
(?), (?)), (sensor_id,hum))
conn.commit()
conn.close()
humidity, temperature = Adafruit_DHT.read_retry(Adafruit_DHT.AM2302, 17)
if humidity is not None and temperature is not None:
log_values(1, temperature, humidity)
else:
log_values(1, -999, -999)

The database-related code is marked in red. This script contains a method, log_values, that
manages the process of creating new records in the database with the values passed to it.

Step 8.9
Run the new script to make sure it works. Lets activate the Python interpreter for our
virtual environment first. Alternatively, you can call our applications Python interpreter
by typing in the complete path to its executable. Ill do the activation since it is less
typing:
>. venv/bin/activate
>python env_log.py

Step 8.10
Use the Sqlite command line tool to verify that we have a new record in our database.
>sqlite3 lab_app.db
SQLite version 3.7.13 2012-06-11 02:05:22
Enter .help for instructions
Enter SQL statements terminated with a ;
sqlite> select * from temperatures;
2015-04-07 05:13:20|1|24.2999992370605
sqlite> select * from humidities;
2015-04-07 05:13:20|1|48.7000007629395
sqlite>.exit

You may have additional records in your database, but the records we are interested in
here are the ones just inserted by the script we executed in the previous step.
All good, our Python script can retrieve readings from the sensor and record them in the
database.

Step 8.11
Lets schedule a measurement every ten minutes. We will use the Linux scheduler for that,
Cron. Cron is very simple. You use a text editor to edit a special cron file.
For each task, you create a line of text that contains the schedule and the command to be
executed. They important thing to remember is the Cron tasks are executed without a
shell, which means that environmental variables are not retrieved.
In other words, you need to always use full and absolute paths for every file you reference
in a Cron schedule.
Start the Cron editor like this:
>crontab -e

This will start vim. Go to the bottom of the file (you can use the G shortcut to get there
fast), go into insert mode (i) and copy this schedule at that location:
*/10 * * * * /var/www/lab_app/venv/bin/python /var/www/lab_app/env_log.py

The */10 parameter means every 10 minutes. The * * * * means every hour, every,
minute, every day, every day of the week.
Notice that the paths are full to make sure that the Cron daemon will be able to find the
files we need it to execute.

Step 8.12
Ten minutes later, go back into the SQlite command line interface to see if we have a new
record:
sqlite> select * from temperatures;
2015-04-07 05:13:20|1|24.2999992370605
2015-04-07 06:00:06|1|24.3999996185303
sqlite> select * from humidities;
2015-04-07 05:13:20|1|48.7000007629395
2015-04-07 06:00:06|1|47.7999992370606

(Newer records in red)


One problem with Cron is that failures are silent. If there is a problem with your schedule
or with your script, and the task fails, we would not know why.
Would can only know that it didnt work because, in our present instance, there is no
additional record in the database. If this happens to you, double and triple check your
script and Cron task.

Step 8.13
Having to use the SQLite command line interface to get the latest values is not very
convenient. Lets get our web application to create a new page with a list that contains the
records for the temperature and humidity tables.
First, well add a new method in the lab_app.py file which will respond to web requests at
/lab_env_db. Open lab_app.py in vim for editing, and make it like this (additions in red this is file lab_app_v2.py on Github):
from flask import Flask, request, render_template
app = Flask(__name__)
app.debug = True # Make this False if you are no longer debugging
@app.route(/)
def hello():
return Hello World!
@app.route(/lab_temp)
def lab_temp():
import sys
import Adafruit_DHT
humidity, temperature = Adafruit_DHT.read_retry(Adafruit_DHT.AM2302, 4)
if humidity is not None and temperature is not None:
return render_template(lab_temp.html,temp=temperature,hum=humidity)
else:
return render_template(no_sensor.html)
@app.route(/lab_env_db)
def lab_env_db():
import sqlite3
conn=sqlite3.connect(/var/www/lab_app/lab_app.db)
curs=conn.cursor()
curs.execute(SELECT * FROM temperatures)
temperatures = curs.fetchall()
curs.execute(SELECT * FROM humidities)
humidities = curs.fetchall()
conn.close()
return render_template(lab_env_db.html,temp=temperatures,hum=humidities)
if __name__ == __main__:
app.run(host=0.0.0.0, port=8080)

The new code submits two Select statements to the database. The database returns to
collections, one for the temperature and one for the humidity tables. The code then calls
the lab_env_db.html template and passes the two collections to it.

Step 8.14
Lets now create the new template. Use vim to create a new file called lab_env_db.html
and copy this code in it:
<!DOCTYPE html>
<html lang=en>
<head>
<! Basic Page Needs
>

<meta charset=utf-8>
<title>Lab Conditions by RPi</title>
<meta name=description content=Lab conditions - RPi>
<meta name=author content=Peter Dalmaris>
<! Mobile Specific Metas
>
<meta name=viewport content=width=device-width, initial-scale=1>
<! FONT
>
<link href=//fonts.googleapis.com/css?family=Raleway:400,300,600 rel=stylesheet
type=text/css>
<! CSS
>
<link rel=stylesheet href=/static/css/normalize.css>
<link rel=stylesheet href=/static/css/skeleton.css>
<! Favicon
>
<link rel=icon type=image/png href=/static/images/favicon.png>
</head>
<body>
<div class=container>
<div class=row>
<div class=one-third column style=margin-top: 5%>
<strong>Showing all records</strong>
<h2>Temperatures</h2>
<table class=u-full-width>
<thead>
<tr>
<th>Date</th>
<th>&deg;C</th>
</tr>
</thead>
<tbody>
{% for row in temp %}
<tr>
<td>{{row[0]}}</td>
<td>{{%0.2f|format(row[1])}}</td>
</tr>
{% endfor %}
</tbody>
</table>
<h2>Humidities</h2>
<table class=u-full-width>
<thead>
<tr>
<th>Date</th>
<th>%</th>
</tr>
</thead>
<tbody>
{% for row in hum %}
<tr>
<td>{{row[0]}}</td>
<td>{{%0.2f|format(row[1])}}</td>

</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</body>
</html>

The lines where the template engine iterates through the collection objects are marked in
red.
For example, the temperature collection object is named temp. The iterator will take one
record at a time, called row. This record is actually a normal array. We can pick values
out of the array by using the index numbers. For example, row[1] will retrieve the value
from the cell with index 1, which is actually the second cell (arrays in Python are zeroindexed, which means that the first cell is index 0, the second is index 1 and so on).

Step 8.15
Restart uWSGI to make the changes effective and load the new page in your browser:
>restart uwsgi

You should see something like this when requesting


http://192.168.111.63:8080/lab_env_db:

CHAPTER ELEVEN
Select records to view by time and date
If you leave your RPi alone for a while, it will continue to capture sensor data and storing
it in the database. After a day or so, you will have 6*24=124 records for temperature and
another 124 for humidity. A week later, you will have 124*7=1008 of each, and so on.
Displaying all that data in a single page will produce very long pages, and will not be very
useful. It would be really nice to be able to get our RPi to retrieve and display records
within a time frame that we can specify.
In this lecture, I will show you how to make this possible in the easiest way I can think of.
We will update our application so that you can set the URL in the URL of your browsers
request. It will look like this: http://192.168.111.63/lab_env_db?from=2015-0413&to=2015-04-14.

Step 9.1
Lets work on the database first. We want to design an SQL query that will retrieve records
before and after a date/time we specify. To do this, we will use an SQL query like this:
SELECT * FROM humidities WHERE rDatetime BETWEEN 2015-04-12 AND 2015-04-13

Step 9.2
Lets try this statement in the SQLite database and see if it works. Log on to your RPi if
not already logged on, and navigate into your applications working directory:
>cd /var/www/lab_app/
>ls -al

Notice the size of the database file:


>-rw-rr 1 root root 76800 Apr 14 03:10 lab_app.db

Over 76,000 bytes! It has been growing over the last week.

Step 9.3
Start the SQLite console, with the database file as a parameter:
sqlite3 lab_app.db
SQLite version 3.7.13 2012-06-11 02:05:22
Enter .help for instructions
Enter SQL statements terminated with a ;
sqlite>

Step 9.4
Try out the sample SQL query from above:
sqlite> SELECT * FROM humidities WHERE rDatetime BETWEEN 2015-04-12 AND 2015-04-13;
2015-04-12 00:00:02|1|56.9000015258789
2015-04-12 00:10:01|1|56.7000007629395
2015-04-12 00:20:02|1|56.9000015258789
2015-04-12 00:30:02|1|56.7000007629395
2015-04-12 00:40:02|1|56.7000007629395
2015-04-12 00:50:01|1|56.2999992370606
2015-04-12 01:00:02|1|57
2015-04-12 01:10:02|1|57.0999984741211

(continues)
The database returns records inserted during the dates specified.

Step 9.5
We can be more specific and request records recorded with minute-level accuracy. For
example:
sqlite> SELECT * FROM humidities WHERE rDatetime BETWEEN 2015-04-12 18:00:00 AND 2015-04-12
19:00:00;
2015-04-12 18:00:01|1|51.2999992370606
2015-04-12 18:10:02|1|51.2000007629395
2015-04-12 18:20:02|1|51.2999992370606
2015-04-12 18:30:02|1|51.2000007629395
2015-04-12 18:40:01|1|51.2999992370606
2015-04-12 18:50:02|1|51.2999992370606

This statement returned records created on April 12, 2015, between 6pm and 7pm.
You can exit the SQLite console:
sqlite> .exit

Step 9.6
Ok, we have a way to get the records we need out of the database. We now need a way to
get the date/times into the URL, alongside our browser GET requests, and into the Python
script. And we want to do this in the lab_env_db method since that method is already
setup to retrieve data from the database.
We should be able to get all this done with minimal fuss.
Open up lab_app.py. In the top of the file, add the following import statements so that it
looks like this (this is lab_app_v3.py on Github):
from flask import Flask, request, render_template
import time
import datetime

We will be working with time and dates, so we are importing the time and datetime
packages.

Next, we will work only in method lab_env_db(), so I am only showing that part of the
code here. Edit that method so that it looks like this (this is also in lab_app_v3.py on
Github):
@app.route(/lab_env_db, methods=[GET])
def lab_env_db():
from_date_str = request.args.get(from,time.strftime(%Y-%m-%d %H:%M)) #Get the from date
value from the URL
to_date_str = request.args.get(to,time.strftime(%Y-%m-%d %H:%M)) #Get the to date value
from the URL
import sqlite3
conn=sqlite3.connect(/var/www/lab_app/lab_app.db)
curs=conn.cursor()
# curs.execute(SELECT * FROM temperatures)
# temperatures = curs.fetchall()
# curs.execute(SELECT * FROM humidities)
# humidities = curs.fetchall()
# conn.close()
curs.execute(SELECT * FROM temperatures WHERE rDateTime BETWEEN ? AND ?, (from_date_str,
to_date_str))
temperatures = curs.fetchall()
curs.execute(SELECT * FROM humidities WHERE rDateTime BETWEEN ? AND ?, (from_date_str,
to_date_str))
humidities = curs.fetchall()
conn.close()
return render_template(lab_env_db.html,temp=temperatures,hum=humidities)

I have commented out the old lines that relate to the database so that you can easily see
what has changed. As far as the database is concerned, we are constructing two new SQL
queries, one for the humidities table and one for the temperatures, so that they match the
query that we tested earlier.
In order to get the date/time range from the URL, in the very first line of this code I am
including the methods parameter. With it, I am telling Python that this method can
receive GET parameters as part of the HTTP request. A URL can contain key-value pairs
after the ? delimiter, and such requests are called GET requests.
In the 3rd and 4th lines, I use the request.args.get method to retrieve the values with the
from and to keys from the URL. I am also providing default values in case those are
not provided. This is done by using the python time method, and formatting the value
that this method in the format that the database can accept.

Step 9.7
Lets try out the new code. Save and close your editor, restart uWSGI:
>restart uwsgi

and type this URL in your browser:


http://192.168.111.63/lab_env_db?from=2015-04-13&to=2015-04-14

Your browser will display something like this:

It will contain records from April 14 2015 only. You can also try times:
http://192.168.111.63/lab_env_db?from=2015-04-12+13%3A25&to=2015-04-12+15%3A00

and you will get something like this:

where you only have records created on April 14, 2015, between 13:30 and 15:00.
Notice that the URL is HTTP encoded, which means that the white spaces and other
special characters have been replaced by code.
You can use a utility like this: http://www.url-encode-decode.com to encode your URLs.
For example, this: 2015-04-12 13:25 will be converted to this: 2015-04-12+13%3A25 so that it can
be transmitted correctly via HTTP.
Your browser does all this automatically, and at the moment we are just testing
functionality. Soon we will add user interface elements to make the process of creating
time and date ranges much easier.
Another thing to consider is the timezone in which you are located. By default, the
operating system is set to UTC, or Coordinated Universal Time. To see what your time
settings are try the date command:
>date
Tue Apr 14 04:48:56 BST 2015

Apparently, although I live in Sydney, Australia, the default setting for my brand-new
Rasbian installation is BST (British Summer Time).
Dont worry about the time zone issues at the moment, we will spend time adjusting our
application for that later.

Step 9.8
If the time and date strings that we typed in the URL are not correct (example:
http://192.168.111.63/lab_env_db?from=2015-04-13&to=201514), the SQL query will not return an
results. It will look like this:

If you would rather have it retrieve a preset date range, then we need to add some
validation to the lab_env_db method.
Go in lab_env_db and edit the method to look like this (this is lab_app_v4.py on Github):
@app.route(/lab_env_db, methods=[GET])
def lab_env_db():

import datetime
from_date_str = request.args.get(from,time.strftime(%Y-%m-%d 00:00)) #Get the from date
value from the URL
to_date_str = request.args.get(to,time.strftime(%Y-%m-%d %H:%M)) #Get the to date value
from the URL
if not validate_date(from_date_str): # Validate date before sending it to the DB
from_date_str = time.strftime(%Y-%m-%d 00:00)
if not validate_date(to_date_str):
to_date_str = time.strftime(%Y-%m-%d %H:%M) # Validate date before sending it to the DB

import sqlite3
conn=sqlite3.connect(/var/www/lab_app/lab_app.db)
curs=conn.cursor()
curs.execute(SELECT * FROM temperatures WHERE rDateTime BETWEEN ? AND ?, (from_date_str,
to_date_str))
temperatures = curs.fetchall()
curs.execute(SELECT * FROM humidities WHERE rDateTime BETWEEN ? AND ?, (from_date_str,
to_date_str))
humidities = curs.fetchall()
conn.close()
return render_template(lab_env_db.html,temp=temperatures,hum=humidities)
def validate_date(d):
try:
datetime.datetime.strptime(d, %Y-%m-%d %H:%M)
return True
except ValueError:
return False

I have highlighted the code that does the date validation in bold. There is a new method,
called validate_date, that accepts a string. It will try to match this string against a text
pattern, and if it is matched the method will return true, otherwise it will return false. The
try-except block is there to catch an exception, which is a condition that happens with, in
this case, it is not possible to match the date string with the pattern we have provided to
the strptime method.
Restart uWSGI, and refresh your browser (which still contains the incorrect URL from the
previous step, http://192.168.111.63/lab_env_db?from=2015-04-13&to=201514). This is what you
should be seeing now:

Step 9.9
Before going any further, lets tidy up. The lab_env_db method has grown a bit, and it will
continue to grow. It is better to simplify it at this point before we add more code to it.
A big part of what is happening inside this method is the decoding of the URL query
string. Lets split that part into its own method. Heres is the end result:
@app.route(/lab_env_db, methods=[GET])
def lab_env_db():
temperatures, humidities, from_date_str, to_date_str = get_records()
return render_template(lab_env_db.html,temp=temperatures,hum=humidities)
def get_records():
from_date_str = request.args.get(from,time.strftime(%Y-%m-%d 00:00)) #Get the from date
value from the URL
to_date_str = request.args.get(to,time.strftime(%Y-%m-%d %H:%M)) #Get the to date value
from the URL
if not validate_date(from_date_str): # Validate date before sending it to the DB
from_date_str = time.strftime(%Y-%m-%d 00:00)
if not validate_date(to_date_str):
to_date_str = time.strftime(%Y-%m-%d %H:%M) # Validate date before sending it to the DB
import sqlite3
conn=sqlite3.connect(/var/www/lab_app/lab_app.db)
curs=conn.cursor()
curs.execute(SELECT * FROM temperatures WHERE rDateTime BETWEEN ? AND ?, (from_date_str,
to_date_str))
temperatures = curs.fetchall()
curs.execute(SELECT * FROM humidities WHERE rDateTime BETWEEN ? AND ?, (from_date_str,
to_date_str))
humidities = curs.fetchall()
conn.close()
return [temperatures, humidities, from_date_str, to_date_str]

The new method, get_records, inherits the code that did the decoding of the URL and that
works with the database. It returns the two collections and the date strings in an array, so
that we can do multiple assignment in the first line of the lab_env_db method to local
variable.
Save the updated script, restart uWSGI and refresh your web page. It should reload as
nothing has changed!

Step 9.10
What we have now is nice, but not user friendly. Lets add some UI elements so that we
can just click on a button and retrieve records from a predefined time range. For example,
we can create buttons that retrieve records from the past 3, 6, 12 or 24 hours with a single
click.
To do this, we will need to work with the HTML template and the Python script. Lets start
with the HTML template. Open lab_env_db.html in your editor (the file is located inside
the templates directory), and update the contents with this (this is named
lab_env_db_v2.html in Github):
<!DOCTYPE html>
<html lang=en>

<head>
<! Basic Page Needs
>
<meta charset=utf-8>
<title>Lab Conditions by RPi</title>
<meta name=description content=Lab conditions - RPi>
<meta name=author content=Peter Dalmaris>
<! Mobile Specific Metas
>
<meta name=viewport content=width=device-width, initial-scale=1>
<! FONT
>
<link href=//fonts.googleapis.com/css?family=Raleway:400,300,600 rel=stylesheet
type=text/css>
<! CSS
>
<link rel=stylesheet href=/static/css/normalize.css>
<link rel=stylesheet href=/static/css/skeleton.css>
<! Favicon
>
<link rel=icon type=image/png href=/static/images/favicon.png>
</head>
<body>
<div class=container>
<div class=row>
<div class=eleven columns>
<form id=range_select action = /lab_env_db method=GET>
<div class=one column>
<input type=radio name=range_h value=3 id=radio_3 /><label
for=radio_3>3hrs</label>
</div>
<div class=one column>
<input type=radio name=range_h value=6 id=radio_6 /><label
for=radio_6>6hrs</label>
</div>
<div class=one column>
<input type=radio name=range_h value=12 id=radio_12 /><label
for=radio_12>12hrs</label>
</div>
<div class=one column>
<input type=radio name=range_h value=24 id=radio_24 /><label
for=radio_24>24hrs</label>
</div>
</form>
</div>
</div>
<div class=row>
<div class=one-third column style=margin-top: 5%>
<strong>Showing all records</strong>
<h2>Temperatures</h2>
<table class=u-full-width>
<thead>
<tr>
<th>Date</th>
<th>&deg;C</th>

</tr>
</thead>
<tbody>
{% for row in temp %}
<tr>
<td>{{row[0]}}</td>
<td>{{%0.2f|format(row[2])}}</td>
</tr>
{% endfor %}
</tbody>
</table>
<h2>Humidities</h2>
<table class=u-full-width>
<thead>
<tr>
<th>Date</th>
<th>%</th>
</tr>
</thead>
<tbody>
{% for row in hum %}
<tr>
<td>{{row[0]}}</td>
<td>{{%0.2f|format(row[2])}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</body>
<script src=//code.jquery.com/jquery-1.11.2.min.js></script>
<script src=//code.jquery.com/jquery-migrate-1.2.1.min.js></script>
<script>
jQuery(#range_select input[type=radio]).click(function(){
jQuery(#range_select).submit();
});
</script>
</html>

The new code is highlighted in bold letters. The first block is a form that contains a list of
radio buttons. Each button represents a block of time that the user can select.
Since we want the user on simply click on one of the buttons and have the form submitted,
I have inserted some Javascript code, using jQuery, to make this easy. The second
highlighted block loads the jQuery library from the jquery.com repository and then
submits the form when one of the radio buttons is clicked.
Update the code, save the file and reload the page in your web browser. You should see
this:

Notice the 4 buttons at the top of the page. If you click any of them, the page will behave
as if it is reloading. What happened is that the jQuery statement at the bottom of the
HTML code detected the click and submitted the form.
Have a look at the URL. You will see something like this:
http://192.168.111.63/lab_env_db?range_h=6

Notice the range_h value? In my case it carries the value 6 because I click on the radio
button 6hrs. I now need to update my code in the Python file so that it can read this
value and calculate the start and end date/times for this time range.

Step 9.11
Open lab_app.py in your editor. Update the get_records method with this code:
def get_records():
from_date_str = request.args.get(from,time.strftime(%Y-%m-%d 00:00)) #Get the from date
value from the URL
to_date_str = request.args.get(to,time.strftime(%Y-%m-%d %H:%M)) #Get the to date value

from the URL


range_h_form = request.args.get(range_h,); #This will return a string, if field range_h
exists in the request
range_h_int = nan #initialise this variable with not a number
try:
range_h_int = int(range_h_form)
except:
print range_h_form not a number
if not validate_date(from_date_str): # Validate date before sending it to the DB
from_date_str = time.strftime(%Y-%m-%d 00:00)
if not validate_date(to_date_str):
to_date_str = time.strftime(%Y-%m-%d %H:%M) # Validate date before sending it to the DB
# If range_h is defined, we dont need the from and to times
if isinstance(range_h_int,int):
time_now = datetime.datetime.now()
time_from = time_now - datetime.timedelta(hours = range_h_int)
time_to = time_now
from_date_str = time_from.strftime(%Y-%m-%d %H:%M)
to_date_str = time_to.strftime(%Y-%m-%d %H:%M)
import sqlite3
conn=sqlite3.connect(/var/www/lab_app/lab_app.db)
curs=conn.cursor()
curs.execute(SELECT * FROM temperatures WHERE rDateTime BETWEEN ? AND ?, (from_date_str,
to_date_str))
temperatures = curs.fetchall()
curs.execute(SELECT * FROM humidities WHERE rDateTime BETWEEN ? AND ?, (from_date_str,
to_date_str))
humidities = curs.fetchall()
conn.close()
return [temperatures, humidities, from_date_str, to_date_str]

The new code (in bold letters) retrieves the hours range from the URL. Because it is
transmitted to the server as a character, we first convert it to an integer. Then, from this
integer we calculate the date range variables, and finally we pass them to the SQL query.
Try out. Save the file and restart uWSGI. Then reload the page, and click on the 3hrs
button. You will get records that were created in the last three hours:

We will be doing more work with the user interface later. For now, we have a way to
record sensor readings every 10 minutes, and retrieve those readings using a simple radiobutton based date/time range mechanism.
Before adding more form widgets, lets add the ability to visualise our data. We will do
this in the next section.

CHAPTER TWELVE
Add Visualisations
I think it would be nice to convert our sensor data into good-looking charts. Google offers
an API for creating charts, called Google Chart API. Here I will show you how to use it to
convert the data from your sensor into a chart that is embedded in your existing page.
The Google Chart API is extensive. You can create amazing visualisations with it. All the
details are documented here: https://developers.google.com/chart/ .
For now, Id like to show you how to create something like this:

To make it possible, we will work exclusively on the client side, the HTML page.

Step 10.1
Lets start by adding HTML code to create a placeholder for the charts in the page. We
will create one placeholder for the temperature chart, and one for the humidity. Both will
be placed at the right side of the page, like in the example screenshot above.
Open lab_env_db.html in your editor (inside the templates directory). Just before the
</div> tag above </body>, insert this code (all changes and additions are in a file titled
lab_env_db_v3.html on Github):
<div class=two-thirds column style=margin-top: 5%>
<div class=row>
<div class=row>
<div class=three columns>
<div id=chart_temps></div>
<div id=chart_humid></div>
</div>
</div>
</div>
</div>
</div>
</body>

This creates two div boxes, one with id chart_temps and one with id chart_humid.
This is where the two charts will be placed by the Google Charts API.

Step 10.2
Just before the </html> tag at the very end of the file, add this Javascript code:
<script type=text/javascript src=https://www.google.com/jsapi?autoload={modules:
[{name:visualization,version:1,packages:[corechart]}]}></script>
<script>
google.load(visualization, 1, {packages: [corechart]});
google.setOnLoadCallback(drawChart);
function drawChart() {
var data = new google.visualization.DataTable();
data.addColumn(datetime, Time);
data.addColumn(number, Temperature);
data.addRows([
{% for row in temp %}
[new Date({{row[0][0:4]}},{{row[0][5:7]}},{{row[0][8:10]}},{{row[0][11:13]}},{{row[0][14:16]}}),
{{%0.2f|format(row[2])}}],
{% endfor %}
]);
var options = {
width: 600,
height: 563,
hAxis: {
title: Date,
gridlines: { count: {{temp_items}}, color: #CCC },
format: dd-MMM-yyyy HH:mm },
vAxis: {
title: Degrees

},
title: Temperature,
curveType: function //Makes line curved
};
var chart = new google.visualization.LineChart(document.getElementById(chart_temps));
chart.draw(data, options);
}
</script>
<script>
google.load(visualization, 1, {packages: [corechart]});
google.setOnLoadCallback(drawChart);
function drawChart() {
var data = new google.visualization.DataTable();
data.addColumn(datetime, Time);
data.addColumn(number, Humidity);
data.addRows([
{% for row in hum %}
[new Date({{row[0][0:4]}},{{row[0][5:7]}},{{row[0][8:10]}},{{row[0][11:13]}},{{row[0][14:16]}}),
{{%0.2f|format(row[2])}}],
{% endfor %}
]);
var options = {
width: 600,
height: 563,
hAxis: {
title: Date,
gridlines: { count: {{hum_items}}, color: #CCC },
format: dd-MMM-yyyy HH:mm },
vAxis: {
title: Percent
},
title: Humidity,
curveType: function //Makes line curved
};
var chart = new google.visualization.LineChart(document.getElementById(chart_humid));
chart.draw(data, options);
}
</script>
</html>

Step 10.3
We need to pass the total number of records in the humidities and temperatures collections
because the Google Charts API needs this information to construct the grid for the charts.
Open lab_env_db.py in your editor, and adjust the lab_env_db method to be like this
(titled lab_app_v7.py on Github):
@app.route(/lab_env_db, methods=[GET])
def lab_env_db():
temperatures, humidities, from_date_str, to_date_str = get_records()
return render_template(lab_env_db.html,temp = temperatures,hum= humidities, temp_items=
len(temperatures),hum_items= len(humidities))

The additions are highlighted in bold. We simply use the len method to count how many
items we have in the two collections, and pass this integer to the template.

Step 10.4
Unless theres a typo, this should work. Save the updated Python script and restart
uWSGI.
Reload your browser, and marvel at your new charts!

If you are not familiar with Javascript, it is worth the effort of studying this code in some
detail. Essentially, for each chart, we are constructing a data table that contains

information about the axis and the data for each row. For each row, we plot the value,
rounded to two decimals, against a time stamp. This way, we are constructing a date and
time chart. This is documented in the Chart Documentation, which I strongly recommend
you spend some time with.
In the following sections, we will improve the user experience by adding elements to
allow the user to select an arbitrary date range, address the issue of the time zone, and
make it possible to send the current sensor data set to Plotly, an amazing cloud
visualisation and analysis service, where you can do things like this:

CHAPTER THIRTEEN
Improve the UI
In this section we will improve the existing user interface by adding widgets that allow the
user to easily select date/time ranges for the charts.
To do this, I will introduce JQuery and the DateTimePicker widget.
JQuery is one of the most widely used Javascript frameworks. JQuery makes Javascript
easy to use by providing a library of useful functions and visual elements. I will show you
some of these features as we go through the content of this section.
JQuery is a technology that runs on the client side (browser). Therefore, most of the work
we will be doing in this section will be done on the template files.

Step 11.1
Lets meet JQuery and the DateTimePicker widget. Here is jquery.com from where you
can download or find the CDN link for the library file:

There are several widgets that allow date and time selection that work with JQuery. The

one that I prefer to use in this project is datetimepicker because it is very configurable and
looks great:

Step 11.2
To make jQuery and the widget available to our template, we need to include them into
our page. For jQuery, the easiest way to use the CDN (Content Delivery Network) link.
This way, our template will always be using the official library maintained by the
maintainers of jQuery. This was already done in the previous sections, so there is no more
work to do.

To add the datetime widget, download the ZIP file from


http://xdsoft.net/jqplugins/datetimepicker/. A CDN is not supported.

Take the .css file and copy it into the static/css folder, and the .js file into the
static/javascript folder. If the javascript folder doesnt exist, create it.
Your static folder should look like this now:

Step 11.3
Open the lab_env_db.html template file and add these two lines right under the reference
to the jquery-1.11.2.min.js file (this version of the file is titled lab_env_db_v4.html on
Github):
<script src=//code.jquery.com/jquery-1.11.2.min.js></script>
<link rel=stylesheet type=text/css href=/static/css/jquery.datetimepicker.css/ >
<script src=/static/javascript/jquery.datetimepicker.js></script>

Step 11.4
Next, we will define two input fields in which we will place the two datetime picker
widgets.
Lets add a new CSS row that contains a new form. Within that form, well have two
inputs that will serve as datetime pickers. Clicking on a button will submit the two dates
selected by the user.
Copy this code immediately under the <body> tag:
<div class=container>
<div class=row>
<form id=datetime_range action=/lab_env_db method=GET>
<! <div class=row> >
<div class=three columns>
<label for=from>From date</label>
<input class=u-full-width id=datetimepicker1 type=text value={{from_date}} name=from>
</div>
<! </div> >
<! <div class=row> >
<div class=three columns>
<label for=to>To date</label>
<input class=u-full-width id=datetimepicker2 type=text value={{to_date}} name=to>
</div>
<! </div> >
<! <div class=row> >
<div class=two columns>
<input class=button-primary type=submit value=Submit style=position:relative; top: 28px
id=submit_button />
</div>
<! </div> >
</form>
</div>

The two inputs are highlighted bold.


Notice that the default date and times to be displayed in each field are provided by the
Flash application via the from_date and to_date variables. We will need to adjust our
Python code to include these variables in the parameters that are passed to the template.

Step 11.5
Scroll towards the bottom of the lab_env_db.html template file, to where the Javascript
code is.
Under the inclusion of the datetimepicker library, paste this code:
<script>
jQuery(#datetimepicker1).datetimepicker(
{
format:Y-m-d H:i,
defaultDate:{{from_date}}
});
jQuery(#datetimepicker2).datetimepicker({
format:Y-m-d H:i,
defaultDate:{{to_date}}
});
jQuery(#range_select input[type=radio]).click(function(){
jQuery(#range_select).submit();
});
</script>

This code will place the datetimepickers to the input fields with names datetimepicker1
and datetimepicker2, and configure the way they should display their values.
Here is the Gist for this file and version: http://txplo.re/1klcHDS

Step 11.6
Lets switch to the Python script. Open lab_app.py in your editor. Go to the lab_env_db
function, and change the return statement to this:
@app.route(/lab_env_db, methods=[GET])
def lab_env_db():
temperatures, humidities, from_date_str, to_date_str = get_records()
return render_template( lab_env_db.html, temp = temperatures,
hum = humidities,
from_date = from_date_str,
to_date = to_date_str,
temp_items = len(temperatures),
hum_items = len(humidities))

The new text is in bold.


The Gist for this file and version is: http://txplo.re/1l2EY2v

Step 11.7
To see the changes, restart uwsgi and refresh the page:
>restart uwsgi
uwsgi start/running, process 13058

Try changing the date and time range, and clicking Submit. Confirm that the records
retrieved are actually within the range you selected.
It is now getting easier to interact with the database.
There is a pending issue though: the dates and times as recorded on the RPi server are in
whichever time zone the server is set for. Usually, this is UTC. If you are at a different
timezone, the time displayed in the Google chart and in the records table will not match
the actual time that the record was created, but it will be offset.
Lets correct this problem in the next section.

CHAPTER FOURTEEN
Adjusting for actual time zone
It would be really nice for the application to be able to detect the users time zone based
on the information provided by the browser, and then to automatically adjust the times of
each record for that time zone. This way, times shown in the records table and in the
Google chart will be correct for the users time zone.
So there are two components for our timezone solution: client (Javascript) and server
(Python).

Step 12.1
We will start with the client. We want to detect the users timezone by getting information
from their browser. We can then pass this information to the server by piggybacking it in a
form GET request.
There is a very useful Javascript library that can help us get the timezone information:
https://bitbucket.org/pellepim/jstimezonedetect
This library is available via CDN, so there is nothing to download to the RPi. Open
lab_env_db.html in your editor and add this line under the previous Javascript inclusions:
<script src=https://cdnjs.cloudflare.com/ajax/libs/jstimezonedetect/1.0.4/jstz.min.js ></script>

Step 12.2
We will transmit the timezone information to the server with the use of hidden fields.
Since we have to forms, we will add a timezone hidden field to each one. The timezone
information will travel to the server when the user submit either form.
As long as the hidden form is within the form tags, its exact position does not matter. I
usually place them right before the submit button or right after the <form> tag if there is
no button (the second form only had radio buttons).
Here are the two forms, with the hidden fields in bold (the updated file for this lecture is
titled lab_env_db_v5.html on Github):
<div class=row>
<form id=datetime_range action=/lab_env_db method=GET>
<! <div class=row> >
<div class=three columns>
<label for=from>From date</label>
<input class=u-full-width id=datetimepicker1 type=text value={{from_date}} name=from>
</div>
<! </div> >
<! <div class=row> >
<div class=three columns>
<label for=to>To date</label>
<input class=u-full-width id=datetimepicker2 type=text value={{to_date}} name=to>
</div>

<! </div> >


<! <div class=row> >
<div class=two columns>
<input type=hidden class=timezone name=timezone />
<input class=button-primary type=submit value=Submit style=position:relative; top: 28px
id=submit_button />
</div>
<! </div> >
</form>
</div>
<div class=row>
<div class=eleven columns>
<form id=range_select action = /lab_env_db method=GET>
<input type=hidden class=timezone name=timezone />
<div class=one column>
<input type=radio name=range_h value=3 id=radio_3 /><label for=radio_3>3hrs</label>
</div>
<div class=one column>
<input type=radio name=range_h value=6 id=radio_6 /><label for=radio_6>6hrs</label>
</div>
<div class=one column>
<input type=radio name=range_h value=12 id=radio_12 /><label for=radio_12>12hrs</label>
</div>
<div class=one column>
<input type=radio name=range_h value=24 id=radio_24 /><label for=radio_24>24hrs</label>
</div>
</form>
</div>
</div>

Step 12.3
Before each form is submitted, the page must first get the timezone for the user and put
the value in the hidden fields. We will use jQuery for this. Here is the example code:
timezone = jstz.determine();
jQuery(.timezone).val(timezone.name());

We get the timezone with jstsz.determine() and copy the value to the hidden field with the
id timezone in the second line.
We want this code to be executed with the realisation of a form submit event. For the
datetimepicker form, the code is this:
jQuery( #datetime_range ).submit(function( event ) {
timezone = jstz.determine();
jQuery(.timezone).val(timezone.name());
});

For the radio buttons form, it looks like this:


jQuery(#range_select input[type=radio]).click(function(){
timezone = jstz.determine();
jQuery(.timezone).val(timezone.name());
jQuery(#range_select).submit();
});

Complete file Gist for this version: http://txplo.re/1N6luAG

Step 12.4
Upload the new template code to the server if you havent done so already.

Step 12.5
In Python, we will do time and date calculations using Arrow, a popular package for just
this purpose.
Lets install it in our apps virtual environment:
>/var/www/lab_app/venv/bin/pip install arrow
Downloading/unpacking arrow
Downloading arrow-0.5.4.tar.gz (81Kb): 81Kb downloaded
Running setup.py egg_info for package arrow
Downloading/unpacking python-dateutil (from arrow)
Downloading python-dateutil-2.4.2.tar.gz (209Kb): 209Kb downloaded
Running setup.py egg_info for package python-dateutil
Downloading/unpacking six>=1.5 (from python-dateutil->arrow)
Downloading six-1.9.0.tar.gz
Running setup.py egg_info for package six
no previously-included directories found matching documentation/_build
Installing collected packages: arrow, python-dateutil, six
Running setup.py install for arrow
Running setup.py install for python-dateutil
Running setup.py install for six
no previously-included directories found matching documentation/_build
Successfully installed arrow python-dateutil six
Cleaning up

Step 12.6
Open lab_app.py in your editor. Start by importing the arrow library to the script at the top
of the file:
import arrow

The rest of the changes are in methods lab_env_db and get_records. You should spend a
bit of time looking at the code in these methods in order to understand how timezone
information is used to convert time from one timezone to the other.
A few things to remember:
If a timezone is not defined, then we use Etc/UTC as the default.
Times used in the database records are in the timezone of the server (RPi). It is a
good practice to always adjust your server so that its timezone is Etc/UTC. You can
use dpkg-reconfigure tzdata to make changes.
Here is the code for lab_env_db() (the updated file for this lecture is titled
lab_app_v9.py on Github):
@app.route(/lab_env_db, methods=[GET]) #Add date limits in the URL #Arguments: from=2015-0304&to=2015-03-05
def lab_env_db():
temperatures, humidities, timezone, from_date_str, to_date_str = get_records()
# Create new record tables so that datetimes are adjusted back to the user browsers time zone.
time_adjusted_temperatures = []
time_adjusted_humidities = []
for record in temperatures:

local_timedate = arrow.get(record[0], YYYY-MM-DD HH:mm).to(timezone)


time_adjusted_temperatures.append([local_timedate.format(YYYY-MM-DD HH:mm),
round(record[2],2)])
for record in humidities:
local_timedate = arrow.get(record[0], YYYY-MM-DD HH:mm).to(timezone)
time_adjusted_humidities.append([local_timedate.format(YYYY-MM-DD HH:mm), round(record[2],2)])
print rendering lab_env_db.html with: %s, %s, %s % (timezone, from_date_str, to_date_str)
return render_template(lab_env_db.html, timezone = timezone,
temp = time_adjusted_temperatures,
hum = time_adjusted_humidities,
from_date = from_date_str,
to_date = to_date_str,
temp_items = len(temperatures),
hum_items = len(humidities))

The code for get_records() is this:


def get_records():
from_date_str = request.args.get(from,time.strftime(%Y-%m-%d 00:00)) #Get the from date
value from the URL
to_date_str = request.args.get(to,time.strftime(%Y-%m-%d %H:%M)) #Get the to date value
from the URL
timezone = request.args.get(timezone,Etc/UTC);
range_h_form = request.args.get(range_h,); #This will return a string, if field range_h
exists in the request
range_h_int = nan #initialise this variable with not a number
print REQUEST:
print request.args
try:
range_h_int = int(range_h_form)
except:
print range_h_form not a number
print Received from browser: %s, %s, %s, %s % (from_date_str, to_date_str, timezone,
range_h_int)
if not validate_date(from_date_str): # Validate date before sending it to the DB
from_date_str = time.strftime(%Y-%m-%d 00:00)
if not validate_date(to_date_str):
to_date_str = time.strftime(%Y-%m-%d %H:%M) # Validate date before sending it to the DB
print 2. From: %s, to: %s, timezone: %s % (from_date_str,to_date_str,timezone)
# Create datetime object so that we can convert to UTC from the browsers local time
from_date_obj = datetime.datetime.strptime(from_date_str,%Y-%m-%d %H:%M)
to_date_obj = datetime.datetime.strptime(to_date_str,%Y-%m-%d %H:%M)
# If range_h is defined, we dont need the from and to times
if isinstance(range_h_int,int):
arrow_time_from = arrow.utcnow().replace(hours=-range_h_int)
arrow_time_to = arrow.utcnow()
from_date_utc = arrow_time_from.strftime(%Y-%m-%d %H:%M)
to_date_utc = arrow_time_to.strftime(%Y-%m-%d %H:%M)
from_date_str = arrow_time_from.to(timezone).strftime(%Y-%m-%d %H:%M)
to_date_str = arrow_time_to.to(timezone).strftime(%Y-%m-%d %H:%M)
else:
#Convert datetimes to UTC so we can retrieve the appropriate records from the database
from_date_utc = arrow.get(from_date_obj, timezone).to(Etc/UTC).strftime(%Y-%m-%d %H:%M)
to_date_utc = arrow.get(to_date_obj, timezone).to(Etc/UTC).strftime(%Y-%m-%d %H:%M)
conn = sqlite3.connect(/var/www/smart/temp_hum2.db)
curs = conn.cursor()

curs.execute(SELECT * FROM temperatures WHERE rDateTime BETWEEN ? AND ?,


(from_date_utc.format(YYYY-MM-DD HH:mm), to_date_utc.format(YYYY-MM-DD HH:mm)))
temperatures = curs.fetchall()
curs.execute(SELECT * FROM humidities WHERE rDateTime BETWEEN ? AND ?,
(from_date_utc.format(YYYY-MM-DD HH:mm), to_date_utc.format(YYYY-MM-DD HH:mm)))
humidities = curs.fetchall()
conn.close()
return [temperatures, humidities, timezone, from_date_str, to_date_str]

Here is the complete app script Gist for this version: http://txplo.re/1WsscWE

Step 12.7
Notice that in the lab_env_db method, we have created two new arrays. In those arrays we
copy the data from those that are retrieved from the database, but with their times adjusted
for the users local timezone. These arrays only contain 2 columns, as opposed to the three
columns that come out of the database; we only need the time/date and value. So, we need
to make a small change in the template file to accommodate for this change.
Open lab_env_db.html and search for this string:
{{%0.2f|format(row[2])}}

Change both instances it to this:


{{%0.2f|format(row[1])}}

Also make the same change to the Javascript code for the Google Charts API. Search for
format(row[2])

and change both instances to:


format(row[1])

Here is the complete template Gist for this version: http://txplo.re/1LL1cyR

Step 12.8
Upload and save the new code to your RPi, and restart uWSGI.
If your page is not loading (502 Bad Gateway), check the error logs:
>tail /var/log/uwsgi/lab_app_uwsgi.log

Step 12.9
Refresh your browser. If you are using Google Chrome, turn on the developer tools so you
can see the contents of the GET request when you submit one of the forms.
For example if I click on the 6hr button, I get this:

Check the URL, and that it contains a value for the timezone parameter. You can see the
same thing in the developer tools section: click on Network, then on the first request in the
left pane of the window. The request URL contains the timezone information from the
users browser.
Not much more left to do. In the next section, we will add code so that the user can easily
move between the past records and current values pages. And in the last section, we will
make it easy to sent our records to Plotly so that we can use Plotlys data analysis tools.

CHAPTER FIFTEEN
Link the two pages
In this section, we will make it easy for the user to move between the two pages that make
up our application. In the database records page, well add a link to the current conditions
page.
And in the current conditions page, well add the radio buttons for the time ranges so that
the user can quickly retrieve recent history.

Step 13.1
Lets start with the Current Conditions page. Open lab_temp.html in your editor.
This file currently looks like this (Gist): http://txplo.re/1KRHw8s

Step 13.2
The time range radio buttons form we want to add in lab_temp.html already exists in
lab_env_db.html. You can open this file in your editor and find that code segment, or just
copy the following to lab_temp.html (paste it right after the </div> that closes the <div
class=row> (the updated file is titled lab_temp_v3.html on Github):
<div class=row>
<div class=eleven columns>
<form id=range_select action = /lab_env_db method=GET>
<input type=hidden class=timezone name=timezone />
<div class=one column>
<input type=radio name=range_h value=3 id=radio_3 /><label for=radio_3>3hrs</label>
</div>
<div class=one column>
<input type=radio name=range_h value=6 id=radio_6 /><label for=radio_6>6hrs</label>
</div>
<div class=one column>
<input type=radio name=range_h value=12 id=radio_12 /><label for=radio_12>12hrs</label>
</div>
<div class=one column>
<input type=radio name=range_h value=24 id=radio_24 /><label for=radio_24>24hrs</label>
</div>
</form>
</div>

Remember that we have some Javascript code that deals with the timezones, so we will
need to also copy that across to lab_temp.html. Find this code in lab_env_db.html, or copy
the code from below (this code goes right after the </body>:
<script src=//code.jquery.com/jquery-1.11.2.min.js></script>
<script src=https://cdnjs.cloudflare.com/ajax/libs/jstimezonedetect/1.0.4/jstz.min.js ></script>
<script>
jQuery( document ).ready(function() {
timezone = jstz.determine();
jQuery(#timezone).text(timezone.name());
});
jQuery(#range_select input[type=radio]).click(function(){
timezone = jstz.determine();
jQuery(.timezone).val(timezone.name());
jQuery(#range_select).submit();
});
</script>

This Gist contains the final version for lab_temp.html: http://txplo.re/1klcXmj

Step 13.3
Load lab_temp in your browser, and you should see this:

You can click one of the radio buttons to see records from recent past:

Notice that my chart here has a strange range, from 400 to -1200 degrees centigrade.
Obviously this is not right. What happened is that the sensor produced a false reading of
-999.00 at 2015-04-21 11:50, probably because I was fiddling around with the wires.
It would be nice to add some code to filter out values like these that are obviously
erroneous, instead of letting them through and corrupting the chart. I will leave that up to
you to do if you wish.

Step 13.4
Lets move to lab_env_db.html. We will add a simple link that we can follow to view the
current temperature and humidity in the lab. We will add this link under the two form
fields used by the datetimepicker widget.
Paste this code right before the <form> tag for the time range radio buttons (the updated
file is titled lab_env_db_v6.html on Github):
<div class=one column>
<a href=/lab_temp style=position:relative;top:15px>Current</a>
</div>

Your lab_env_db.html should now be this (Gist): http://txplo.re/1OhQ686

Step 13.5
Load lab_env_db in your browser, you should be seeing this:

Notice the Current link? If you click on it, you will reach the Current Conditions page.
Interconnecting the two pages of the application was an easy task considering that we
already had most of the necessary code.
In the next and last section of this project, we will make it possible for data from our
application to be transmitted to Plotly for analysis.
With Plotly, you can visualise and analyse your data on the cloud, and share it, if you
want, with the rest of the world.

CHAPTER SIXTEEN
Uploading to Plotly
In this section, we will upload sensor data from our application database to Plotly. Once
your data is on Plotly, we will visualise it and scratch the surface of what is possible to do
in terms of data analysis.

Step 14.1
Start by creating a free account on Plotly. Got to plotly.io, click on the Sign Up button,
and follow the process:

Step 14.2
Plotly provides extensive documentation on how to use it with Python. You can start here:
https://plot.ly/python/getting-started/.
Go ahead and install the Plotly package for Python. With this package, connecting your
Python application to Plotly is (almost) trivial.
From your application directory in RPi, run the PIP tool to install the package in the apps
virtual directory:
root@raspberrypi:/var/www/lab_app# venv/bin/pip install plotly
Downloading/unpacking plotly
Downloading plotly-1.6.16.tar.gz (103Kb): 103Kb downloaded
Running setup.py egg_info for package plotly
Downloading/unpacking requests (from plotly)
Downloading requests-2.6.0.tar.gz (450Kb): 450Kb downloaded
Running setup.py egg_info for package requests
Requirement already satisfied (use upgrade to upgrade): six in ./venv/lib/python2.7/site-packages
(from plotly)
Downloading/unpacking pytz (from plotly)
Downloading pytz-2015.2.tar.bz2 (166Kb): 166Kb downloaded
Running setup.py egg_info for package pytz
Installing collected packages: plotly, requests, pytz
Running setup.py install for plotly
Running setup.py install for requests
Running setup.py install for pytz
Successfully installed plotly requests pytz
Cleaning up

Step 14.3
Lets test the package. All communication between your computer and Plotly is signed
with your account API. Before going any further, log on to your Plotly account and get
your API key.
To find yours, click on your username in the top right corner of the page, then Settings.
The API key is in the first box. Keep this window open so you can copy the key and
username in the next step.

Step 14.4
Now we can initialise the Plotly package.
In the code below, replace DemoAccount with your actual account name, and
your_api_key with your actual API key. Assuming you are still in the root directory of
your application, enter this command:
>venv/bin/python -c import plotly; plotly.tools.set_credentials_file(username=DemoAccount,
api_key=your_api_key)

Step 14.5
Your account is now registered with the Plotly package. Lets try to create a new chart. We
can use the Python command line interpreter and an example from the documentation.
Type venv/bin/python in the command line to enter the command line interpreter:
>venv/bin/python
Python 2.7.3 (default, Mar 18 2014, 05:13:23)
[GCC 4.6.3] on linux2
Type help, copyright, credits or license for more information.
>>>

Step 14.6
One line at a time, copy and paste this program into the command line:
import plotly.plotly as py
from plotly.graph_objs import *
trace0 = Scatter(
x=[1, 2, 3, 4],
y=[10, 15, 13, 17]
)
trace1 = Scatter(
x=[1, 2, 3, 4],
y=[16, 5, 11, 9]
)
data = Data([trace0, trace1])
unique_url = py.plot(data, filename = basic-line)

The process looked like this for me:


>>> import plotly.plotly as py
>>> from plotly.graph_objs import *
>>> trace0 = Scatter(
x=[1, 2, 3, 4],
y=[10, 15, 13, 17]
)
>>> trace1 = Scatter(
x=[1, 2, 3, 4],
y=[16, 5, 11, 9]
)
>>> data = Data([trace0, trace1])
>>> unique_url = py.plot(data, filename = basic-line)
/var/www/lab_app/venv/local/lib/python2.7/site-packages/requests/packages/urllib3/util/ssl_.py:79:
InsecurePlatformWarning:
A true SSLContext object is not available. This prevents urllib3 from configuring SSL appropriately
and may cause certain SSL connections to fail. For more information, see
https://urllib3.readthedocs.org/en/latest/security.html#insecureplatformwarning.

Step 14.7
Plotly complained about an issue with SSL. If you received this error message you can
safely ignore it for now. The chart has been created anyway. At some later time, you can
upgrade your Python virtual environment to a newer version, 2.7.9 or later, where this
issue has been corrected.
We need to get the URL for the new chart. The URL is stored in the unique_url variable.
Type this in the Python interpreter:
>>> print unique_url
https://plot.ly/~futureshocked/8

The URL (for my chart) is highlighted in bold. Copy to your web browser. You should see
something like this:

Step 14.8
Ok, we know that we can send chart data to our Plotly account. Lets update our
application so that we can send sensor data on demand, with the click of a button.
By studying the example program, and looking through the documentation, we learn that
we need to create a Data object. In it, we create trace objects. The trace object will contain
the data to be displayed, and some attributes, like the text for the axes, the name of the
chart, and more.
I would like my chart to be a time series, since time is arranged on the horizontal axis. I
would also like to display both humidity and temperature on a single chart, with the
Celsius scale on the left vertical axis and the Humidity Percent scale on the right vertical
axis.
This is the result I would like to achieve:

Step 14.9
From a user interface perspective, I would like to user to be able to click on a link in the
lab_env_db page, and have the data sent to Plotly. When the chart is ready, I would like a
link to appear under the Plotly link which I can click and go to the chart.
This involves asynchronous calls at two levels. First, when I click on the Plotly link, an
asynchronous GET request is sent to the application on the RPi requesting the chart.
Next, the Plotly package makes its own request to Plotly, and retrieves the URL for the
chart, when the chart is ready. Last, this URL is returned to the browser, as a response to
the first asynchronous request.

Step 14.10
jQuery will make all this easy. Lets start with the HTML template page. Open
lab_env_db.html in your editor.
Add the link first. Copy this code just before the link to the lab_temp page:
<div class=one column>
<a href= id=plotly style=position:relative;top:15px>Plotly</a>
</div>

The href attribute is empty, because its content depends on the data that we want to sent to
Plotly. It will be populated by the jQuery code (next).

Step 14.11
We will also add code for a link that will be able to click when the chart is ready. Here is
the code:
<div class=row>
<a href= id=plotly_url target=_blank></a><span id=plotly_wait></span>
</div>

Paste this code right above the records row.

Step 14.12
Finally, the jQuery. Here it is:
jQuery(#plotly).click(function(){
jQuery(#plotly_wait).text(Sending data);
jQuery(#plotly_url).text();
{% autoescape false %}
jQuery.get(/to_plotly?{{query_string}})
{% endautoescape %}
.done(function( data ) {
jQuery(#plotly_url).attr(href,data);
jQuery(#plotly_url).text(Click to see your plot);
jQuery(#plotly_wait).text();
});
return false; //This is so that the click on the link does not cause the page to refresh
});

Paste this right after the jQuery code for the range_select buttons. This is the code that
generates the URL to be called when we click on the link. It points to a method in our
application, which we have not created yet.
This method constructs the data objects for the chart, and transmits them to Plotly using
the Plotly API package. It then reads the response from the API that contains the new
chart URL and puts that URL in our page so that the user can click on it.
Here is the full code in lab_env_db.html at this point (Gist): http://txplo.re/1N6lTTR

Step 14.13
Lets switch to lab_app.py. Here is the code that makes up the to_plotly method (you can
find this version on Github, titled lab_app_v10.py):
@app.route(/to_plotly, methods=[GET]) #This method will send the data to ploty.
def to_plotly():
import plotly.plotly as py
from plotly.graph_objs import *
temperatures, humidities, timezone, from_date_str, to_date_str = get_records()
# Create new record tables so that datetimes are adjusted back to the user browsers time zone.
time_series_adjusted_tempreratures = []
time_series_adjusted_humidities = []
time_series_temprerature_values = []
time_series_humidity_values = []
for record in temperatures:
local_timedate = arrow.get(record[0], YYYY-MM-DD HH:mm).to(timezone)
time_series_adjusted_tempreratures.append(local_timedate.format(YYYY-MM-DD HH:mm))
time_series_temprerature_values.append(round(record[2],2))
for record in humidities:
local_timedate = arrow.get(record[0], YYYY-MM-DD HH:mm).to(timezone)
time_series_adjusted_humidities.append(local_timedate.format(YYYY-MM-DD HH:mm)) #Best to pass
datetime in text
#so that Plotly respects it
time_series_humidity_values.append(round(record[2],2))
temp = Scatter(
x=time_series_adjusted_tempreratures,

y=time_series_temprerature_values,
name=Temperature
)
hum = Scatter(
x=time_series_adjusted_humidities,
y=time_series_humidity_values,
name=Humidity,
yaxis=y2
)
data = Data([temp, hum])
layout = Layout(
title=Temperature and humidity in Peters lab,
xaxis=XAxis(
type=date,
autorange=True
),
yaxis=YAxis(
title=Celcius,
type=linear,
autorange=True
),
yaxis2=YAxis(
title=Percent,
type=linear,
autorange=True,
overlaying=y,
side=right
)
)
fig = Figure(data=data, layout=layout)
plot_url = py.plot(fig, filename=lab_temp_hum)
return plot_url

Copy this code somewhere in the script. It will respond to GET requests only, construct
the data object as per the parameters provided by the user, transmit the data object to
Plotly, get the URL for the new chart from the Plotly response, and return the URL to the
jQuery code in the template page.
Here is the complete code for lab_app.py (Gist): http://txplo.re/1OhQbst

Step 14.14
Lets try out the new code. Restart uWSGI and load the records page:

Step 14.15
Click on the Plotly link to trigger the chart request. The Sending data message will
appear for a few seconds, and then it will be replaced by Click to see your plot link:

Step 14.16
Click on it, and marvel on your new chart!

Step 14.17
Ok, my chart is not very glamorous because that infamous bad record in my dataset. But
here is one I created earlier:

Step 14.18
It works! So what else can you do with chart on Plotly? Start by checking out the rest of
the tabs in the graph plot box. Theres Data, Code and Extras. Then, click on Edit Graph
for numerous options on themes and various functions.
For example, clicking on Traces gives you access to options that allow you to manipulate
either of the lines. There is also a function that can calculate the best fitting line to a trace,
which is good for detecting trends. Plotly is a very capable online resource, worth
spending a bit of time to become familiar with.

CHAPTER SEVENTEEN
Conclusion
Congratulations!
You have completed this introductory project on the Raspberry Pi. You are now familiar
with some of the things that you can do with it, and with the flexibility and power it offers
when compared to other technologies, like the Arduino.
Lets recap what you have learned:
You know how to install and configure Rasbian,
How to install and use Python and the Python Virtual Environment,
You understand what is the web application stack and its components,
You understand the use of naming system of GPIOs,
You can interact with simple and more complicated devices connected to the GPIOs,
You are familiar with a variety of fundamental open source software and services,
like Nginx, Flask, uWSGI, Upstart, Skeleton, SQLite3, cron, Javascript, Jquery.
You have experience with popular web services, like Google Charts and Plotly.
And, most important, you have created a full-stack application on your Raspberry Pi
in which you applied all these components in a single system.
Whats next? Its up to you! However, I can suggest a couple of ideas.
How about you extend your existing application? Add more sensors, add automatic
notifications when these sensors reach a trigger value, think about controlling something.
How about working on the user interface to make it it more interesting? You can
experiment with different widgets or data visualisations. You can figure out a way to make
your application web site accessible from the Web. You can publish your data to data
logging services. You can connect your Raspberry Pi to a thermostat and get it to control
your home environment!
Or, forget about the application you just created, and start from scratch. Think big, or think
small, it is up to you and it depends on what drives you and how you learn.
But whatever you decide to do, maybe I can help you. Send me a message and let me
know.
This was just the beginning!

Table of Contents
About this book
Video course companion
Discussion forum and email list
To make the most of this book
CHAPTER ONE - Introduction
CHAPTER TWO - About the Raspberry Pi
CHAPTER THREE - Install an Operating System
CHAPTER FOUR - Setup Python and try out the GPIOs
CHAPTER FIVE - Use a DHT22 sensor
CHAPTER SIX - Create a web application stack on the RPi
CHAPTER SEVEN - Serving static assets
CHAPTER EIGHT - Flask templates and CSS styling
CHAPTER NINE - View sensor readings via a web browser
CHAPTER TEN - Setup a simple database and use it to log sensor data
CHAPTER ELEVEN - Select records to view by time and date
CHAPTER TWELVE - Add Visualisations
CHAPTER THIRTEEN - Improve the UI
CHAPTER FOURTEEN - Adjusting for actual time zone
CHAPTER FIFTEEN - Link the two pages
CHAPTER SIXTEEN - Uploading to Plotly
CHAPTER SEVENTEEN - Conclusion

Potrebbero piacerti anche