aerodynamics
Published © LGPL

Weather and news station (e-paper and Raspberry Pi)

Fed up of ugly weather station ? Build your own providing current weather and forecast but also air pollution tracking and top news !

IntermediateFull instructions provided2 hours4,732
Weather and news station (e-paper and Raspberry Pi)

Things used in this project

Hardware components

Raspberry Pi Zero Wireless
Raspberry Pi Zero Wireless
×1
Waveshare 7.5inch e paper
×1
Picture Frame
×1

Software apps and online services

Raspbian
Raspberry Pi Raspbian
Open Weather Map
News API

Story

Read more

Code

Main code

Python
To be launch to run the project
###########################################
#
# Author : Aerodynamics
# Date : 21 jan 2021
# Version : 1.1
#
###########################################

# -*- coding:utf-8 -*-
# -*- coding:utf-8 -*-

from weather import *
from news import *
from display import *
import json

lat = "48.85" # Paris, France :)
lon = "2.39"
api_key_weather = ""
api_key_news = ""
debug = 0 # If debug != 0 -> debug on
if debug ==0:
    import epd7in5b_V2
else:
    pass

def map_resize(val, in_mini, in_maxi, out_mini, out_maxi):
    if in_maxi - in_mini != 0:
        out_temp = (val - in_mini) * (out_maxi - out_mini) // (in_maxi - in_mini) + out_mini
    else:
        out_temp = out_mini
    return out_temp


def main():
    ############################################################################
    # FRAME
    display.draw_black.rectangle((5, 5, 795, 475), fill=255, outline=0, width=2)  # INNER FRAME
    display.draw_black.line((540, 5, 540, 350), fill=0, width=1)  # VERTICAL SEPARATION
    display.draw_black.line((350, 5, 350, 350), fill=0, width=1)  # VERTICAL SEPARATION slim
    display.draw_black.line((5, 350, 795, 350), fill=0, width=1)  # HORIZONTAL SEPARATION

    # UPDATED AT
    display.draw_black.text((10, 8), "Mis  jour le " + weather.current_time(), fill=0, font=font8)

    ############################################################################
    # CURRENT WEATHER
    display.draw_icon(20, 55, "r", 75, 75,
                      weather.weather_description(weather.current_weather())[0])  # CURRENT WEATHER ICON
    display.draw_black.text((120, 15), weather.current_temp(), fill=0, font=font48)  # CURRENT TEMP
    display.draw_black.text((230, 15), weather.current_hum(), fill=0, font=font48)  # CURRENT HUM
    display.draw_black.text((245, 65), "Humidit", fill=0, font=font12)  # LABEL "HUMIDITY"
    display.draw_black.text((120, 75), weather.current_wind()[0] + " " + weather.current_wind()[1], fill=0, font=font24)

    display.draw_icon(120, 105, "b", 35, 35, "sunrise")  # SUNRISE ICON
    display.draw_black.text((160, 110), weather.current_sunrise(), fill=0, font=font16)  # SUNRISE TIME
    display.draw_icon(220, 105, "b", 35, 35, "sunset")  # SUNSET ICON
    display.draw_black.text((260, 110), weather.current_sunset(), fill=0, font=font16)  # SUNSET TIME

    ############################################################################
    # NEXT HOUR RAIN
    try :
        data_rain = weather.rain_next_hour()

        # FRAME
        display.draw_black.text((20, 150), "Pluie dans l'heure - " + time.strftime("%H:%M", time.localtime()), fill=0,
                                font=font16)  # NEXT HOUR RAIN LABEL
        display.draw_black.rectangle((20, 175, 320, 195), fill=255, outline=0, width=1)  # Red rectangle = rain

        # LABEL
        for i in range(len(data_rain)):
            display.draw_black.line((20 + i * 50, 175, 20 + i * 50, 195), fill=0, width=1)
            display.draw_black.text((20 + i * 50, 195), data_rain[i][0], fill=0, font=font16)
            if data_rain[i][1] != 0:
                display.draw_red.rectangle((20 + i * 50, 175, 20 + (i + 1) * 50, 195), fill=0)
    except:
        pass

    ############################################################################
    # HOURLY FORECAST
    display.draw_black.text((30, 227), "+3h", fill=0, font=font16)  # +3h LABEL
    display.draw_black.text((150, 227), "+6h", fill=0, font=font16)  # +6h LABEL
    display.draw_black.text((270, 227), "+12h", fill=0, font=font16)  # +12h LABEL
    # 3H
    display.draw_icon(25, 245, "r", 50, 50,
                      weather.weather_description(weather.hourly_forecast()["+3h"]["id"])[0])  # +3H WEATHER ICON
    display.draw_black.text((25, 295), weather.weather_description(weather.hourly_forecast()["+3h"]["id"])[1], fill=0,
                            font=font12)  # WEATHER DESCRIPTION +3h
    display.draw_black.text((35, 307), weather.hourly_forecast()["+3h"]["temp"], fill=0, font=font16)  # TEMP +3H
    display.draw_black.text((35, 323), weather.hourly_forecast()["+3h"]["pop"], fill=0, font=font16)  # POP +3H
    # +6h
    display.draw_icon(145, 245, "r", 50, 50,
                      weather.weather_description(weather.hourly_forecast()["+6h"]["id"])[0])  # +6H WEATHER ICON
    display.draw_black.text((145, 295), weather.weather_description(weather.hourly_forecast()["+6h"]["id"])[1], fill=0,
                            font=font12)  # WEATHER DESCRIPTION +6h
    display.draw_black.text((155, 307), weather.hourly_forecast()["+6h"]["temp"], fill=0, font=font16)  # TEMP +6H
    display.draw_black.text((155, 323), weather.hourly_forecast()["+6h"]["pop"], fill=0, font=font16)  # POP +6H
    # +12h
    display.draw_icon(265, 245, "r", 50, 50,
                      weather.weather_description(weather.hourly_forecast()["+12h"]["id"])[0])  # +12H WEATHER ICON
    display.draw_black.text((265, 295), weather.weather_description(weather.hourly_forecast()["+12h"]["id"])[1], fill=0,
                            font=font12)  # WEATHER DESCRIPTION +12h
    display.draw_black.text((275, 307), weather.hourly_forecast()["+12h"]["temp"], fill=0, font=font16)  # TEMP +12H
    display.draw_black.text((275, 323), weather.hourly_forecast()["+12h"]["pop"], fill=0, font=font16)  # POP +12H

    ############################################################################
    # DAILY FORECAST
    # +24h
    display.draw_black.text((360, 30), weather.daily_forecast()["+24h"]["date"], fill=0, font=font16)  # +24H DAY
    display.draw_icon(400, 50, "r", 50, 50,
                      weather.weather_description(weather.daily_forecast()["+24h"]["id"])[0])  # +24H WEATHER ICON
    display.draw_black.text((465, 50), weather.daily_forecast()["+24h"]["min"], fill=0, font=font14)
    display.draw_black.text((498, 50), "min", fill=0, font=font14)  # +24H MIN TEMPERATURE
    display.draw_black.text((465, 65), weather.daily_forecast()["+24h"]["max"], fill=0, font=font14)
    display.draw_black.text((498, 65), "max", fill=0, font=font14)  # +24H MAX TEMPERATURE
    display.draw_black.text((465, 80), weather.daily_forecast()["+24h"]["pop"], fill=0, font=font14)
    display.draw_black.text((498, 80), "pluie", fill=0, font=font14)  # +24H RAIN PROBABILITY

    # +48h
    display.draw_black.text((360, 105), weather.daily_forecast()["+48h"]["date"], fill=0, font=font16)  # +48H DAY
    display.draw_icon(400, 125, "r", 50, 50,
                      weather.weather_description(weather.daily_forecast()["+48h"]["id"])[0])  # +48H WEATHER ICON
    display.draw_black.text((465, 125), weather.daily_forecast()["+48h"]["min"], fill=0, font=font14)
    display.draw_black.text((498, 125), "min", fill=0, font=font14)  # +48H MIN TEMPERATURE
    display.draw_black.text((465, 140), weather.daily_forecast()["+48h"]["max"], fill=0, font=font14)
    display.draw_black.text((498, 140), "max", fill=0, font=font14)  # +48H MAX TEMPERATURE
    display.draw_black.text((465, 155), weather.daily_forecast()["+48h"]["pop"], fill=0, font=font14)
    display.draw_black.text((498, 155), "pluie", fill=0, font=font14)  # +48H RAIN PROBABILITY

    # +72h
    display.draw_black.text((360, 180), weather.daily_forecast()["+72h"]["date"], fill=0, font=font16)  # +72H DAY
    display.draw_icon(400, 200, "r", 50, 50,
                      weather.weather_description(weather.daily_forecast()["+72h"]["id"])[0])  # +72H WEATHER ICON
    display.draw_black.text((465, 200), weather.daily_forecast()["+72h"]["min"], fill=0, font=font14)
    display.draw_black.text((498, 200), "min", fill=0, font=font14)  # +72H MIN TEMPERATURE
    display.draw_black.text((465, 215), weather.daily_forecast()["+72h"]["max"], fill=0, font=font14)
    display.draw_black.text((498, 215), "max", fill=0, font=font14)  # +72H MAX TEMPERATURE
    display.draw_black.text((465, 230), weather.daily_forecast()["+72h"]["pop"], fill=0, font=font14)
    display.draw_black.text((498, 230), "pluie", fill=0, font=font14)  # +72H RAIN PROBABILITY

    # +96h
    display.draw_black.text((360, 255), weather.daily_forecast()["+96h"]["date"], fill=0, font=font16)  # +96H DAY
    display.draw_icon(400, 275, "r", 50, 50,
                      weather.weather_description(weather.daily_forecast()["+96h"]["id"])[0])  # +96H WEATHER ICON
    display.draw_black.text((465, 275), weather.daily_forecast()["+96h"]["min"], fill=0, font=font14)
    display.draw_black.text((498, 275), "min", fill=0, font=font14)  # +96H MIN TEMPERATURE
    display.draw_black.text((465, 290), weather.daily_forecast()["+96h"]["max"], fill=0, font=font14)
    display.draw_black.text((498, 290), "max", fill=0, font=font14)  # +96H MAX TEMPERATURE
    display.draw_black.text((465, 305), weather.daily_forecast()["+96h"]["pop"], fill=0, font=font14)
    display.draw_black.text((498, 305), "pluie", fill=0, font=font14)  # +96H RAIN PROBABILITY

    ############################################################################
    # GRAPHS
    # PRESSURE & TEMPERATURE
    pression = []
    temperature = []
    maxi = 440  # MAX VERT. PIXEL OF THE GRAPH
    mini = 360  # MIN VERT PIXEL OF THE GRAPH
    x = [55, 105, 155, 205, 255, 305, 355]  # X value of the points
    j = ["J-6", "J-5", "J-4", "J-3", "J-2", "J-1", "J"]  # LABELS


    weather.graph_p_t()
    data = weather.prevision[1]
    global been_reboot #If reboot load the saved infos
    if (been_reboot == 1):
        try :
            file = open("saved.txt","r")
            weather.prevision[1] = json.loads(file.read())
            data = weather.prevision[1]
            been_reboot = 0
            file.close()
        except:
            pass

    else :
        pass

    file = open("saved.txt", "w") #savinf to file
    file.write(str(data))
    file.close()
    for i in range(len(data)):
        pression.append(data[i][0])
        temperature.append(data[i][1])

    # PRESSURE
    display.draw_black.line((40, mini, 40, maxi + 20), fill=0, width=1)  # GRAPH AXIS
    display.draw_black.text((10, mini), str(max(pression)), fill=0, font=font12)  # MAX AXIS GRAPH LABEL
    display.draw_black.text((10, maxi), str(min(pression)), fill=0, font=font12)  # MIN AXIS GRAPH LABEL
    display.draw_black.text((10, mini + (maxi - mini) // 2), str((max(pression) + min(pression)) // 2), fill=0,
                            font=font12)  # MID VALUE LABEL
    for i in range(len(x)):  # UPDATE CIRCLE POINTS
        display.draw_black.text((x[i], 455), j[i], fill=0, font=font12)
        display.draw_circle(x[i], map_resize(pression[i], min(pression), max(pression), maxi, mini), 3, "r")
    for i in range(len(x) - 1):  # UPDATE LINE
        display.draw_red.line((x[i], map_resize(pression[i], min(pression), max(pression), maxi, mini), x[i + 1],
                               map_resize(pression[i + 1], min(pression), max(pression), maxi, mini)), fill=0,
                              width=2)
    # TEMPERATURE
    display.draw_black.line((430, mini, 430, maxi + 20), fill=0, width=1)  # GRAPH AXIS
    display.draw_black.text((410, mini), str(max(temperature)), fill=0, font=font12)  # MAX AXIS GRAPH LABEL
    display.draw_black.text((410, maxi), str(min(temperature)), fill=0, font=font12)  # MIN AXIS GRAPH LABEL
    display.draw_black.text((410, mini + (maxi - mini) // 2), str((max(temperature) + min(temperature)) // 2), fill=0,
                            font=font12)  # MID VALUE LABEL
    for i in range(len(x)):  # UPDATE CIRCLE POINTS
        display.draw_black.text((x[i] + 400, 455), j[i], fill=0, font=font12)
        display.draw_circle(x[i] + 400, map_resize(temperature[i], min(temperature), max(temperature), maxi, mini), 3,
                            "r")
    for i in range(len(x) - 1):  # UPDATE LINE
        display.draw_red.line((x[i] + 400, map_resize(temperature[i], min(temperature), max(temperature), maxi, mini),
                               x[i + 1] + 400,
                               map_resize(temperature[i + 1], min(temperature), max(temperature), maxi, mini)),
                              fill=0, width=2)

    ############################################################################
    # ALERT AND POLLUTION

    ############################################################################
    # NEWS UPDATE
    news_selected = news.selected_title()
    display.draw_black.text((550, 15), "NEWS", fill=0, font=font24)
    for i in range(len(news_selected)):
        if len(news_selected) == 1:
            display.draw_black.text((550, 40), news_selected[0], fill=0, font=font14)
        elif len(news_selected[i]) <= 3 :
            for j in range(len(news_selected[i])):
                display.draw_black.text((550, 40 + j * 15 + i * 60), news_selected[i][j], fill=0, font=font14)
        else:
            for j in range(2):
                display.draw_black.text((550, 40 + j * 15 + i * 60), news_selected[i][j], fill=0, font=font14)
            display.draw_black.text((550, 40 + 2 * 15 + i * 60), news_selected[i][2] + "[...]", fill=0, font=font14)



    ############################################################################
    print("Updating screen...")

    try:
        print("\tClearing Screen...")
        if debug ==0:
            # display.im_black.show()
            # display.im_red.show()
            epd.init()
            time.sleep(1)
            epd.Clear()
            time.sleep(2)
            print("\tPrinting...")
            epd.display(epd.getbuffer(display.im_black), epd.getbuffer(display.im_red))
            print("Done")
            time.sleep(2)
            epd.sleep()  # ADVISED BY MANUFACTURER TO PROTECT THE SCREEN
            print("Going to sleep....")
        else :
            display.im_black.show()
            display.im_red.show()
    except:
        print("Printing error")
    print("------------")
    return True


if __name__ == "__main__":
    global been_reboot
    been_reboot=1

    if debug==0:
        epd = epd7in5b_V2.EPD()
    else :
        pass

    while True:
        try:
            weather = Weather(lat, lon, api_key_weather)
            # pollution = Pollution()
            news = News()
            break
        except:
            current_time = time.strftime("%d/%m/%Y %H:%M:%S", time.localtime())
            print("INITIALIZATION PROBLEM- @" + current_time)
            time.sleep(2)

    while True:
        try:
            # Defining objects
            current_time = time.strftime("%d/%m/%Y %H:%M", time.localtime())
            print("Begin update @" + current_time)
            print("Creating display")
            display = Display()
            # Update values
            weather.update()
            print("Weather Updated")
            # pollution.update(lat, lon, api_key_weather)
            news.update(api_key_news)
            print("News Updated")
            main()
            time.sleep(3600)
        except KeyboardInterrupt:
            if debug ==0 :
                epd.init()
                epd.Clear()
                time.sleep(2)
                epd.Dev_exit()
            else :
                pass
            exit()
        except:
            current_time = time.strftime("%d/%m/%Y %H:%M:%S", time.localtime())
            print("PROBLEM OCCURED WHILE REFRESHING - NEXT TRY 1000s - @" + current_time)
            time.sleep(1000)

Weather code

Python
Code to update and generate weather data
# -*- coding:utf-8 -*-

import time
import requests
import locale

locale.setlocale(locale.LC_TIME, '')

class Weather:
    def __init__(self, latitude, longitude, api_id):
        self.latitude = latitude
        self.longitude = longitude
        self.api_key = api_id
        self.prevision = [0, [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]]
        self.data = requests.get(
            f"https://api.openweathermap.org/data/2.5/onecall?lat={self.latitude}&lon={self.longitude}&lang=fr&appid={self.api_key}").json()
        self.prevision[0] = self.data["daily"][0]["dt"]
        self.prevision[1][6] = [self.data["daily"][0]["pressure"],
                                round(self.data["daily"][0]["temp"]["day"] - 273.15, 0)]
        pass

    def update(self):
        self.data = requests.get(
            f"https://api.openweathermap.org/data/2.5/onecall?lat={self.latitude}&lon={self.longitude}&lang=fr&appid={self.api_key}").json()
        return self.data

    def current_time(self):
        return time.strftime("%d/%m/%Y %H:%M", time.localtime(self.data["current"]["dt"]))

    def current_temp(self):
        return "{:.0f}".format(self.data["current"]["temp"] - 273.15) + "C"

    def current_hum(self):
        return "{:.0f}".format(self.data["current"]["humidity"]) + "%"

    def current_cloud_cov(self):
        return "{:.0f}".format(self.data["current"]["clouds"]) + "%"

    def current_sunrise(self):
        return time.strftime("%H:%M", time.localtime(self.data["current"]["sunrise"]))

    def current_sunset(self):
        return time.strftime("%H:%M", time.localtime(self.data["current"]["sunset"]))

    def current_wind(self):
        deg = self.data["current"]["wind_deg"]
        if deg < 30 or deg >= 330:
            direction = "N"
        elif 30 <= deg < 60:
            direction = "NE"
        elif 60 <= deg < 120:
            direction = "E"
        elif 120 <= deg < 150:
            direction = "SE"
        elif 150 <= deg < 210:
            direction = "S"
        elif 210 <= deg < 240:
            direction = "SO"
        elif 240 <= deg < 300:
            direction = "O"
        elif 300 <= deg < 330:
            direction = "NO"
        else:
            direction = "N/A"
        return "{:.0f}".format(self.data["current"]["wind_speed"] * 3.6) + "km/h", direction

    def current_weather(self):
        description = self.data["current"]["weather"][0]["id"]
        return description

    def rain_next_hour(self):
        input_minutely = self.data["minutely"]
        rain = []
        rain_next_hour = [["+10'", 0], ["+20'", 0], ["+30'", 0], ["+40'", 0], ["+50'", 0], ["+1h", 0]]
        for i in range(len(input_minutely)):
            rain.append(input_minutely[i]["precipitation"])
        for i in range(6):
            rain_next_hour[i][1] = sum(rain[i * 10 + 1:i * 10 + 10])
        return rain_next_hour

    def hourly_forecast(self):
        hourly = {"+3h": {"temp": "", "pop": "", "id": ""}, "+6h": {"temp": "", "pop": "", "id": ""},
                  "+12h": {"temp": "", "pop": "", "id": ""}}
        # Forecast +3h
        hourly["+3h"]["temp"] = "{:.0f}".format(self.data["hourly"][3]["temp"] - 273.15) + "C"
        hourly["+3h"]["pop"] = "{:.0f}".format(self.data["hourly"][3]["pop"] * 100) + "%"
        hourly["+3h"]["id"] = self.data["hourly"][3]["weather"][0]["id"]
        # Forecast +3h
        hourly["+6h"]["temp"] = "{:.0f}".format(self.data["hourly"][6]["temp"] - 273.15) + "C"
        hourly["+6h"]["pop"] = "{:.0f}".format(self.data["hourly"][6]["pop"] * 100) + "%"
        hourly["+6h"]["id"] = self.data["hourly"][6]["weather"][0]["id"]
        # Forecast +3h
        hourly["+12h"]["temp"] = "{:.0f}".format(self.data["hourly"][12]["temp"] - 273.15) + "C"
        hourly["+12h"]["pop"] = "{:.0f}".format(self.data["hourly"][12]["pop"] * 100) + "%"
        hourly["+12h"]["id"] = self.data["hourly"][12]["weather"][0]["id"]

        return hourly

    def daily_forecast(self):
        daily = {"+24h": {"date": "", "min": "", "max": "", "pop": "", "id": ""},
                 "+48h": {"date": "", "min": "", "max": "", "pop": "", "id": ""},
                 "+72h": {"date": "", "min": "", "max": "", "pop": "", "id": ""},
                 "+96h": {"date": "", "min": "", "max": "", "pop": "", "id": ""}}
        i = 1
        for key in daily.keys():
            daily[key]["date"] = time.strftime("%A", time.localtime(self.data["daily"][i]["dt"]))
            daily[key]["min"] = "{:.0f}".format(self.data["daily"][i]["temp"]["min"] - 273.15) + "C"
            daily[key]["max"] = "{:.0f}".format(self.data["daily"][i]["temp"]["max"] - 273.15) + "C"
            daily[key]["pop"] = "{:.0f}".format(self.data["daily"][i]["pop"] * 100) + "%"
            daily[key]["id"] = self.data["daily"][i]["weather"][0]["id"]
            i += 1

        return daily

    def graph_p_t(self):
        if self.prevision[0] != self.data["daily"][0]["dt"]:
            self.prevision[0] = self.data["daily"][0]["dt"]
            self.prevision = [self.prevision[0], self.prevision[1][1:]]
            self.prevision[1].append(
                [self.data["daily"][0]["pressure"], round(self.data["daily"][0]["temp"]["day"] - 273.15, 0)])

    def weather_description(self, id):
        icon = "sun"
        weather_detail = "Beau temps"
        if id // 100 != 8:
            id = id // 100
            if id == 2:
                icon = "thunder"
                weather_detail = "Orage"
            elif id == 3:
                icon = "drizzle"
                weather_detail = "Bruine"
            elif id == 5:
                icon = "rain"
                weather_detail = "Pluie"
            elif id == 6:
                icon = "snow"
                weather_detail = "Neige"
            elif id == 7:
                icon = "atm"
                weather_detail = "Brouillard"
            else:
                weather_detail = "Erreur"
        else:
            if id == 801:
                icon = "25_clouds"
                weather_detail = "Peu nuageux"
            elif id == 802:
                icon = "50_clouds"
                weather_detail = "Nuageux"
            elif id == 803 or id == 804:
                icon = "100_clouds"
                weather_detail = "Couvert"

        return icon, weather_detail

    def alert(self):
        try:
            alert_descrip = self.data["alerts"][0]["event"]
        except:
            alert_descrip = 0
        return alert_descrip


class Pollution:
    def __init__(self):
        self.max_lvl_pollution = {"co": 10000, "no": 30, "no2": 40, "o3": 120, "so2": 50, "pm2_5": 20, "pm10": 30,
                                  "nh3": 100}
        pass

    def update(self, lattitude, longitude, api_id):
        self.data = requests.get(
            f"http://api.openweathermap.org/data/2.5/air_pollution?lat={lattitude}&lon={longitude}&appid={api_id}").json()
        return self.data

    def co(self):
        return self.data["list"][0]["components"]["co"]

    def no(self):
        return self.data["list"][0]["components"]["no"]

    def no2(self):
        return self.data["list"][0]["components"]["no2"]

    def o3(self):
        return self.data["list"][0]["components"]["o3"]

    def so2(self):
        return self.data["list"][0]["components"]["so2"]

    def pm2_5(self):
        return self.data["list"][0]["components"]["pm2_5"]

    def pm10(self):
        return self.data["list"][0]["components"]["pm10"]

    def nh3(self):
        return self.data["list"][0]["components"]["nh3"]

Display code

Python
Some few display class and functions
# -*- coding:utf-8 -*-

from PIL import Image, ImageDraw, ImageFont

font_choice = 7
if font_choice == 1:
    project_font = "font/Architects_Daughter/ArchitectsDaughter-Regular.ttf"
elif font_choice == 2:
    project_font = "font/Inconsolata/static/Inconsolata-SemiBold.ttf"
elif font_choice == 3:
    project_font = "font/Comfortaa/static/Comfortaa-Light.ttf"
elif font_choice == 4:
    project_font = "font/Open_Sans/OpenSans-SemiBold.ttf"
elif font_choice == 5:
    project_font = "font/Roboto/Roboto-Regular.ttf"
elif font_choice == 6:
    project_font = "font/Roboto_Slab/static/RobotoSlab-Regular.ttf"
elif font_choice == 7:
    project_font = "font/Ubuntu_Mono/UbuntuMono-Bold.ttf"
else:
    project_font = "font/Open_Sans/OpenSans-SemiBold.ttf"


font8 = ImageFont.truetype(project_font, 8)
font12 = ImageFont.truetype(project_font, 12)
font14 = ImageFont.truetype(project_font, 14)
font16 = ImageFont.truetype(project_font, 16)
font24 = ImageFont.truetype(project_font, 24)
font48 = ImageFont.truetype(project_font, 48)


class Display:
    def __init__(self):
        self.im_black = Image.new('1', (800, 480), 255)
        self.im_red = Image.new('1', (800, 480), 255)
        self.draw_black = ImageDraw.Draw(self.im_black)
        self.draw_red = ImageDraw.Draw(self.im_red)

    def draw_circle(self, x, y, r, c):
        if c == "b":
            self.draw_black.ellipse((x - r, y - r, x + r, y + r), fill=0)
        else:
            self.draw_red.ellipse((x - r, y - r, x + r, y + r), fill=0)

    def draw_icon(self, x, y, c, l, h, icon):
        im_icon = Image.open("icons/" + icon + ".png")
        # im_icon = im_icon.convert("LA")
        im_icon = im_icon.resize((l, h))
        if c == "b":
            self.im_black.paste(im_icon, (x, y), im_icon)
        else:
            self.im_red.paste(im_icon, (x, y), im_icon)

News code

Python
Code to update and generate news data
# -*- coding:utf-8 -*-

import requests
import textwrap

class News:
    def __init__(self):
        pass

    def update(self, api_id):
        self.news_list = requests.get(
            f"https://newsapi.org/v2/top-headlines?sources=google-news-fr&apiKey={api_id}").json()
        return self.news_list

    def selected_title(self):
        list_news = []
        if self.news_list["status"] == "ok":
            for i in range(len(self.news_list["articles"])):
                line = self.news_list["articles"][i]["title"]
                line = textwrap.wrap(line, width=30)
                list_news.append(line)
        else:
            list_news = ["Problme de chargement des news"]
        return list_news

Files from waveshare 1/2

Python
# *****************************************************************************
# * | File        :	  epd7in5b_V2.py
# * | Author      :   Waveshare team
# * | Function    :   Electronic paper driver
# * | Info        :
# *----------------
# * | This version:   V4.1
# * | Date        :   2020-11-30
# # | Info        :   python demo
# -----------------------------------------------------------------------------
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documnetation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to  whom the Software is
# furished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#


import logging
import epdconfig

# Display resolution
EPD_WIDTH       = 800
EPD_HEIGHT      = 480

class EPD:
    def __init__(self):
        self.reset_pin = epdconfig.RST_PIN
        self.dc_pin = epdconfig.DC_PIN
        self.busy_pin = epdconfig.BUSY_PIN
        self.cs_pin = epdconfig.CS_PIN
        self.width = EPD_WIDTH
        self.height = EPD_HEIGHT

    # Hardware reset
    def reset(self):
        epdconfig.digital_write(self.reset_pin, 1)
        epdconfig.delay_ms(200) 
        epdconfig.digital_write(self.reset_pin, 0)
        epdconfig.delay_ms(4)
        epdconfig.digital_write(self.reset_pin, 1)
        epdconfig.delay_ms(200)   

    def send_command(self, command):
        epdconfig.digital_write(self.dc_pin, 0)
        epdconfig.digital_write(self.cs_pin, 0)
        epdconfig.spi_writebyte([command])
        epdconfig.digital_write(self.cs_pin, 1)

    def send_data(self, data):
        epdconfig.digital_write(self.dc_pin, 1)
        epdconfig.digital_write(self.cs_pin, 0)
        epdconfig.spi_writebyte([data])
        epdconfig.digital_write(self.cs_pin, 1)
        
    def ReadBusy(self):
        logging.debug("e-Paper busy")
        self.send_command(0x71)
        busy = epdconfig.digital_read(self.busy_pin)
        while(busy == 0):
            self.send_command(0x71)
            busy = epdconfig.digital_read(self.busy_pin)
        epdconfig.delay_ms(200)
            
    def init(self):
        if (epdconfig.module_init() != 0):
            return -1
            
        self.reset()
        
        self.send_command(0x01);			#POWER SETTING
        self.send_data(0x07);
        self.send_data(0x07);    #VGH=20V,VGL=-20V
        self.send_data(0x3f);		#VDH=15V
        self.send_data(0x3f);		#VDL=-15V

        self.send_command(0x04); #POWER ON
        epdconfig.delay_ms(100);
        self.ReadBusy();

        self.send_command(0X00);			#PANNEL SETTING
        self.send_data(0x0F);   #KW-3f   KWR-2F	BWROTP 0f	BWOTP 1f

        self.send_command(0x61);        	#tres
        self.send_data(0x03);		#source 800
        self.send_data(0x20);
        self.send_data(0x01);		#gate 480
        self.send_data(0xE0);

        self.send_command(0X15);
        self.send_data(0x00);

        self.send_command(0X50);			#VCOM AND DATA INTERVAL SETTING
        self.send_data(0x11);
        self.send_data(0x07);

        self.send_command(0X60);			#TCON SETTING
        self.send_data(0x22);

        self.send_command(0x65);
        self.send_data(0x00);
        self.send_data(0x00);
        self.send_data(0x00);
        self.send_data(0x00);
    
        return 0

    def getbuffer(self, image):
        # logging.debug("bufsiz = ",int(self.width/8) * self.height)
        buf = [0xFF] * (int(self.width/8) * self.height)
        image_monocolor = image.convert('1')
        imwidth, imheight = image_monocolor.size
        pixels = image_monocolor.load()
        logging.debug('imwidth = %d  imheight =  %d ',imwidth, imheight)
        if(imwidth == self.width and imheight == self.height):
            logging.debug("Horizontal")
            for y in range(imheight):
                for x in range(imwidth):
                    # Set the bits for the column of pixels at the current position.
                    if pixels[x, y] == 0:
                        buf[int((x + y * self.width) / 8)] &= ~(0x80 >> (x % 8))
        elif(imwidth == self.height and imheight == self.width):
            logging.debug("Vertical")
            for y in range(imheight):
                for x in range(imwidth):
                    newx = y
                    newy = self.height - x - 1
                    if pixels[x, y] == 0:
                        buf[int((newx + newy*self.width) / 8)] &= ~(0x80 >> (y % 8))
        return buf

    def display(self, imageblack, imagered):
        self.send_command(0x10)
        for i in range(0, int(self.width * self.height / 8)):
            self.send_data(imageblack[i]);
        
        self.send_command(0x13)
        for i in range(0, int(self.width * self.height / 8)):
            self.send_data(~imagered[i]);
        
        self.send_command(0x12)
        epdconfig.delay_ms(100)
        self.ReadBusy()
        
    def Clear(self):
        self.send_command(0x10)
        for i in range(0, int(self.width * self.height / 8)):
            self.send_data(0xff)
            
        self.send_command(0x13)
        for i in range(0, int(self.width * self.height / 8)):
            self.send_data(0x00)
                
        self.send_command(0x12)
        epdconfig.delay_ms(100)
        self.ReadBusy()

    def sleep(self):
        self.send_command(0x02) # POWER_OFF
        self.ReadBusy()
        
        self.send_command(0x07) # DEEP_SLEEP
        self.send_data(0XA5)
        
    def Dev_exit(self):
        epdconfig.module_exit()
### END OF FILE ###

Files from waveshare 2/2

Python
# /*****************************************************************************
# * | File        :	  epdconfig.py
# * | Author      :   Waveshare team
# * | Function    :   Hardware underlying interface
# * | Info        :
# *----------------
# * | This version:   V1.0
# * | Date        :   2019-06-21
# * | Info        :   
# ******************************************************************************
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documnetation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to  whom the Software is
# furished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#

import os
import logging
import sys
import time


class RaspberryPi:
    # Pin definition
    RST_PIN         = 17
    DC_PIN          = 25
    CS_PIN          = 8
    BUSY_PIN        = 24

    def __init__(self):
        import spidev
        import RPi.GPIO

        self.GPIO = RPi.GPIO

        # SPI device, bus = 0, device = 0
        self.SPI = spidev.SpiDev(0, 0)

    def digital_write(self, pin, value):
        self.GPIO.output(pin, value)

    def digital_read(self, pin):
        return self.GPIO.input(pin)

    def delay_ms(self, delaytime):
        time.sleep(delaytime / 1000.0)

    def spi_writebyte(self, data):
        self.SPI.writebytes(data)

    def module_init(self):
        self.GPIO.setmode(self.GPIO.BCM)
        self.GPIO.setwarnings(False)
        self.GPIO.setup(self.RST_PIN, self.GPIO.OUT)
        self.GPIO.setup(self.DC_PIN, self.GPIO.OUT)
        self.GPIO.setup(self.CS_PIN, self.GPIO.OUT)
        self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN)
        self.SPI.max_speed_hz = 4000000
        self.SPI.mode = 0b00
        return 0

    def module_exit(self):
        logging.debug("spi end")
        self.SPI.close()

        logging.debug("close 5V, Module enters 0 power consumption ...")
        self.GPIO.output(self.RST_PIN, 0)
        self.GPIO.output(self.DC_PIN, 0)

        self.GPIO.cleanup()


implementation = RaspberryPi()


for func in [x for x in dir(implementation) if not x.startswith('_')]:
    setattr(sys.modules[__name__], func, getattr(implementation, func))


### END OF FILE ###

Credits

aerodynamics

aerodynamics

4 projects • 18 followers

Comments