Raspberry Pi touchscreen noticeboard with live weather forecast and news headlines displayed using Tkinter GUI.
Using Tkinter GUI and python to create a noticeboard with a Raspberry Pi and its touchscreen.
Retrieving Weather Data with Dark Sky API
To retrieve weather data and create a five-day forecast I used Dark Sky's API, their free tier allows 1000 calls a day, you just have to sign up with your email.
Their API get request uses latitude and longitude coordinates to provide location specific data. To get your current lat/long position I used IP API, which is free and doesn't require an account. I chose this one because it was the only one from the first couple Google listings that got my city right. The documentation for the json reply format can be found here. I also filtered out the country code which will be used later in getting news headlines.
For today's weather I grabbed the suggested weather icon, temperature as well as a brief summary of the conditions. For the following four days I only grabbed the temperature and the icon.
import requests
import json
from decimal import *
def getWeather():
four_day_forecast = []
weather_api_key = '<your api key>'
r = requests.get('http://ip-api.com/json')
response = r.text
response = json.loads(response)
lat_long = str(response['lat']) + ', ' + str(response['lon'])
country = str(response['countryCode'])
country = country.lower()
url = 'https://api.darksky.net/forecast/' + weather_api_key + '/' + lat_long
weather = requests.get(url, params={'exclude': 'minutely,alerts,flags', 'units': 'ca'})
weather = weather.json()
todays_weather_raw = weather['hourly']['data'][0]
todays_summary = todays_weather_raw['summary']
todays_icon = todays_weather_raw['icon']
temp = Decimal(todays_weather_raw['temperature'])
temp = round(temp, 1)
todays_temp = temp
todays_weather = [todays_icon, todays_temp, todays_summary]
for i in range(1, 5):
daily_weather = weather['daily']['data'][i]
x = Decimal((daily_weather['temperatureHigh'] + daily_weather['temperatureLow']) / 2)
avg_temp = round(x, 1)
four_day_forecast.append((daily_weather['icon'], avg_temp))
print todays_weather, four_day_forecast
getWeather()
If everything works properly you should get an output similar to the one below.
Retrieving Location Specific News Healdines
To get news headlines I used the well named News API. They also have a free tier of 1000 API calls a day and a python library which made it super simple. You just have to sign up with an email.
Instructions to install their python library and some examples can be found here.
In the code below I just grab the top 10 headlines and their corresponding urls' for the notice board.
from newsapi import NewsApiClient
import requests
import json
def getNews(country):
newsapi = NewsApiClient(api_key='<your API Key>')
newsHeadlines = []
top_headlines = newsapi.get_top_headlines(country=country)
response = top_headlines
top_ten_headlines = response['articles'][:10]
for articles in top_ten_headlines:
newsHeadlines.append((articles['title'].encode('utf-8'), articles['url'].encode('utf-8')))
print(newsHeadlines)
getNews('ca')
If everything went smoothly you should get a json with a list of 10 articles titles and their corresponding urls'. With these we will be able to make clickable buttons that take you to the linked article. It should look something like this.
Getting Weather Icons
I used this website to find my weather icons, they had a big selection of free monochrome icons. The icons I used can be found here in the 'weather-icons' folder. As part of the free use of the icons I do have to give the artists credit so here they are, Haze - Zlatko Najdenovski, moon, cloudy-night - xnimrodx, snow - Nikita Golubev, water, air - Freepik, rain, cloudy, cloud, sun - Iconnice.
If you choose to pick your own icons, make sure you name them according to the Dark Sky's possible icon names which can be found here. Or you can just copy the names of the icons I used accordingly.
Wiring the Raspberry Pi Display
Wiring the display is pretty simple. All you need to do is use the ribbon cable provided and insert it into the display port on your raspberry pi, like so:
Next connect two wires, one to the 5 volt pin on the display, and the other to the ground pin. Connect each one respectively to a 5V out and ground pin on your Raspberry Pi as seen below. Now restart your raspberry pi and you should see it boot to the display.
Getting Started with Tkinter GUI
To keep consistent with python I used Tkinter, which in retrospect is a little out of date, but it was still fun. Anyways, there is a pretty good and occasionally funny YouTube tutorial series that I used which can be found here. I found all of his videos pretty useful so if you want to expand on what I did, and are new to Tkinter, it's not a bad place to start.
Basically to make the headlines clickable I had to make them buttons, with an onclick() method that opens the link in your default browser. The weather and clock are all just labels. There is then a sleep that updates the information on the screen every five minutes.
The finished code looks like this:
from newsapi import NewsApiClient
from Tkinter import *
from decimal import *
import webbrowser
import PIL.Image
import datetime
import requests
import time
import json
import sys
import dis
import os
directory = '<your weather-icon directory'
time1 = ''
news_api_key = '<your news api key>'
weather_api_key = '<your weather api key'
width = 100
height = 100
degree_sign = u'\N{DEGREE SIGN}'
class Headline:
def __init__(self, headline, url, parent_frame):
self.Btn = Button(parent_frame, justify=LEFT, wraplength=250, text=headline, command=self.onClick,
highlightbackground='black', highlightcolor='black', highlightthickness=1)
self.url = url
def onClick(self):
webbrowser.open(self.url)
class DailyWeather:
days_of_the_week = ['Mon', 'Tues', 'Wed', 'Thurs', 'Fri', 'Sat', 'Sun']
today = datetime.datetime.today().weekday()
def __init__(self, icon, temp, index, parent_frame, forecast_icons):
self.frame = Frame(parent_frame, highlightbackground='black', highlightcolor='black',
highlightthickness=1)
if index == 0:
self.frame.grid(column=2, row=1, sticky=N + S + E + W)
if index == 1:
self.frame.grid(column=0, row=2, sticky=N + S + E + W)
if index == 2:
self.frame.grid(column=1, row=2, sticky=N + S + E + W)
if index == 3:
self.frame.grid(column=2, row=2, sticky=N + S + E + W)
for i in range(0, 3):
self.frame.rowconfigure(i, weight=1)
self.frame.columnconfigure(i, weight=1)
self.fp = forecast_icons.get(icon)
self.img = PhotoImage(
file=self.fp)
self.icon = Label(self.frame, image=self.img)
self.temp = Label(self.frame, text=str(temp) + ' ' + degree_sign + 'C')
self.day_name = self.days_of_the_week[
(self.today + index + 1) % len(
self.days_of_the_week)]
self.day = Label(self.frame, text=self.day_name)
def place(self):
self.icon.grid(row=0, column=0, rowspan=3, columnspan=2, sticky=N + S + E + W)
self.day.grid(row=0, column=2, sticky=N + S + E + W)
self.temp.grid(row=2, column=2, sticky=N + S + E + W)
class TodaysWeather:
def __init__(self, icon_fp, temp, summary, parent_frame):
self.todaysWeatherFrame = Frame(parent_frame, highlightbackground='black', highlightcolor='black',
highlightthickness=1)
self.todaysWeatherFrame.grid(column=0, columnspan=2, row=0, rowspan=2, sticky=N + S + E + W)
for i in range(0, 3):
self.todaysWeatherFrame.rowconfigure(i, weight=1)
self.todaysWeatherFrame.columnconfigure(i, weight=1)
self.fp = icon_fp
self.img = PhotoImage(file=self.fp)
self.icon = Label(self.todaysWeatherFrame, image=self.img)
self.temp = Label(self.todaysWeatherFrame, text=str(temp) + ' ' + degree_sign + 'C',
justify=LEFT)
self.summary = Label(self.todaysWeatherFrame, text=summary)
self.today = Label(self.todaysWeatherFrame, text='Today', justify=LEFT)
def place(self):
self.icon.grid(row=0, column=0, rowspan=2, columnspan=3, sticky=N + S + E + W)
self.temp.grid(row=1, column=3, sticky=N + S + E + W)
self.summary.grid(row=2, column=0, columnspan=4, sticky=N + S + E + W)
self.today.grid(row=0, column=3, sticky=N + S + E + W)
class MainWindow:
def __init__(self, directory, news_api_key, weather_api_key, width, height):
self.width = width
self.height = height
self.news_api_key = news_api_key
self.weather_api_key = weather_api_key
self.directory = directory
self.buttonHeadlines = []
self.forecast = []
self.forecast_icons = {}
self.todays_icons = {}
self.objects = {}
self.state = False
self.root = Tk()
self.country = ''
self.todays_weather_list = []
def body(self):
self.root.attributes('-fullscreen', True)
self.root.bind("<F11>", self.toggle_fullscreen)
self.root.bind("<Escape>", self.end_fullscreen)
self.root.columnconfigure(0, weight=1)
self.root.rowconfigure(0, weight=1)
frame = Frame(self.root)
frame.rowconfigure(0, weight=1)
frame.columnconfigure(1, weight=1)
frame.grid(sticky=N + S + E + W)
leftFrame = Frame(frame, bg="red")
leftFrame.grid(row=0, column=0, sticky=N + S + E + W)
leftFrame.columnconfigure(0, weight=1)
for i in range(0, 10):
leftFrame.rowconfigure(i, weight=1)
rightFrame = Frame(frame)
rightFrame.grid(column=1, row=0, sticky=N + S + E + W)
rightFrame.columnconfigure(0, weight=1)
rightFrame.rowconfigure(0, weight=1)
weatherFrame = Frame(rightFrame)
weatherFrame.grid(column=0, row=0, sticky=N + S + E + W)
for i in range(0, 3):
weatherFrame.rowconfigure(i, weight=1)
weatherFrame.columnconfigure(i, weight=1)
todays_forecast, four_day_forecast = self.getWeather()
for i, day in enumerate(four_day_forecast):
item = DailyWeather(day[0], day[1], i, weatherFrame, self.forecast_icons)
self.objects['item' + str(i)] = item
for item in self.objects:
self.objects[item].place()
icon_fp = self.todays_icons.get(todays_forecast[0])
temp = todays_forecast[1]
summary = todays_forecast[2]
todays_weather = TodaysWeather(icon_fp, temp, summary, weatherFrame)
self.todays_weather_list.append(todays_weather)
self.todays_weather_list[0].place()
for title in self.getNews(self.country):
headline = Headline(title[0], title[1], leftFrame)
self.buttonHeadlines.append(headline)
Clock = Label(weatherFrame, font=('helvetica', 20, 'bold'), highlightbackground='black',
highlightcolor='black', highlightthickness=1)
for i, object in enumerate(self.buttonHeadlines):
object.Btn.grid(row=i, sticky=N + S + E + W)
def tick():
global time1
time2 = time.strftime('%H:%M:%S')
if time2 != time1:
time1 = time2
Clock.config(text=time2)
Clock.after(200, tick)
Clock.grid(column=2, row=0, sticky=N + S + E + W)
tick()
def toggle_fullscreen(self, event=None):
self.state = not self.state
self.root.attributes("-fullscreen", self.state)
def end_fullscreen(self, event=None):
self.state = False
self.root.attributes("-fullscreen", False)
def getNews(self, country):
newsapi = NewsApiClient(api_key=self.news_api_key)
newsHeadlines = []
top_headlines = newsapi.get_top_headlines(country=country)
response = top_headlines
top_ten_headlines = response['articles'][:10]
for articles in top_ten_headlines:
newsHeadlines.append((articles['title'].encode('utf-8'), articles['url'].encode('utf-8')))
return newsHeadlines
def getWeather(self):
four_day_forecast = []
r = requests.get('http://ip-api.com/json')
response = r.text
response = json.loads(response)
lat_long = str(response['lat']) + ', ' + str(response['lon'])
self.country = str(response['countryCode'])
self.country = self.country.lower()
url = 'https://api.darksky.net/forecast/' + self.weather_api_key + '/' + lat_long
weather = requests.get(url, params={'exclude': 'minutely,alerts,flags', 'units': 'ca'})
weather = weather.json()
todays_weather_raw = weather['hourly']['data'][0]
todays_summary = todays_weather_raw['summary']
todays_icon = todays_weather_raw['icon']
temp = Decimal(todays_weather_raw['temperature'])
temp = round(temp, 1)
todays_temp = temp
todays_weather = [todays_icon, todays_temp, todays_summary]
for i in range(1, 5):
daily_weather = weather['daily']['data'][i]
x = Decimal((daily_weather['temperatureHigh'] + daily_weather['temperatureLow']) / 2)
avg_temp = round(x, 1)
four_day_forecast.append((daily_weather['icon'], avg_temp))
return todays_weather, four_day_forecast
def resize_icons(self):
subdir_forecast = self.directory + 'forecast'
subdir_todays = self.directory + 'todays'
if not os.path.exists(subdir_forecast):
os.makedirs(subdir_forecast)
if not os.path.exists(subdir_todays):
os.makedirs(subdir_todays)
for file in os.listdir(directory):
if file.endswith('.jpeg') or file.endswith('.png'):
original_path = directory + file
fp = open(original_path)
img = PIL.Image.open(fp)
resizedImage = img.resize((self.width, self.height), PIL.Image.ANTIALIAS)
resizedImage.save(subdir_forecast + '/' + file, "PNG")
fp.close()
resizedImage.close()
fp = open(original_path)
img = PIL.Image.open(fp)
resizedImage = img.resize((int(2 * width), int(2 * height)), PIL.Image.ANTIALIAS)
resizedImage.save(subdir_todays + '/' + file, "PNG")
fp.close()
resizedImage.close()
name, extension = file.split(".")
self.forecast_icons[name] = str(subdir_forecast + '/' + file)
self.todays_icons[name] = str(subdir_todays + '/' + file)
if __name__ == '__main__':
window = MainWindow(directory, news_api_key, weather_api_key, width, height)
window.resize_icons()
window.body()
window.root.mainloop()
The code is all commented, but basically it implements the two functions described above and another function that resizes your weather icons to fit the screen of the raspberry pi and which you can adjust the size of in the 'width' and 'height' variables. If you have any questions please comment, and if you have any cool spin offs please feel free to fork the project! Here's what it looks like, Enjoy.