Easy as PI Weather Station – collecting the data

I inherited a Raspberry PI from a work colleague. It was complete with the Astro PI Hat(now known as the Sense Hat). This marvellous add-on gives a variety of environmental sensors such as temperature, humidity and air pressure.

For a while it sat there on the shelf dusty and forelorn. While doing some work in my shed I wondered whether I could use my PI to gather information and display it on the matrix display. I found a marvellous article on how to create a weather station by John M. Wargo. It is well worth a read. In the article, data is collected at regular intervals from the PI sensors and then uploaded to an external site hosted by weather underground. In no time I had a working weather station. I tweaked the script to show a line graph but it was a little janky because of the low resolution of the display.

Here is the PI in all its glory showing realtime temperature readings in graph form

In this post we are going to do something similar. We are going to collect data but upload it on our own server running a REST API. Then we are going to display this information on a lovely D3 chart. Wait! What? Yes, that is a lot to take in but fear not, this is going to be a 3 part post. The first part? Getting the data from the PI.

Let’s assume we have a fresh PI complete with an Astro Hat. Log in to your PI using puTTY or another application. I connect my PI direct to my laptop using an Ethernet cable but a wireless connection will work as well.

Now in your home directory(in my case /home/pi) create a new directory called collector

mkdir collector

Next use your editor of choice to create a file in the collector directory called collector.py. I use nano so in this case type nano collector.py.

Below is the code for collector.py. I ‘ll skip the first few functions. get_cpu_temp(), get_smooth() and get_temp(). These are used to try and get an accurate temperature reading because the Astro PI hat is affected by the heat given off y the PI CPU. These functions try and make allowances for that. Details here. If you can physically separate your PI using a ribbon cable then you can simply take the standard reading from the humidity sensor as detailed in the Sense Hat API.

#!/usr/bin/python
'''*******************************************************************************************************************************************
* This program collects environmental information from the sensors in the Astro PI hat and uploads the data to a server at regular intervals *
*******************************************************************************************************************************************'''
from __future__ import print_function
import datetime
import os
import sys
import time
import urllib
import urllib2
from sense_hat import SenseHat
# ============================================================================
# Constants
# ============================================================================
# specifies how often to measure values from the Sense HAT (in minutes)
MEASUREMENT_INTERVAL = 5 # minutes
def get_cpu_temp():
    # 'borrowed' from https://www.raspberrypi.org/forums/viewtopic.php?f=104&t=111457
    # executes a command at the OS to pull in the CPU temperature
    res = os.popen('vcgencmd measure_temp').readline()
    return float(res.replace("temp=", "").replace("'C\n", ""))
# use moving average to smooth readings
def get_smooth(x):
    # do we have the t object?
    if not hasattr(get_smooth, "t"):
        # then create it
        get_smooth.t = [x, x, x]
    # manage the rolling previous values
    get_smooth.t[2] = get_smooth.t[1]
    get_smooth.t[1] = get_smooth.t[0]
    get_smooth.t[0] = x
    # average the three last temperatures
    xs = (get_smooth.t[0] + get_smooth.t[1] + get_smooth.t[2]) / 3
    return xs
def get_temp():
    # ====================================================================
    # Unfortunately, getting an accurate temperature reading from the
    # Sense HAT is improbable, see here:
    # https://www.raspberrypi.org/forums/viewtopic.php?f=104&t=111457
    # so we'll have to do some approximation of the actual temp
    # taking CPU temp into account. The Pi foundation recommended
    # using the following:
    # http://yaab-arduino.blogspot.co.uk/2016/08/accurate-temperature-reading-sensehat.html
    # ====================================================================
    # First, get temp readings from both sensors
    t1 = sense.get_temperature_from_humidity()
    t2 = sense.get_temperature_from_pressure()
    # t becomes the average of the temperatures from both sensors
    t = (t1 + t2) / 2
    # Now, grab the CPU temperature
    t_cpu = get_cpu_temp()
    # Calculate the 'real' temperature compensating for CPU heating
    t_corr = t - ((t_cpu - t) / 1.5)
    # Finally, average out that value across the last three readings
    t_corr = get_smooth(t_corr)
    # convoluted, right?
    # Return the calculated temperature
    return t_corr

The main meat is in the, erm, main() function. Here we set up a loop to poll the PI at regular intervals, every 5 seconds so that the smoothing algorithm works effectively. The data is sent to the web service every 5 minutes. This is specified in the global variable MEASUREMENT_INTERVAL.

def main():
    global last_temp
    sense.clear()
    last_minute = datetime.datetime.now().minute;
    minute_count = 0;
    # infinite loop to continuously check weather values
    while True:
        dt = datetime.datetime.now()
        current_minute = dt.minute;
        temp_c = get_temp();
        # The temp measurement smoothing algorithm's accuracy is based
        # on frequent measurements, so we'll take measurements every 5 seconds
        # but only upload on measurement_interval
        current_second = dt.second
        # are we at the top of the minute or at a 5 second interval?
        if (current_second == 0) or ((current_second % 5) == 0):
            message = "{}C".format(int(temp_c));
            sense.show_message(message, text_colour=[255, 0, 0])
        if current_minute != last_minute:
            minute_count +=1
        if minute_count == MEASUREMENT_INTERVAL:
            print('Logging data from the PI')
            payload = {
                'date':dt.strftime("%Y/%m/%d %H:%M:%S"),
                'temperature':round(temp_c,2),
                'pressure':round(sense.get_pressure(),2),
                'humidity':round(sense.get_humidity(),2),
            }
            print(payload)
            # TODO post the results to our server
            minute_count = 0;
        last_minute = current_minute
        # wait a second then check again
        time.sleep(2)  # this should never happen since the above is an infinite loop
    print("Leaving main()")
# ============================================================================
# initialize the Sense HAT object
# ============================================================================
try:
    print("Initializing the Sense HAT client")
    sense = SenseHat()
    sense.set_rotation(90)
except:
    print("Unable to initialize the Sense HAT library:", sys.exc_info()[0])
    sys.exit(1)
print("Initialization complete!")
# Now see what we're supposed to do next
if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print("\nExiting application\n")
        sense.clear()
        sys.exit(0)

A JSON object is used to hold this data which will look something like this

{
    'date': '2020/05/26 14:01:00',
    'pressure': 1035.2,
    'temperature': 28.36,
    'humidity': 39.82
}

This will be used as the payload to our web service. More to follow in part 2…