weather api working -> 2025-01-27 17:49:29

This commit is contained in:
pepev-nrt 2025-01-27 17:49:36 +01:00
parent 7a933631d9
commit a9c7381977
14 changed files with 288 additions and 17 deletions

4
.gitignore vendored
View file

@ -1,3 +1,7 @@
# Specific for my project
.cache.sqlite
# Python generic .gitignore
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files
__pycache__/ __pycache__/
*.py[cod] *.py[cod]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -1,9 +1,16 @@
{ {
"isDisplayConected": false, "isDisplayConected": false,
"screenWidth": 250, "screenWidth": 250,
"screenHeight": 122, "screenHeight": 122,
"imageWidth": 250, "imageWidth": 250,
"imageHeight": 122, "imageHeight": 122,
"rotateAngle": 180, "rotateAngle": 180,
"hourFormat": "12h"
"hourFormat": "12h",
"latitude": 40.4084,
"longitude": -3.6876,
"timezone": "Europe/Madrid",
"locales": "es_ES.UTF-8"
} }

View file

@ -11,12 +11,15 @@ CSS stylesheets in the "render" folder.
import datetime as dt import datetime as dt
import os import os
import sys import sys
import locale
import json import json
import logging import logging
from PIL import Image,ImageDraw,ImageFont from PIL import Image,ImageDraw,ImageFont
from weather.weather import WeatherHelper
def main(): def main():
# Basic configuration settings (user replaceable) # Basic configuration settings (user replaceable)
@ -32,6 +35,15 @@ def main():
imageHeight = config['imageHeight'] # Height of image to be generated for display. imageHeight = config['imageHeight'] # Height of image to be generated for display.
rotateAngle = config['rotateAngle'] # If image is rendered in portrait orientation, angle to rotate to fit screen rotateAngle = config['rotateAngle'] # If image is rendered in portrait orientation, angle to rotate to fit screen
hourFormat = config['hourFormat'] # The format the hour will be displayed. eg. 13:02 or 01:02 PM hourFormat = config['hourFormat'] # The format the hour will be displayed. eg. 13:02 or 01:02 PM
latitude = config['latitude'] # A float. The latitude for the Weather API.
longitude = config['longitude'] # A float. The longitude for the Weather API.
timezone = config['timezone'] # The timezone is necesary for the Weather API
locales = config['locales'] # Set the locales for the month name
weatherService = WeatherHelper(latitude, longitude, timezone)
weather_data = weatherService.fetch_open_meteo_data()
locale.setlocale(locale.LC_TIME, locales)
# Set the hour, this is important to see what time the e-Paper has been syncronized # Set the hour, this is important to see what time the e-Paper has been syncronized
if hourFormat == "12h": if hourFormat == "12h":
@ -39,32 +51,68 @@ def main():
else: else:
time = dt.datetime.now().strftime("%H:%M") time = dt.datetime.now().strftime("%H:%M")
day = dt.datetime.now().strftime("%A, %d %b")
assets = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'assets') assets = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'assets')
weather_icons = os.path.join(assets, 'weather-icons')
fonts = os.path.join(assets, 'fonts') fonts = os.path.join(assets, 'fonts')
# Drawing on the image # Drawing on the image
font15 = ImageFont.truetype(os.path.join(fonts, 'wavesharefont.ttc'), 15)
font24 = ImageFont.truetype(os.path.join(fonts, 'wavesharefont.ttc'), 24)
font8 = ImageFont.truetype(os.path.join(os.path.join(fonts, 'pixel_operator'), 'PixelOperator8.ttf'), 8) font8 = ImageFont.truetype(os.path.join(os.path.join(fonts, 'pixel_operator'), 'PixelOperator8.ttf'), 8)
font16 = ImageFont.truetype(os.path.join(os.path.join(fonts, 'pixel_operator'), 'PixelOperator.ttf'), 16) font16 = ImageFont.truetype(os.path.join(os.path.join(fonts, 'pixel_operator'), 'PixelOperator.ttf'), 16)
font16_bold = ImageFont.truetype(os.path.join(os.path.join(fonts, 'pixel_operator'), 'PixelOperator-Bold.ttf'), 16) font16_bold = ImageFont.truetype(os.path.join(os.path.join(fonts, 'pixel_operator'), 'PixelOperator-Bold.ttf'), 16)
#image = Image.open('assets/test4.bmp')
image = Image.new('1', (imageWidth, imageHeight), 255) # 255: clear the frame image = Image.new('1', (imageWidth, imageHeight), 255) # 255: clear the frame
draw = ImageDraw.Draw(image) draw = ImageDraw.Draw(image)
draw.text((0, 0), time, font = font16_bold, fill = 0) # draw the current time in the top left corner draw.text((0, 0), f"{day}. {time}", font = font16_bold, fill = 0) # draw the current time in the top left corner
draw.line([(0,16),(250,16)], fill = 0,width = 2) # Frames
draw.line([(125,16),(125,122)], fill = 0,width = 2) draw.line([(0,16),(250,16)], fill = 0,width = 2) # Draw a line below the date
draw.line([(90,17),(90,122)], fill = 0,width = 2) # Line dividing the screeen in two parts
cal_icon = Image.open(os.path.join(assets, "cal-icon3.bmp"))
image.paste(cal_icon, (129,20)) # Calendar icon
cal_coordinates_x = 207
cal_coordinates_y = 2
draw.text((160, 26), 'Agenda', font = font16_bold, fill = 0) cal_icon = Image.open(os.path.join(assets, "cal-icon3.bmp")) # calendar icon
# This seems complicates, but it just draw a white rectangle bellow the calendar
draw.rectangle([(cal_coordinates_x-2, cal_coordinates_y),(cal_coordinates_x + 28, cal_coordinates_y + 30)], fill = 255)
image.paste(cal_icon, (cal_coordinates_x, cal_coordinates_y))
# LEFT SEGMENT (WEATHER)
# Todays information
today_icon = weatherService.iconize_weather(weather_data["current_weather_code"])
today_icon = Image.open(os.path.join(weather_icons, today_icon))
image.paste(today_icon, (29,20))
today_temperature = f"{str(weather_data['current_temperature_2m'])}°"
draw.text((66,28), today_temperature, font = font16_bold, fill = 0) # The tomorrows temperature is bellow the icon
#draw.text((160, 26), 'Agenda', font = font16_bold, fill = 0)
# All the tomorrows information
tomorrow_icon = weatherService.iconize_weather(weather_data["tomorrow_weather_code"])
tomorrow_icon = Image.open(os.path.join(weather_icons, tomorrow_icon))
image.paste(tomorrow_icon, (6,74)) # The icon is in the bottom left corner
tomorrow_temperature = f"{str(weather_data['tomorrow_temperature_2m_max'])}/{str(weather_data['tomorrow_temperature_2m_min'])}°"
draw.text((6,106), tomorrow_temperature, font = font16_bold, fill = 0) # The tomorrows temperature is bellow the icon
# All the day after tomorrows information
day_after_icon = weatherService.iconize_weather(weather_data["day_after_weather_code"])
day_after_icon = Image.open(os.path.join(weather_icons, day_after_icon))
image.paste(day_after_icon, (52,74))
day_after_temperature = f"{str(weather_data['day_after_temperature_2m_max'])}/{str(weather_data['day_after_temperature_2m_min'])}°"
draw.text((52,106), day_after_temperature, font = font16_bold, fill = 0)
# RIGHT SEGMENT (CALENDAR/AGENDA)
# TODO: writing this part and also the logic part
# draw.line([(0,50),(50,0)], fill = 0,width = 1) # draw.line([(0,50),(50,0)], fill = 0,width = 1)
# draw.chord((10, 60, 50, 100), 0, 360, fill = 0) # draw.chord((10, 60, 50, 100), 0, 360, fill = 0)
@ -78,7 +126,6 @@ def main():
image.save(os.path.join(assets, 'output.bmp')) image.save(os.path.join(assets, 'output.bmp'))
if isDisplayConected: if isDisplayConected:
from display.display import DisplayHelper from display.display import DisplayHelper

View file

@ -0,0 +1,65 @@
import openmeteo_requests
import requests_cache
import pandas as pd
from retry_requests import retry
# Setup the Open-Meteo API client with cache and retry on error
cache_session = requests_cache.CachedSession('.cache', expire_after = 3600)
retry_session = retry(cache_session, retries = 5, backoff_factor = 0.2)
openmeteo = openmeteo_requests.Client(session = retry_session)
# Make sure all required weather variables are listed here
# The order of variables in hourly or daily is important to assign them correctly below
url = "https://api.open-meteo.com/v1/forecast"
params = {
"latitude": 40.408,
"longitude": -3.688,
"current": ["temperature_2m", "is_day", "weather_code"],
"daily": ["weather_code", "temperature_2m_max", "temperature_2m_min"],
"timezone": "Europe/Madrid",
"forecast_days": 3
}
responses = openmeteo.weather_api(url, params=params)
# Process first location. Add a for-loop for multiple locations or weather models
response = responses[0]
print(f"Coordinates {response.Latitude()}°N {response.Longitude()}°E")
print(f"Elevation {response.Elevation()} m asl")
print(f"Timezone {response.Timezone()} {response.TimezoneAbbreviation()}")
print(f"Timezone difference to GMT+0 {response.UtcOffsetSeconds()} s")
# Current values. The order of variables needs to be the same as requested.
current = response.Current()
current_temperature_2m = current.Variables(0).Value()
current_is_day = current.Variables(1).Value()
current_weather_code = current.Variables(2).Value()
print(f"Current time {current.Time()}")
print(f"Current temperature_2m {current_temperature_2m}")
print(f"Current is_day {current_is_day}")
print(f"Current weather_code {current_weather_code}")
# Process daily data. The order of variables needs to be the same as requested.
daily = response.Daily()
daily_weather_code = daily.Variables(0).ValuesAsNumpy()
daily_temperature_2m_max = daily.Variables(1).ValuesAsNumpy()
daily_temperature_2m_min = daily.Variables(2).ValuesAsNumpy()
daily_data = {"date": pd.date_range(
start = pd.to_datetime(daily.Time(), unit = "s", utc = True),
end = pd.to_datetime(daily.TimeEnd(), unit = "s", utc = True),
freq = pd.Timedelta(seconds = daily.Interval()),
inclusive = "left"
)}
daily_data["weather_code"] = daily_weather_code
daily_data["temperature_2m_max"] = daily_temperature_2m_max
daily_data["temperature_2m_min"] = daily_temperature_2m_min
daily_dataframe = pd.DataFrame(data = daily_data)
print(daily_dataframe)

View file

@ -0,0 +1,148 @@
#!/usr/bin python3
# -*- coding: utf-8 -*-
"""
This part of the code exposes functions to fetch data from the open-meteo.com API.
The bellow code is the generated by the https://open-meteo.com/en/docs itself.
It has been slighly modified to return a dictionary with all the information nedeed.
"""
import openmeteo_requests
import requests_cache
#import pandas as pd
from retry_requests import retry
class WeatherHelper:
def __init__(self, latitude: float, longitude: float, timezone: str):
self.latitude = latitude
self.longitude = longitude
self.timezone = timezone
def fetch_open_meteo_data(self) -> dict:
# Setup the Open-Meteo API client with cache and retry on error
cache_session = requests_cache.CachedSession('.cache', expire_after = 3600)
retry_session = retry(cache_session, retries = 5, backoff_factor = 0.2)
openmeteo = openmeteo_requests.Client(session = retry_session)
# Make sure all required weather variables are listed here
# The order of variables in hourly or daily is important to assign them correctly below
url = "https://api.open-meteo.com/v1/forecast"
params = {
"latitude": self.latitude,
"longitude": self.longitude,
"current": ["temperature_2m", "is_day", "weather_code"],
"daily": ["weather_code", "temperature_2m_max", "temperature_2m_min"],
"timezone": self.timezone,
"forecast_days": 3
}
responses = openmeteo.weather_api(url, params=params)
# Process first location. Add a for-loop for multiple locations or weather models
response = responses[0]
#print(f"Coordinates {response.Latitude()}°N {response.Longitude()}°E")
#print(f"Elevation {response.Elevation()} m asl")
#print(f"Timezone {response.Timezone()} {response.TimezoneAbbreviation()}")
#print(f"Timezone difference to GMT+0 {response.UtcOffsetSeconds()} s")
# Current values. The order of variables needs to be the same as requested.
current = response.Current()
current_temperature_2m = current.Variables(0).Value()
current_is_day = current.Variables(1).Value()
current_weather_code = current.Variables(2).Value()
#print(f"Current time {current.Time()}")
#print(f"Current temperature_2m {current_temperature_2m}")
#print(f"Current is_day {current_is_day}")
#print(f"Current weather_code {current_weather_code}")
# Process daily data. The order of variables needs to be the same as requested.
daily = response.Daily()
daily_weather_code = daily.Variables(0).ValuesAsNumpy()
daily_temperature_2m_max = daily.Variables(1).ValuesAsNumpy()
daily_temperature_2m_min = daily.Variables(2).ValuesAsNumpy()
#daily_data = {"date": pd.date_range(
# start = pd.to_datetime(daily.Time(), unit = "s", utc = True),
# end = pd.to_datetime(daily.TimeEnd(), unit = "s", utc = True),
# freq = pd.Timedelta(seconds = daily.Interval()),
# inclusive = "left"
#)}
#daily_data["weather_code"] = daily_weather_code
#daily_data["temperature_2m_max"] = daily_temperature_2m_max
#daily_data["temperature_2m_min"] = daily_temperature_2m_min
#daily_dataframe = pd.DataFrame(data = daily_data)
#print(daily_dataframe)
# Here we are creating the dictionary that will be returned
weather_data = {}
weather_data['current_temperature_2m'] = int(current_temperature_2m)
weather_data['current_is_day'] = int(current_is_day)
weather_data['current_weather_code'] = int(current_weather_code)
weather_data['tomorrow_temperature_2m_max'] = int(daily_temperature_2m_max[1])
weather_data['tomorrow_temperature_2m_min'] = int(daily_temperature_2m_min[1])
weather_data['tomorrow_weather_code'] = int(daily_weather_code[1])
weather_data['day_after_temperature_2m_max'] = int(daily_temperature_2m_max[2])
weather_data['day_after_temperature_2m_min'] = int(daily_temperature_2m_min[2])
weather_data['day_after_weather_code'] = int(daily_weather_code[2])
return weather_data
def iconize_weather(self, weather_code: int) -> str:
# This are the WMO Weather interpretation codes (WW)
# More info in: https://open-meteo.com/en/docs at the end of the page.
# Here we only put the name of the icons, the path will be handle in main()
if weather_code >= 100:
weather_icon = "placeholder32.bmp"
return weather_icon
if weather_code in [0]:
weather_icon = "sunny-icon32.bmp"
elif weather_code in [1, 2, 3]:
weather_icon = "None"
elif weather_code in [45, 48]:
weather_icon = "None"
elif weather_code in [51, 53, 55]:
weather_icon = "None"
elif weather_code in [56, 57]:
weather_icon = "None"
elif weather_code in [61, 63, 65]:
weather_icon = "None"
elif weather_code in [66, 67]:
weather_icon = "None"
elif weather_code in [71, 73, 75]:
weather_icon = "None"
elif weather_code in [77]:
weather_icon = "None"
elif weather_code in [80, 81, 82]:
weather_icon = "None"
elif weather_code in [85, 86]:
weather_icon = "None"
elif weather_code in [95]:
weather_icon = "None"
elif weather_code in [96, 99]:
weather_icon = "None"
else:
weather_icon = "None"
# If there is no an icon for that code, search the next highest icon
if weather_icon == "None":
weather_icon = self.iconize_weather(weather_code + 1)
return weather_icon