Maker Pro
Raspberry Pi

Raspberry Pi Noticeboard using 7" Touchscreen display

July 23, 2018 by Connor Moore
Share
banner

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 = []  # list of tuples[(icon, avgTemp)]
    weather_api_key = '<your api key>'

    # ***Use IP to get Lat/Long for local weather*** #
    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()

    # ***API request to get weather data*** #
    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()

    # ***Today's weather info*** #
    todays_weather_raw = weather['hourly']['data'][0]
    todays_summary = todays_weather_raw['summary']  # summary of today's weather
    todays_icon = todays_weather_raw['icon']  # icon for today's weather
    temp = Decimal(todays_weather_raw['temperature'])
    temp = round(temp, 1)
    todays_temp = temp  # current temp
    todays_weather = [todays_icon, todays_temp, todays_summary]

    # ***Next 4 days weather*** #
    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  # this is already in json format, hype
    top_ten_headlines = response['articles'][:10]  # take top 10 headlines
    for articles in top_ten_headlines:
        newsHeadlines.append((articles['title'].encode('utf-8'), articles['url'].encode('utf-8')))
    print(newsHeadlines)  # return list of tuples

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'  # Where you stored your collection of weather icons
time1 = ''  # used in clock
news_api_key = '<your news api key>'  # News API key
weather_api_key = '<your weather api key'  # Dark Sky API key
width = 100  # Width of four day forecast icons in pixels
height = 100  # Height of four day forecast icons in pixels
degree_sign = u'\N{DEGREE SIGN}'  # unicode degrees sign for temperature


# *****Headline Class***** #
class Headline:

    # Headline Object consists of a button and an onClick() method
    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)  # When pressed open url in web browser


# *****Following 4 day forecast Tkinter layout***** #
class DailyWeather:
    days_of_the_week = ['Mon', 'Tues', 'Wed', 'Thurs', 'Fri', 'Sat', 'Sun']  # list of abbreviated weekdays
    today = datetime.datetime.today().weekday()  # get integer value for current day of the week

    # index is it's index of the list produced by getWeather, this identifies it's numerical distance from today's day of the week
    # forecast_icons is a dictionary consisting of the icon key names parameter and the local path to the corresponding icon image file
    def __init__(self, icon, temp, index, parent_frame, forecast_icons):
        self.frame = Frame(parent_frame, highlightbackground='black', highlightcolor='black',
                           highlightthickness=1)  # create frame inside parent_frame
        # index determines day of the week and therefore placement relative to today's weather
        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)

        # configure the 3 by 3 layout to scale when there is available space
        for i in range(0, 3):
            self.frame.rowconfigure(i, weight=1)
            self.frame.columnconfigure(i, weight=1)

        self.fp = forecast_icons.get(icon)  # get file path of the icon
        self.img = PhotoImage(
            file=self.fp)  # convert image into Tkinter PhotoImage object so it can be placed in a label
        self.icon = Label(self.frame, image=self.img)  # create icon label
        self.temp = Label(self.frame, text=str(temp) + ' ' + degree_sign + 'C')  # create temperature label
        self.day_name = self.days_of_the_week[
            (self.today + index + 1) % len(
                self.days_of_the_week)]  # use modulo to make list circular and retrieve relative day of the week
        self.day = Label(self.frame, text=self.day_name)  # create day of the week label

    # place the objects labels in the proper spots
    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)


# *****Today's Weather Label Layout***** #
class TodaysWeather:

    # icon_fp is the filepath to today's weather icon
    def __init__(self, icon_fp, temp, summary, parent_frame):
        self.todaysWeatherFrame = Frame(parent_frame, highlightbackground='black', highlightcolor='black',
                                        highlightthickness=1)  # create frame for today's weather
        self.todaysWeatherFrame.grid(column=0, columnspan=2, row=0, rowspan=2, sticky=N + S + E + W)

        # configure todaysWeatherFrame to scale
        for i in range(0, 3):
            self.todaysWeatherFrame.rowconfigure(i, weight=1)
            self.todaysWeatherFrame.columnconfigure(i, weight=1)

        # image process is the same as explained in DailyWeather
        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):
        # initialize MainWindow object variables
        self.width = width  # width of DailyWeather icons
        self.height = height  # height of DailyWeather icons
        self.news_api_key = news_api_key
        self.weather_api_key = weather_api_key
        self.directory = directory #  directory of base weather icons
        self.buttonHeadlines = []  # list of headline buttons
        self.forecast = []  # list containing the next four days weather
        self.forecast_icons = {}  # dictionary of standardized weather keys, and their corresponding DailyWeather sized icon paths
        self.todays_icons = {}  # dictionary of standardized weather keys, and their corresponding TodaysWeather sized icon paths
        self.objects = {}  # holds daily weather objects
        self.state = False  # state for full screen toggle
        self.root = Tk()  # base Tk() object
        self.country = ''  # country code used for news headlines
        self.todays_weather_list = []

    def body(self):
        self.root.attributes('-fullscreen', True)  # start in fullscreen mode
        self.root.bind("<F11>", self.toggle_fullscreen)  # bind F11 as button to toggle fullscreen
        self.root.bind("<Escape>", self.end_fullscreen)  # bind Escape as a way to exit fullscreen
        self.root.columnconfigure(0, weight=1)  # making root scalable in y
        self.root.rowconfigure(0, weight=1)  # and x

        # ***Base Frame*** #
        frame = Frame(self.root)
        # allow frame to scale to the size of root
        frame.rowconfigure(0, weight=1)
        frame.columnconfigure(1, weight=1)
        frame.grid(sticky=N + S + E + W)  # sticky allows it to scale in all directions

        # ***Create Left Frame for News Headlines*** #
        leftFrame = Frame(frame, bg="red")
        leftFrame.grid(row=0, column=0, sticky=N + S + E + W)  # in the first row and column of frame

        # ***Making the headlines scalable*** #
        leftFrame.columnconfigure(0, weight=1)
        # top ten headlines, be displayed in 10 rows
        for i in range(0, 10):
            leftFrame.rowconfigure(i, weight=1)

        # ***Frame for clock and weather*** #
        rightFrame = Frame(frame)
        rightFrame.grid(column=1, row=0, sticky=N + S + E + W)
        # allow frame to scale
        rightFrame.columnconfigure(0, weight=1)
        rightFrame.rowconfigure(0, weight=1)

        # ***Frame for weather data*** #
        weatherFrame = Frame(rightFrame)
        weatherFrame.grid(column=0, row=0, sticky=N + S + E + W)
        # make rows and columns of weather data scalable
        for i in range(0, 3):
            weatherFrame.rowconfigure(i, weight=1)
            weatherFrame.columnconfigure(i, weight=1)

        # *****Weather***** #
        todays_forecast, four_day_forecast = self.getWeather()

        # *** Four Day Forecast*** #
        for i, day in enumerate(four_day_forecast):
            item = DailyWeather(day[0], day[1], i, weatherFrame, self.forecast_icons)  # day[0] = icon, day[1] = avgTemp
            self.objects['item' + str(i)] = item  # store in dictionary to avoid garbage collection on image
        for item in self.objects:
            self.objects[item].place()  # place the DailyWeather Items

        # ***Today's weather*** #
        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)  # store it in list so that garbage collection doesn't throw out the image
        self.todays_weather_list[0].place()  # place stored TodaysWeather object

        # ***Headlines*** #
        for title in self.getNews(self.country):  # takes in country code for relevant news
            headline = Headline(title[0], title[1], leftFrame)  # create Headline object with each headline. title[0] = title or article, title[1] = url of article
            self.buttonHeadlines.append(headline)  # create list of headline objects

        # ***Clock Placeholder*** #
        Clock = Label(weatherFrame, font=('helvetica', 20, 'bold'), highlightbackground='black',
                      highlightcolor='black', highlightthickness=1)

        # ***Packing in Headlines*** 3
        for i, object in enumerate(self.buttonHeadlines):
            object.Btn.grid(row=i, sticky=N + S + E + W)

        # *****Clock Widget***** #
        def tick():
            global time1
            # get the current local time from the PC
            time2 = time.strftime('%H:%M:%S')
            # if time string has changed, update it
            if time2 != time1:
                time1 = time2
                Clock.config(text=time2)
            # calls itself every 200 milliseconds
            # to update the time display as needed
            # could use >200 ms, but display gets jerky
            Clock.after(200, tick)

        # ***Placing Clock*** #
        Clock.grid(column=2, row=0, sticky=N + S + E + W)
        tick()

    def toggle_fullscreen(self, event=None):
        self.state = not self.state  # Just toggling the boolean
        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  # this is already in json format, hype
        top_ten_headlines = response['articles'][:10]  # take top 10 headlines
        for articles in top_ten_headlines:
            newsHeadlines.append((articles['title'].encode('utf-8'), articles['url'].encode('utf-8')))
        return newsHeadlines  # return list of tuples

    def getWeather(self):
        four_day_forecast = []  # list of tuples[(icon, avgTemp)]

        # ***Use IP to get Lat/Long for local weather
        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()

        # ***API request to get weather data
        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()

        # ***Today's weather info*** #
        todays_weather_raw = weather['hourly']['data'][0]
        todays_summary = todays_weather_raw['summary']  # summary of today's weather
        todays_icon = todays_weather_raw['icon']  # icon for today's weather
        temp = Decimal(todays_weather_raw['temperature'])
        temp = round(temp, 1)
        todays_temp = temp  # current temp
        todays_weather = [todays_icon, todays_temp, todays_summary]

        # ***Next 4 days weather*** #
        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))
            # now just have to reference icon title to a library of icons for representation in gui
        return todays_weather, four_day_forecast

        # *****Resize Icons***** #

    def resize_icons(self):
        # ***Create Subdirectories for resized icons*** #
        subdir_forecast = self.directory + 'forecast'  # directory for daily forecast sized icons
        subdir_todays = self.directory + 'todays'  # directory for today's weather sized icons
        # if they don't already exist
        if not os.path.exists(subdir_forecast):
            os.makedirs(subdir_forecast)
        if not os.path.exists(subdir_todays):
            os.makedirs(subdir_todays)
        # ***Iterate through icons directory resizing each image*** #
        for file in os.listdir(directory):
            if file.endswith('.jpeg') or file.endswith('.png'):  # expand depending on what file types your icons are
                original_path = directory + file
                # open images so they can be resized
                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")  # save forecast sized icon
                fp.close()
                resizedImage.close()
                # repeat above for double the size today's icons
                fp = open(original_path)
                img = PIL.Image.open(fp)
                resizedImage = img.resize((int(2 * width), int(2 * height)), PIL.Image.ANTIALIAS) # adjust the multiplying factor of height and width
                resizedImage.save(subdir_todays + '/' + file, "PNG")
                fp.close()
                resizedImage.close()
                # fill dictionary for reference in filling label
                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.

Author

Avatar
Connor Moore

3rd year computer engineering student.

Related Content

Comments


You May Also Like