Quantcast




The software that powers this site is now available for beta testing at my new site, Pyplate. Check out the installation instructions, and build your own Raspberry Pi powered site.

Follow me on Twitter and Google+

Google+







submit to reddit       
       

Building a web user interface for the temperature monitor

In my last post, I wrote about logging temperatures in an SQL database. In this article, I'm going to build a web based user interface.

You can download the code for this project from Github: https://github.com/Pyplate/rpi_temp_logger. There's a button to download the project as a zip file at the bottom of the right hand column.

The UI is generated by a script called webgui.py. When webgui.py executes, it searches the database and returns a list of records. Webgui.py begins execution in the main function. The first thing it does is call get_option() to see if any options were passed to the script, and recover them if available. The first time a user opens the UI in their browser, no options will be passed to the script. As you'll see a few paragraphs down, users can select an option in the UI and reload the script. When this happens, an option is passed to the script, and get_option() returns that option so that it can be used in the rest of the script.

Next, webgui.py searches the database for records within the time limit imposed by any options that were passed. Function get_data() handles this:

# get data from the database
# if an interval is passed, 
# return a list of records from the database
def get_data(interval):

    conn=sqlite3.connect(dbname)
    curs=conn.cursor()

    if interval == None:
        curs.execute("SELECT * FROM temps")
    else:
        curs.execute("SELECT * FROM temps WHERE 
             timestamp>datetime('now','-%s hours')" % interval)
    
    rows=curs.fetchall()

    conn.close()

    return rows

It connects to the database and creates a cursor. If no option was passed to the script, then all records in the database are returned. If a time limit was specified, the database is queried for records where the timestamp is greater than the current time minus the interval.

Print the HTTP header

Before anything else happens, the HTTP header is sent to the browser. This is just a string that tells the browser that we're about to send it some HTML:

Content-type: text/html\n\n

There must be a blank line after the header, so the string is terminated with two new line characters.

The list of records returned by get_data is then passed to create_table(), a function that formats the data as a javascript table as follows:

['2013-09-19 10:30:03', 20.062],
['2013-09-19 10:45:02', 20.687],
['2013-09-19 11:00:02', 21.125]

This table will be embedded in a javascript snippet later on. Note that the last line in the table does not have a comma at the end. This is important because of the format of the code that the table is embedded in.

Printing the HTML head section

Now we're ready to print the rest of the page. We start with the HTML tag, and then the head section. The head section of a web page contains meta data, javascript and CSS styling. In this example, there's no need to use meta data or CSS, so we just need to print the page title tags and the javascript for the Google chart. The javascript snippet is printed by function print_graph_script(). The table generated earlier is passed to print_graph_script() so that it can be embedded in the javascript code.

    <script type="text/javascript" src="https://www.google.com/jsapi"></script>
    <script type="text/javascript">
      google.load("visualization", "1", {packages:["corechart"]});
      google.setOnLoadCallback(drawChart);
      function drawChart() {
        var data = google.visualization.arrayToDataTable([
          ['Time', 'Temperature'],
['2013-09-19 20:15:02', 21.187],
['2013-09-19 20:30:02', 21.375],
['2013-09-19 20:45:02', 21.625],
['2013-09-19 21:00:02', 21.812],
['2013-09-19 21:15:02', 21.875],
['2013-09-19 21:30:02', 22],
['2013-09-19 21:45:02', 22.187],
['2013-09-19 22:00:02', 22.062],
['2013-09-19 22:15:03', 22.125]

        ]);

        var options = {
          title: 'Temperature'
        };

        var chart = new google.visualization.LineChart
                        (document.getElementById('chart_div'));
        chart.draw(data, options);
      }
    </script>

Printing the page body

Near the top of the UI, there's a drop down list where users can select whether they view data from the last 6, 12 or 24 hours. The list is an HTML form (see http://raspberrywebserver.com/cgiscripting/web-forms-with-python.html for more information). When the submit button is pressed, the script runs again and the chosen value is passed to the new instance of the script. Function get_option() uses Python's CGI library to get the value of the option. When the list is displayed again, it needs to be displayed with a default value to indicate which option was selected. This is done by embedding 'selected="selected"' in the string for the chosen option:

    <form action="/cgi-bin/webgui.py" method="POST">
        Show the temperature logs for  
        <select name="timeinterval">
            <option value="6" selected="selected">the last 6 hours</option>
            <option value="12">the last 12 hours</option>
            <option value="24">the last 24 hours</option>
        </select>
        <input type="submit" value="Display">
    </form>

Displaying the temperature chart

The code for the chart appears in the head section, but the chart itself is drawn in the page body. The javascript code references an HTML div called chart_div. The function show_graph() prints this line of HTML:

<div id="chart_div" style="width: 900px; height: 500px;"></div>

When the entire page is loaded in a browser, the javascript code executes and draws the chart in chart_div.

Display statistics

The final function to execute is show_stats. This function connects to the database, creates a cursor, and runs three queries. The queries search the database for the minimum, maximum and average temperatures.

    conn=sqlite3.connect(dbname)
    curs=conn.cursor()

    curs.execute("SELECT timestamp,max(temp) FROM temps 
                     WHERE timestamp>datetime('now','-%s hour')" % option)
    rowmax=curs.fetchone()
    rowstrmax="{0}   {1}C".format(str(rowmax[0]),str(rowmax[1]))

    curs.execute("SELECT timestamp,min(temp) FROM temps 
                     WHERE timestamp>datetime('now','-%s hour')" % option)
    rowmin=curs.fetchone()
    rowstrmin="{0}   {1}C".format(str(rowmin[0]),str(rowmin[1]))

    curs.execute("SELECT avg(temp) FROM temps 
                     WHERE timestamp>datetime('now','-%s hour')" % option)
    rowavg=curs.fetchone()

The first two queries use the max() and min() SQL functions to find the minimum and maximum entries in the database. They return a record containing the timestamp and a numeric temperature value. The third query in show_stats uses the avg() function to find the average value in the database. This query just returns a record containing the average time without a timestamp. All of these queries select records where the timestamp is greater than some offset from the current time. The data returned from the three queries is formatted and printed.

Show_stats executes another query to get records from the last hour. These records are displayed as a table at the bottom of the page.

It's not practical for me to leave the temperature logger set up indefinitely (I need to use my Pi for other things), so I've modified the SQL queries in the scripts that are used to display the temperature logger on this site. I unplugged the breadboard at around 21:30, so I replaced 'now' with the timestamp '2013-09-19 21:15:02'. Instead of selecting records that are greater than 'now minus an interval', I modified webgui.py to select records where the timestamp is greater than '2013-09-19 21:15:02' minus an interval AND less than '2013-09-19 21:15:02'.

In webgui.py, there are several lines that contain hardcoded dates so that you can use the script with the sample database provided. There is an equivalent version of each of these lines that uses 'now' instead of a hardcoded timestamp. If you want to view data you've collected yourself, you should uncomment the lines that use 'now', and comment out the lines that have a hardcoded date. See webgui.py, lines 45, 117, 122, 127 and 148.

See also:

Comments

comments powered by Disqus