14

A RASPBERRY PI–BASED WEATHER MONITOR

Image

When you find that you need more computational power or support for peripherals such as USB or high-definition multimedia interface (HDMI) video, you leave the realm of microcontrollers like the Arduino and enter the computer realm. The Raspberry Pi is a tiny computer that can perform high-level tasks such as these quite well, especially when compared to the Arduino.

Like the Arduino, the Raspberry Pi is used in numerous interesting projects. Although it can fit in the palm of your hand, it’s a full computer (you can connect a monitor and keyboard to it), which has made it popular among educators and makers.

In this chapter, you’ll use the Raspberry Pi together with a temperature and humidity sensor (DHT11) to build a web-based temperature- and humidity-monitoring system. The code you run on the Pi will start the Bottle web server, which will listen for incoming connections. When you access the Pi’s Internet Protocol (IP) address on the local network, the Bottle server should serve a web page containing a chart of the weather data. The handler on the Pi will communicate with the DHT11 sensor, retrieve data, and return it to the client, which will use the flot library to plot the sensor data in the browser. You’ll also provide web-based control for a light-emitting diode (LED) attached to the Raspberry Pi. (This is just to demonstrate how you can control external devices via the Web using the Pi.)

NOTE

We’ll use Python 2.7 in this project. The Raspbian OS on the Raspberry Pi comes with Python 2.7 (run as python on shell) and Python 3 (run as python3 on shell) installed. The code, as written, is compatible with both.

The Hardware

Like any laptop or desktop computer sold today, a Pi has a central processing unit (CPU), random access memory (RAM), USB ports, video output, audio output, and network connectivity. But unlike most computers, the Pi is cheap: about $35. In addition, the Pi can easily interface with external hardware, thanks to its onboard general-purpose input/output (GPIO) pins, making it ideal for all kinds of embedded hardware projects. You’ll connect it up with a DHT11 temperature and humidity sensor to monitor your environment.

The DHT11 Temperature and Humidity Sensor

The DHT11 (see Figure 14-1) is a popular sensor that measures temperature and humidity. It has four pins—VDD (+), GND (−), DATA, and a fourth that is not used. The DATA pin connects to a microcontroller (in this case, a Raspberry Pi), and both input and output run through this pin. You’ll use the Adafruit Python library Adafruit_Python_DHT to communicate with a DHT11 to retrieve temperature and humidity data.

Image

Figure 14-1: The DHT11 temperature and humidity sensor

The Raspberry Pi

As I write this, three models of Raspberry Pi are available: the Raspberry Pi 1 Model A+, the Raspberry Pi 1 Model B+, and the Raspberry Pi 2 Model B. For this project, I used an older Raspberry Pi Model B Rev 2, but the pin numbers used in this project are compatible across all models, and the code will work on any of the models without changes.

Figure 14-2 shows a Raspberry Pi Model B computer that has two USB ports, an HDMI connector, a composite video output jack, an audio output jack, a micro USB port for power supply, an Ethernet port, and 26 GPIO pins arranged as two columns of 13 pins each. It also has an SD card slot on the bottom side of the board (not shown). The Raspberry Pi uses a Broadcom BCM2835 chip, which has an ARM CPU that runs at 700 MHz and consumes very little power. (This explains why the Pi doesn’t need any giant heat sinks to cool it down, like a desktop computer does). Model B also has 512MB of SDRAM. Most of these details are not important for this project, but they might come in handy if you want to impress folks with all the specs of your latest pocket computer.

Image

Figure 14-2: The Raspberry Pi Model B

Setting Up Your Pi

Unlike the Arduino, you can’t just plug the Raspberry Pi in to your computer and start coding. As a full-fledged computer, the Pi needs an operating system and a few peripherals. At a minimum, I suggest the peripherals shown in Figure 14-3.

• An 8GB or higher-capacity SD card with a suitable operating system

• A Pi-compatible USB Wi-Fi adapter

• A Pi-compatible power supply (The official recommendation is to use a 5V 1200 mA supply, or a 5V 2500 mA supply if you need to use all the USB ports.)

• A case to protect your precious Pi

• A keyboard and mouse (Consider a wireless combination that takes up only a single USB port for convenience.)

• A composite video cable or an HDMI cable (See Appendix C for more on using an HDMI cable with the Pi.)

Image

Figure 14-3: Recommended set of Raspberry Pi peripherals

NOTE

Be sure to check the list of peripherals known to work with the Pi at http://elinux.org/RPI_VerifiedPeripherals before purchasing.

Installing and Configuring Software

Now it’s time to set up your Raspberry Pi and get it ready for Python. I’ll cover the required steps briefly in the following section, but you should watch the setup videos available at the Raspberry Pi Foundation’s “Getting Started with NOOBS” page (http://www.raspberrypi.org/help/noobs-setup/) before installing.

The Operating System

Your Pi’s operating system and files will reside on an external SD card. Although you have your choice of several operating systems, I suggest installing Raspbian. The operating system needs to be installed in a particular way, and rather than bore you here with the details (which may well change), I’ll ask you to take a look at “RPi Easy SD Card Setup” on the Linux wiki at http://elinux.org/RPi_Easy_SD_Card_Setup or the “Using NOOBS” instructions at the same link, which is a bit more beginner-friendly.

Initial Configuration

Once you’ve installed your operating system, it’s time for the first boot. Plug in your formatted SD card, hook up a composite video cable to a TV or monitor, attach the keyboard and mouse, and connect the Pi to the power supply. When the Pi boots, a program called raspi-config should start, and you should see various configuration options. (You’ll find documentation for raspi-config at the Raspberry Pi Foundation’s website: https://www.raspberrypi.org/documentation/configuration/raspi-config.md). Modify the configuration as follows:

1. Select Expand Filesystem to make use of the full SD card.

2. Select Enable Boot to Desktop/Scratch and then select Desktop.

3. Select Change Time Zone and set your time zone under Internationalization Options.

4. Go to Advanced Options and enable Overscan.

5. Go to SSH and enable remote command line access in the Advanced Options menu by choosing the Enable or Disable SSH Server option.

Now select Finish; the Pi should reboot and display a desktop.

Wi-Fi Setup

You’ll connect your Pi wirelessly in this project. Assuming that you installed a compatible Wi-Fi adapter (after consulting the http://elinux.org/ site), Raspbian should automatically recognize the adapter when you plug it in. Assuming that all goes well, you’ll set up a static IP address by editing the network configuration file using the built-in Nano editor. (Nano has a bare-bones UI, which can take some getting used to. The most important thing to remember is to press CTRL-X and enter Yes to save your file and exit.)

You’ll run the commands in a terminal. Open LXTerminal (it should be installed with Raspbian) and enter the following:

sudo nano /etc/network/interfaces

This command opens the interfaces file, which you’ll use to configure the network settings, as shown here:

auto lo

iface lo inet loopback
iface eth0 inet dhcp

allow-hotplug wlan0
iface wlan0 inet manual
wpa-roam /etc/wpa_supplicant/wpa_supplicant.conf
iface default inet static
address 192.x.x.x 
netmask 255.255.255.0
gateway 192.x.x.x

Add or modify the address, netmask, and gateway lines at the end of the file to suit your local network. Enter the netmask for your network (likely 255.255.255.0), enter your network’s gateway (run ipconfig on Linux from a Linux terminal; press WINDOWS and R keys and then run ipconfig /all on Windows; or choose System Preferences Network on OS X, and give your Pi a static network IP address that’s different from that of any other devices on your network.

Now connect the Pi to your Wi-Fi network using the WiFi Config utility; you should see a shortcut for it on the desktop. (If you get stuck, try the Adafruit tutorials at https://learn.adafruit.com/. If all goes well, your Pi should connect to the Internet with the built-in browser Midori.)

Setting Up the Programming Environment

Next you’ll install your development environment, including the RPi.GPIO package needed to talk to external hardware, the Bottle web framework, and the tools needed to install other Python packages on your Raspberry Pi. Make sure you’re connected to the Internet and run these commands in a terminal, one at a time:

sudo apt-get update
sudo apt-get install python-setuptools
sudo apt-get install python-dev
sudo apt-get install python-rpi.gpio
sudo easy_install bottle

Now, download the latest version of the flot JavaScript plotting library from http://www.flotcharts.org/, expand it to create the flot directory, and copy this directory into the same folder as your program.

wget http://www.flotcharts.org/downloads/flot-x.zip
unzip flot-x.zip
mv flot myProjectDir/

Next, install the Adafruit_Python_DHT library (https://github.com/adafruit/Adafruit_Python_DHT/), which you’ll use to retrieve data from the DHT11 sensor attached to your Pi, by running these commands in a terminal:

git clone https://github.com/adafruit/Adafruit_Python_DHT.git
cd Adafruit_Python_DHT
sudo python setup.py install

The Raspberry Pi should now be set up with all the software you need to build your weather monitor.

Connecting via SSH

Rather than connect your Pi to a monitor and control it using a connected mouse and keyboard, it’s much easier to work with the Pi by logging in to it from a desktop or laptop. Linux and OS X have built-in support for this sort of thing, in the form of Secure Shell (SSH). If you’re using Windows, install PuTTY to connect to the Pi.

The following listing shows a typical SSH session:

 moksha:~ mahesh$ ssh [email protected]
 [email protected]'s password:
 pi@raspberrypi ~ $ whoami
   pi
   pi@raspberrypi ~ $

In this session, at , I’ve logged in to the Pi from my computer by entering the ssh command, the Pi’s default username (pi), and its IP address as ssh username@ip_address. When you enter ssh , you should be prompted for a password. The default is raspberry.

Once you think you’ve logged in to the Pi, make sure by entering the whoami command . If the response is pi as shown earlier, you have logged in properly.

NOTE

It’s a good idea to change your Pi’s username and password to make it more secure. For more hints on working remotely with the Raspberry Pi, see Appendix C.

The Bottle Web Framework

To monitor and control the Pi via a web interface, you’ll need to have it run a web server. You’ll use Bottle, a Python web framework with a simple interface. (In fact, the entire library consists of a single source file named bottle.py.) Here’s the code needed to serve a simple web page using Bottle:

from bottle import route, run

@route('/hello')
def hello():
    return "Hello Bottle World!"

run(host='192.168.x.x', port=xxxx, debug=True)

This code uses the Python decorator @route to define a route to a URL or path for the client to use to send a data request. The defined route calls the routing function, which returns a string. The run() method starts the Bottle server, which can now accept connections from clients. (Be sure to supply your own IP address and port number.) Note that I’ve set the debug flag to True to make it easier to diagnose problems.)

Now open a browser on any computer connected to the local network and enter http://192.168.4.4:8080/hello. Connect to your Pi and Bottle should serve you a web page with the line “Hello Bottle World!”. With just a few lines of code, you’ve created a web server.

The client will make requests to the server (Bottle, running on the Pi) using the Asynchronous JavaScript and XML (AJAX) framework. To make your AJAX calls easy to write, you’ll use the popular jQuery library.

Plotting with flot

Now let’s look at how you’ll plot your data. The flot library has an easy-to-use and powerful API that lets you create nice-looking graphs with minimal code. Basically, you set up some Hypertext Markup Language (HTML) to hold a chart and provide an array of values to plot, and Flot handles the rest, as shown in this example. (You’ll find this code in the file simple-flot.html in the book’s code repository.)

   <html>
   <head>
     <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
     <title>SimpleFlot</title>
  <style>
       .demo-placeholder {
           width: 80%;
           height: 80%;
       }
     </style>
   <script language="javascript" type="text/javascript"
             src="flot/jquery.js"></script>
     <script language="javascript" type="text/javascript"
             src="flot/jquery.flot.js"></script>
     <script language="javascript" type="text/javascript">
   $(document).ready(function() {
          // create plot
     var data = [];
          for(var i = 0; i < 500; i ++) {
        data.push([i, Math.exp(-i/100)*Math.sin(Math.PI*i/10)]);
          }
     var plot = $.plot("#placeholder", [data]);
       });
   </script>
   </head>

   <body>
     <h3>A Simple Flot Plot</h3>
     <div class="demo-container">
    <div id="placeholder" class="demo-placeholder"></div>
     </div>
   </body>
   </html>

At , you define a CSS class (demo-placeholder) to set the width and height of the placeholder element to hold your plot (which you’ll define in the body of the document). At , you declare the JavaScript files for the libraries you’ll use in this HTML file: jquery.js and flot.js. (Note that jQuery comes bundled with flot, so you don’t need to download it separately. Also, ensure that you put the top-level flot directory into the same directory that contains all the source code for this project.)

Next, you use JavaScript to generate values to plot. At , you use the jQuery method $(document).ready() to define a function to be executed by the browser as soon as the HTML file is loaded. Inside that function, at , you declare an empty JavaScript array and then loop 500 times, adding a value of the form [i, y] to this array . Each value represents the x- and y-coordinates of this interesting function (chosen somewhat arbitrarily).

Image

At , you call plot() from the flot library to do the plotting. At , the plot() function takes as input the id of the HTML element that contains the plot (the placeholder element). When you load the resulting HTML file in a browser, you should see the plot shown in Figure 14-4.

Image

Figure 14-4: A sample plot created with flot

I used flot’s default settings here, but you can customize your flot plots extensively by adjusting colors, using data points instead of lines, adding legends and titles, making the plot interactive, and more. (You’ll see how when you design the chart for the weather data in “Plotting the Data” on page 285.)

Shutting Down the Pi

Never abruptly disconnect the power supply to a running Raspberry Pi or you’ll likely corrupt your filesystem, making the Pi unbootable. To shut down the Pi’s user interface if you’re connected to it directly or from your computer, enter the following via SSH:

sudo shutdown -h now

NOTE

Before you run the previous command, you need to be sure that you are logged in to your Pi. Otherwise, you may end up shutting down your host computer—if you’re running Linux, for instance.

A few seconds after you enter the shutdown command, the Pi’s yellow indicator LED should blink exactly 10 times. Now you can pull the plug safely.

Building the Hardware

In addition to the Pi and the peripherals mentioned earlier, you’ll need one of each of the following, as well as hookup wires:

• DHT11 sensor

• 4.7k ohm resistor

• 100 ohm resistor

• Red LED

• Breadboard

Figure 14-5 shows how to wire everything up. The VDD pin of the DHT11 is connected to the +5V (pin #2 on the Pi), the DHT11 DATA pin is connected to pin #16 on the Pi, and the DHT11 GND pin is connected to GND on the Pi (pin #6). A 4.7k ohm resistor is connected between DATA and VDD. The cathode (negative) of the LED is connected to GND via a 100 ohm resistor, and the anode (positive) is connected to pin #18 of the Pi.

Image

Figure 14-5: A schematic of the connections among the Raspberry Pi, the DHT11 circuit, and the LED

You can use a solderless breadboard to connect the DHT11 and LED circuits and test the setup, as shown in Figure 14-6. Then move the setup to a custom enclosure once you have it working to your satisfaction.

Image

Figure 14-6: The Raspberry Pi, DHT11, and LED connected with a breadboard

The Code

Now, you’ll develop the code to run on the Raspberry Pi. (If you want to see the full project code, skip ahead to “The Complete Code” on page 290.) Here’s the main() function:

   def main():
       print 'starting piweather...'
       # create parser
      parser = argparse.ArgumentParser(description="PiWeather...")
       # add expected arguments
       parser.add_argument('--ip', dest='ipAddr', required=True)
       parser.add_argument('--port', dest='portNum', required=True)

       # parse args
       args = parser.parse_args()

       # GPIO setup
      GPIO.setmode(GPIO.BOARD)
      GPIO.setup(18, GPIO.OUT)
      GPIO.output(18, False)
       # start server
      run(host=args.ipAddr, port=args.portNum, debug=True)

At , you set up a command line argument parser with two required arguments: --ip for the IP address where the server should be started and --port for the server port number. At , you start the GPIO pin setup. You use BOARD mode to indicate you’ll be using the pin-numbering convention based on the board’s physical layout. At , you set up pin #18 as an output because you plan to write data out to it to control the LED, and at , you set the pin value to False so the LED is turned off when you start. Then you start Bottle by supplying the IP address and port number, and set debug to True to monitor any warning messages .

Handling Sensor Data Requests

Now let’s take a quick look at the function that handles sensor data requests:

 @route('/getdata', method='GET')
 def getdata():
    RH, T = Adafruit_DHT.read_retry(Adafruit_DHT.DHT11, 23)
      # return dictionary
    return {"RH": RH, "T": T}

This method defines a route called /getdata . When the client accesses the URL /getdata defined by this route, the getdata() method is called , which uses the Adafruit_DHT module to retrieve humidity and temperature data . At , the retrieved data is returned as a dictionary, which will be available on the client as a JavaScript Object Notation (JSON) object. JSON objects consist of lists of name-value pairs that can be read in as objects. In this case, getdata() returns a JSON object with two pairs: one for the humidity reading (RH) and one for temperature (T).

Plotting the Data

The plot() function handles the client’s plot requests. The first part of this function defines the HTML <head> section, which sets the Cascading Style Sheets (CSS) style and loads the necessary JavaScript code, as shown here:

   @route('/plot')
   def plot():
     return '''
   <html>
   <head>
       <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
       <title>PiWeather</title>
       <style>
       .demo-placeholder {
       width: 90%;
       height: 50%;
       }
       </style>
            <script language="javascript" type="text/javascript"
          src="jquery.js"></script>
            <script language="javascript" type="text/javascript"
          src="jquery.flot.js"></script>
            <script language="javascript" type="text/javascript"
          src="jquery.flot.time.js"></script>

The plot() function is a Bottle route for the /plot URL, which means that the plot() method will be called when you connect to this URL. At , plot() returns the entire HTML data as a single string, which will be displayed by the client’s web browser. The initial lines in the listing are the HTML headers, CSS size declaration for the plot, and code to include the flot library—all similar to the setup that produces the flot chart example in Figure 14-4.

The <body> element of the code shows the overall structure of the HTML.

   <body>
            <div id="header">
               <h2>Temperature/Humidity</h2>
            </div>

            <div id="content">
                <div class="demo-container">
                  <div id="placeholder" class="demo-placeholder"></div>
                </div>
          <div id="ajax-panel"> </div>
            </div>
       <div>
         <input type="checkbox" id="ckLED" value="on">Enable Lighting.
         <span id="data-values"> </span>
       </div>

   </body>
   </html>

At , you simply add a title for the plot. Add a <placeholder> element at , which will be filled later by the flot JavaScript code. At , you define an HTML element with the ID ajax-panel, which will display any AJAX errors, and at , you create a checkbox element with the ID ckLED, which controls the LED connected to the Raspberry Pi. Finally, you create another HTML element with the ID data-values , where you display sensor data when the user clicks a data point in the plot. The JavaScript code will use all of these IDs to access and modify the corresponding elements.

Now let’s dive in to the embedded JavaScript code that initiates sensor data requests as well as toggles the LED on and off. This code goes into the <script language="javascript"...> tag under the <head> of the HTML data.

 $(document).ready(function() {

      // plot options
    var options = {
          series: {
              lines: {show: true},
              points: {show: true}
                  },
        grid: {clickable: true},
        yaxes: [{min: 0, max: 100}],
          xaxes: [{min: 0, max: 100}],
     };

     // create empty plot
   var plot = $.plot("#placeholder", [[]], options);

The ready() function at is called by the browser when the HTML data has been fully loaded. At , you declare an options object to customize your plot. In this object, you tell the plot to display both lines and points, and at , you allow the plot grid to be clicked (for querying values). You also set the axes limits at . At , create the actual plot by calling plot() with three arguments: the ID of the element in which you want the plot to appear, an array of values (which is empty to start), and the options object you just set up.

Next let’s look at the JavaScript code that gets the sensor data:

      // initialize data arrays
    var RH = [];
      var T = [];
      var timeStamp = [];
      // get data from server
    function getData() {
          // AJAX callback
        function onDataReceived(jsonData) {
            timeStamp.push(Date());
              // add RH data
            RH.push(jsonData.RH);
              // removed oldest
            if (RH.length > 100) {
                  RH.splice(0, 1);
              }
              // add T data
              T.push(jsonData.T);
              // removed oldest
              if (T.length > 100) {
                  T.splice(0, 1);
              }
            s1 = [];
              s2 = [];
              for (var i = 0; i < RH.length; i++) {
                   s1.push([i, RH[i]]);
                   s2.push([i, T[i]]);
              }
              // set to plot
            plot.setData([s1, s2]);
              plot.draw();
              }
          // AJAX error handler
        function onError(){
              $('#ajax-panel').html('<p><strong>Ajax error!</strong> </p>');
          }

          // make the AJAX call
        $.ajax({
              url: "getdata",
              type: "GET",
              dataType: "json",
              success: onDataReceived,
              error: onError
          });
      }

At , you initialize empty arrays for the temperature and humidity values, and a timeStamp array that stores the time when each value is collected. You define a getData() method at that you’ll call periodically using a timer. At , you define a onDataReceived() method that you’ll set as a callback method for the AJAX call. Because in JavaScript you can define functions within functions that can be used like regular variables, you can define onDataReceived() within the getData() function and then pass it to the AJAX call as a callback function.

The onDataReceived() function stores a timestamp for the data point at by creating a JavaScript Date object. Data from the server is passed into OnDataReceived() using the jsonData object, and at , you push the temperature data from this object into an array. At , you remove the oldest element in the array if the number of elements exceeds 100, which produces a scrolling graph similar to the one you created for the Arduino light sensor project in Chapter 12. You process the humidity data the same way.

At , the collected data is formatted to make it suitable to be passed to the plot() method. Since you’re plotting two variables simultaneously, you need an array with three layers of the form:

[[[i0,RH0], [i1,RH1],...],[[i0,T0],[i1,T1]]

The data is set and plotted at .

At , you define an error callback, which AJAX will use in case of a failure to display errors, using the HTML element with the ID ajax-panel that you set up previously. At , you make the actual AJAX call, which specifies the URL getdata, the Bottle route. The call uses the HTTP GET() method to request data from the server in the json format. The AJAX setup call sets the OnDataReceived() method as the success callback and onError() as the error callback. This AJAX call returns immediately, and the callback methods are activated asynchronously when data becomes available.

The update() Method

Now let’s look at the update() method that calls getData() every second.

         // define an update function
         function update() {
             // get data
           getData();
             // set timeout
           setTimeout(update, 1000);
      }
      // call update
          update();

The update() method first calls getData() at and then uses the JavaScript setTimeout() method at to call itself after 1,000 milliseconds. As a result, getData() is called every second to request sensor data from the server.

The JavaScript Handler for the LED

Now let’s look at the JavaScript handler for the LED checkbox, which sends an AJAX request to the web server.

      // define the click handler for the LED control button
    $('#ckLED').click(function() {
        var isChecked = $("#ckLED").is(":checked") ? 1:0;
        $.ajax({
              url: '/ledctrl',
              type: 'POST',
              data: { strID:'ckLED', strState:isChecked }
          });
      });

At , you define a click handler for the checkbox with the ID ckLED that you created earlier in the HTML. This click handler function is called each time the user clicks the checkbox. The function stores the state of the checkbox ; then the code at calls AJAX with the URL /ledctrl and the HTTP request type POST, which sends the checked state as data.

The server-side handler for this AJAX request sets the GPIO pin of the Pi to on or off as per the request.

 @route('/ledctrl', method='POST')
   def ledctrl():
      val = request.forms.get('strState')
      on = bool(int(val))
      GPIO.output(18, on)

At , you define the Bottle route for the URL /ledctrl, which is a decorator for the ledctrl() method that handles this request. At , you use the Bottle request object to access the string value of the strState parameter sent by the client code, which is converted to a Boolean at . Use the GPIO.output() method on pin #18 to toggle the LED connected to this pin on or off .

Adding Interactivity

You also want to provide some interactivity for the plot. The flot library provides a way for users to click data points to get values, which you enabled earlier by passing clickable:true in options when you called the plot() function. Here, you define the function to be called when a data point is clicked:

      $("#placeholder").bind("plotclick", function (event, pos, item) {
           if (item) {
              plot.highlight(item.series, item.datapoint);
              var strData = ' [Clicked Data: ' +
                              timeStamp[item.dataIndex] + ': T = ' +
                              T[item.dataIndex] + ', RH = ' + RH[item.dataIndex]
                              + ']';
              $('#data-values').html(strData);
           }
       });
   });

   </script>
   </head>

This function calls the flot highlight() method at to draw a ring around the point clicked. At , it prepares a string to be displayed in the HTML element with the ID data-values. When a data point is clicked, flot passes in an item object to the function, which has a member called dataIndex. You use this index to retrieve the relevant data from the timestamp, temperature, and humidity arrays defined in the ready() function. Finally, the string is added to the HTML element .

This concludes the embedded JavaScript in your Python code, but you need one more Bottle route to find the JavaScript files that the web page needs, as shown here:

@route('/<filename:re:.*.js>')
def javascripts(filename):
    return static_file(filename, root='flot')

This code tells the Bottle server to find these files in the subdirectory flot/ at the same level as your program.

The Complete Code

You can find the complete code listing for this project at https://github.com/electronut/pp/tree/master/piweather/piweather.py.

from bottle import route, run, request, response
from bottle import static_file
import random, argparse
import RPi.GPIO as GPIO
from time import sleep
import Adafruit_DHT
@route('/hello')
def hello():
    return "Hello Bottle World!"

@route('/<filename:re:.*.js>')
def javascripts(filename):
    return static_file(filename, root='flot')

@route('/plot')
def plot():
    return '''
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>PiWeather</title>
    <style>
    .demo-placeholder {
    width: 90%;
    height: 50%;
    }
    </style>
    <script language="javascript" type="text/javascript"
       src="jquery.js"></script>
    <script language="javascript" type="text/javascript"
       src="jquery.flot.js"></script>
    <script language="javascript" type="text/javascript"
       src="jquery.flot.time.js"></script>
    <script language="javascript" type="text/javascript">

$(document).ready(function() {

    // plot options
    var options = {
        series: {
            lines: {show: true},
            points: {show: true}
                },
        grid: {clickable: true},
        yaxes: [{min: 0, max: 100}],
        xaxes: [{min: 0, max: 100}],
    };

    // create empty plot
    var plot = $.plot("#placeholder", [[]], options);

    // initialize data arrays
    var RH = [];
    var T = [];
    var timeStamp = [];
    // get data from server
    function getData() {
        // AJAX callback
        function onDataReceived(jsonData) {
            timeStamp.push(Date());
            // add RH data
            RH.push(jsonData.RH);
            // removed oldest
            if (RH.length > 100) {
                RH.splice(0, 1);
            }
            // add T data
            T.push(jsonData.T);
            // removed oldest
            if (T.length > 100) {
                T.splice(0, 1);
            }
            s1 = [];
            s2 = [];
            for (var i = 0; i < RH.length; i++) {
                s1.push([i, RH[i]]);
                s2.push([i, T[i]]);
            }
            // set to plot
            plot.setData([s1, s2]);
            plot.draw();
        }

        // AJAX error handler
        function onError(){
            $('#ajax-panel').html('<p><strong>Ajax error!</strong> </p>');
        }

        // make the AJAX call
        $.ajax({
            url: "getdata",
            type: "GET",
            dataType: "json",
            success: onDataReceived,
            error: onError
        });
      }

      // define an update function
      function update() {
         // get data
         getData();
         // set timeout
         setTimeout(update, 1000);
      }

      // call update
      update();

      // define click handler for LED control button
      $('#ckLED').click(function() {
          var isChecked = $("#ckLED").is(":checked") ? 1:0;
          $.ajax({
              url: '/ledctrl',
              type: 'POST',
              data: { strID:'ckLED', strState:isChecked }
          });
      });

     $("#placeholder").bind("plotclick", function (event, pos, item) {
          if (item) {
              plot.highlight(item.series, item.datapoint);
              var strData = ' [Clicked Data: ' +
                            timeStamp[item.dataIndex] + ': T = ' +
                            T[item.dataIndex] + ', RH = ' + RH[item.dataIndex]
                            + ']';
              $('#data-values').html(strData);
          }
     });
});

</script>
</head>

<body>
    <div id="header">
        <h2>Temperature/Humidity</h2>
    </div>

    <div id="content">
        <div class="demo-container">
            <div id="placeholder" class="demo-placeholder"></div>
        </div>
        <div id="ajax-panel"> </div>
    </div>
    <div>
        <input type="checkbox" id="ckLED" value="on">Enable Lighting.
        <span id="data-values"> </span>
    </div>

</body>
</html>
'''

@route('/getdata', method='GET')
def getdata():
    RH, T = Adafruit_DHT.read_retry(Adafruit_DHT.DHT11, 23)
    # return dictionary
    return {"RH": RH, "T": T}

@route('/ledctrl', method='POST')
def ledctrl():
    val = request.forms.get('strState')
    on = bool(int(val))
    GPIO.output(18, on)

# main() function
def main():
    print 'starting piweather...'
    # create parser
    parser = argparse.ArgumentParser(description="PiWeather...")
    # add expected arguments
    parser.add_argument('--ip', dest='ipAddr', required=True)
    parser.add_argument('--port', dest='portNum', required=True)

    # parse args
    args = parser.parse_args()

    # GPIO setup
    GPIO.setmode(GPIO.BOARD)
    GPIO.setup(18, GPIO.OUT)
    GPIO.output(18, False)
    # start server
    run(host=args.ipAddr, port=args.portNum, debug=True)

# call main
if __name__ == '__main__':
    main()

Running the Program

Once you’ve connected the Raspberry Pi to the DHT11 and LED circuit, SSH into the Pi from your computer and enter the following (substituting the IP address and port you have set up for your Pi):

sudo python piweather.py --ip 192.168.x.x --port xxx

Now open a browser and enter the IP address and port for your Pi into the browser’s address bar in this form:

http://192.168.x.x:port/plot

You should see a plot similar to the one in Figure 14-7.

Image

Figure 14-7: The resulting web page and chart from a sample run of piweather.py

If you click any data point in the graph, the Clicked Data section should update to show more information about that point. After 100 points are collected, the graph will start scrolling horizontally as new data comes in. (Click the Enable Lighting checkbox to toggle the LED on and off.)

Summary

In this project, you built a Raspberry Pi–based weather monitor that plots temperature and humidity data over a web interface. These were some of the concepts covered in this project:

• Setting up a Raspberry Pi

• Using Raspberry Pi GPIO pins to talk to hardware

• Interfacing with the DHT11 temperature and humidity sensor

• Creating a web server using the Bottle Python web framework

• Making charts using the flot JavaScript library

• Building a client-server application

• Controlling hardware over a web interface

Experiments!

Try refining this project with these modifications:

1. Provide a way to export the sensor data. One simple way would be to maintain a list of (T, RH) tuples on the server, then write a Bottle route for /export, and, in this method, return the values in CSV format. Modify the /plot route so that it includes HTML code to place an Export button on the web page. When the button is clicked, have the corresponding AJAX code call the export() method on the server, which will send the CSV values to be displayed in the browser. These values can then be copied or saved by the user from the browser window.

2. The program plots the DHT11 data, but after 100 values, it starts scrolling. What if you want to see historic data over a longer period? One way would be to maintain a longer list of (T, RH) tuples on the server and then modify the server code to send HTML data with a button and the necessary JavaScript code to toggle between plotting the full range of data or just the most recent 100 values. How will you retrieve old data from before the Pi was switched off? (Hint: write the data to a text file when the server exits and load old data when it starts.) To really make this project scalable, use a database, such as SQLite.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset