Is it cold outside? Part 2
When I left you last, we’d got most of the information for the project assembled and just needed to extract the information from the webpages we found and set up push notifications before sewing it all together - let’s get that sorted now so we can get this up and running!
I’ve used pushover for notifications before so I’ll use that - signing up for an account gives you 30days free, after that it’s $5USD for a lifetime license for an individual which I love. They charge for larger groups using their service but keep the introductory price low and giving me 10k notifications/month gives me a lot of freedom for testing/maintaining multiple projects. Checking the API page I can see I need to send 3 things to “https://api.pushover.net/1/messages.json” for the notifications to work:
User key - you get this when you make an account
API token - you get this when you set up a project
A message - do I really need to explain this one?
After this, just log into your account on a mobile device and you’re all ready to send notifications. Try this:
url_api = 'https://api.pushover.net/1/messages.json' user_token = <secret> api_token = <secret> post_body = {'token': api_token, 'user': user_token, 'message': "Hi!"} post_request = requests.post(url_api, json=post_body)
Now I have notifications working, let’s work on how to extract the information I want out of the webpages. I quickly introduced BeautifulSoup (BS) in the last entry, but I’ll dive more into it’s use today. After parsing HTML code, BS is able to search for information by tag, class, ID, or a combination. If you want the ‘apple’ class <div> tags, you can grab them easily. If you only want the ‘apple’ class <div> tags that are inside <tr> tags, then that’s just as easy! So here’s an example of the syntax:
result = requests.get(url, headers=header_list) soup = BeautifulSoup(result.content, 'html.parser') data = soup.find_all('tr', class_='rowleftcolumn')[0].get_text()
This will parse the webpage at ‘url’ and then find all <tr> tags of the class “rowleftcolumn”, outputting them to a list but I’ll just get the first result by specifying ‘[0]’ - I then extract the text from the tag, rather than all the information. That means that when presented with the following:
<tr class="rowleftcolumn"> <td headers="t1-datetime">24/09:30pm</td> <td headers="t1-tmp">12.1</td> <td headers="t1-apptmp">9.6</td> <td headers="t1-dewpoint">9.7</td> <td headers="t1-relhum">85</td> <td headers="t1-delta-t">1.2</td> <td headers="t1-wind t1-wind-dir">WSW</td> <td headers="t1-wind t1-wind-spd-kmh">13</td> <td headers="t1-wind t1-wind-gust-kmh">17</td> <td headers="t1-wind t1-wind-spd-kts">7</td> <td headers="t1-wind t1-wind-gust-kts">9</td> <td headers="t1-press-qnh">1031.8</td> <td headers="t1-press-msl">1031.8</td> <td headers="t1-rainsince9am">0.2</td> </tr>
I’d get this output, which lets me pick the line that I want
24/09:30pm
12.1
9.6
9.7
85
1.2
WSW
13
17
7
9
1031.8
1031.8
0.2
So when approaching any script you should plan beforehand - let’s map out what I want to do:
It should check the BoM forecast to see if it’s a hot day
Only if it’s a hot day should it start checking my internal sensor and the current temperatures from BoM (don’t want to spam them unnecessarily)
The first time during a hot day that the outside temperature drops below my internal temperature, it should notify me
And now it’s time to compile everything!
import time import requests from bs4 import BeautifulSoup import datetime import Adafruit_DHT # variable setup sensor = Adafruit_DHT.DHT11 pin = 17 url_api = 'https://api.pushover.net/1/messages.json' user_token = <secret> api_token = <secret> url_max = 'http://www.bom.gov.au/nsw/forecasts/sydney.shtml' url_current = 'http://reg.bom.gov.au/products/IDN60901/IDN60901.94767.shtml' header_list = {'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:12.0) Gecko/20100101 Firefox/12.0'} max_checked = False notified = False max_temperature = 0.0 temperature = 0.0 while True: # This will run forever ###setup### hour_now = int(datetime.datetime.now().strftime('%X').split(':')[0]) # extracts the 'hour' value from the current time if hour_now == 1 and max_checked == True: # if the local time is 1AM, it resets values for the new day max_temperature = 0.0 max_checked = False notified = False if hour_now > 12 and max_checked == False: # after midday, it checks the forecast for the max expected temperature once result = requests.get(url_max, headers=header_list) soup = BeautifulSoup(result.content, 'html.parser') max_temperature = float(soup.find_all('em', class_='max')[0].get_text()) max_checked = True ###checking### try: humidity, temperature = Adafruit_DHT.read_retry(sensor, pin) # it checks the current internal temperature except: pass if max_temperature > temperature or max_temperature > 25: # if its colder inside (indicating it's a hot day) or the forcast is >25C if hour_now > 15 and hour_now < 21: # between 3pm & 9pm it gets the current temperature result = requests.get(url_current, headers=header_list) soup = BeautifulSoup(result.content, 'html.parser') current_temperature = soup.find_all('tr', class_='rowleftcolumn')[0].get_text() current_temperature = float(current_temperature.split('\n')[3]) ###notifying### if current_temperature < temperature and notified == False: # if it's cooler outside after a hot day and I haven't been notified already - sends a notification post_body = {'token': api_token, 'user': user_token, 'message': "It's time to open your windows - it's cool outside"} post_request = requests.post(url_api, json = post_body) notified = True else: continue time.sleep(1800) # sleeps for 30mins before restarting
I’ve just thrown a lot on the screen so let’s go through it. I start by importing all the libraries I need and declaring my variables. Some of the variables I’ve already covered, but a note on declaring the last 4 - essentially after a single run-through, each will already have a value, but on it’s first run it needs default values set so it has something to check against. All the if statements refer to these variables and if they don’t exist then the script fails before it fully starts.
I wrap everything in a ‘while True’ loop with a 30min sleep at the end - this reduces the use of my pi’s resources and reduces the strain on the BoM’s site. Now, will one machine have an impact on their website, probably not - but like I said before, it’s the polite thing to do and if enough people aren’t considerate, then BoM might start to feel an impact. As for the main body - I split it into 3 sections: setup, checking & notifying.
Setup resets the values every morning at 1am and checks the day’s maximum temperature in the afternoon. Checking gets the output of the internal sensor, decides if it’s a hot day that day, then monitors the temperature observations for a drop in temperature. Finally, notifying sends the push notification to my phone when the hot day cools enough.
And now I have a working weather-checking script! Buuuuut there’s a small snag - how do I get it to run automatically? What if there’s a blackout? An error? I’ll cover that in the next entry!