python 3.11 & code quality improvements
This commit is contained in:
parent
4f2dacc35a
commit
93c968da53
@ -1,2 +1,3 @@
|
|||||||
from .functions import *
|
from .functions import *
|
||||||
from .inkycal_exceptions import *
|
from .inkycal_exceptions import *
|
||||||
|
from .openweathermap_wrapper import OpenWeatherMap
|
@ -6,8 +6,10 @@ Inkycal custom-functions for ease-of-use
|
|||||||
Copyright by aceinnolab
|
Copyright by aceinnolab
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
|
import traceback
|
||||||
|
|
||||||
from PIL import Image, ImageDraw, ImageFont, ImageColor
|
from PIL import Image, ImageDraw, ImageFont, ImageColor
|
||||||
from urllib.request import urlopen
|
import requests
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
|
||||||
@ -98,11 +100,13 @@ def auto_fontsize(font, max_height):
|
|||||||
Returns:
|
Returns:
|
||||||
A PIL font object with modified height.
|
A PIL font object with modified height.
|
||||||
"""
|
"""
|
||||||
|
text_bbox = font.getbbox("hg")
|
||||||
fontsize = font.getsize('hg')[1]
|
text_height = text_bbox[3] - text_bbox[1]
|
||||||
while font.getsize('hg')[1] <= (max_height * 0.80):
|
fontsize = text_height
|
||||||
|
while text_height <= (max_height * 0.80):
|
||||||
fontsize += 1
|
fontsize += 1
|
||||||
font = ImageFont.truetype(font.path, fontsize)
|
font = ImageFont.truetype(font.path, fontsize)
|
||||||
|
text_height = text_bbox[3] - text_bbox[1]
|
||||||
return font
|
return font
|
||||||
|
|
||||||
|
|
||||||
@ -154,21 +158,34 @@ def write(image, xy, box_size, text, font=None, **kwargs):
|
|||||||
if autofit or (fill_width != 1.0) or (fill_height != 0.8):
|
if autofit or (fill_width != 1.0) or (fill_height != 0.8):
|
||||||
size = 8
|
size = 8
|
||||||
font = ImageFont.truetype(font.path, size)
|
font = ImageFont.truetype(font.path, size)
|
||||||
text_width, text_height = font.getsize(text)[0], font.getsize('hg')[1]
|
text_bbox = font.getbbox(text)
|
||||||
|
text_width = text_bbox[2] - text_bbox[0]
|
||||||
|
text_bbox_height = font.getbbox("hg")
|
||||||
|
text_height = text_bbox_height[3] - text_bbox_height[1]
|
||||||
|
|
||||||
while (text_width < int(box_width * fill_width) and
|
while (text_width < int(box_width * fill_width) and
|
||||||
text_height < int(box_height * fill_height)):
|
text_height < int(box_height * fill_height)):
|
||||||
size += 1
|
size += 1
|
||||||
font = ImageFont.truetype(font.path, size)
|
font = ImageFont.truetype(font.path, size)
|
||||||
text_width, text_height = font.getsize(text)[0], font.getsize('hg')[1]
|
text_bbox = font.getbbox(text)
|
||||||
|
text_width = text_bbox[2] - text_bbox[0]
|
||||||
|
text_bbox_height = font.getbbox("hg")
|
||||||
|
text_height = text_bbox_height[3] - text_bbox_height[1]
|
||||||
|
|
||||||
text_width, text_height = font.getsize(text)[0], font.getsize('hg')[1]
|
text_bbox = font.getbbox(text)
|
||||||
|
text_width = text_bbox[2] - text_bbox[0]
|
||||||
|
text_bbox_height = font.getbbox("hg")
|
||||||
|
text_height = text_bbox_height[3] - text_bbox_height[1]
|
||||||
|
|
||||||
# Truncate text if text is too long so it can fit inside the box
|
# Truncate text if text is too long so it can fit inside the box
|
||||||
if (text_width, text_height) > (box_width, box_height):
|
if (text_width, text_height) > (box_width, box_height):
|
||||||
logs.debug(('truncating {}'.format(text)))
|
logs.debug(('truncating {}'.format(text)))
|
||||||
while (text_width, text_height) > (box_width, box_height):
|
while (text_width, text_height) > (box_width, box_height):
|
||||||
text = text[0:-1]
|
text = text[0:-1]
|
||||||
text_width, text_height = font.getsize(text)[0], font.getsize('hg')[1]
|
text_bbox = font.getbbox(text)
|
||||||
|
text_width = text_bbox[2] - text_bbox[0]
|
||||||
|
text_bbox_height = font.getbbox("hg")
|
||||||
|
text_height = text_bbox_height[3] - text_bbox_height[1]
|
||||||
logs.debug(text)
|
logs.debug(text)
|
||||||
|
|
||||||
# Align text to desired position
|
# Align text to desired position
|
||||||
@ -215,14 +232,17 @@ def text_wrap(text, font=None, max_width=None):
|
|||||||
A list containing chunked strings of the full text.
|
A list containing chunked strings of the full text.
|
||||||
"""
|
"""
|
||||||
lines = []
|
lines = []
|
||||||
if font.getsize(text)[0] < max_width:
|
|
||||||
|
text_width = font.getlength(text)
|
||||||
|
|
||||||
|
if text_width < max_width:
|
||||||
lines.append(text)
|
lines.append(text)
|
||||||
else:
|
else:
|
||||||
words = text.split(' ')
|
words = text.split(' ')
|
||||||
i = 0
|
i = 0
|
||||||
while i < len(words):
|
while i < len(words):
|
||||||
line = ''
|
line = ''
|
||||||
while i < len(words) and font.getsize(line + words[i])[0] <= max_width:
|
while i < len(words) and font.getlength(line + words[i]) <= max_width:
|
||||||
line = line + words[i] + " "
|
line = line + words[i] + " "
|
||||||
i += 1
|
i += 1
|
||||||
if not line:
|
if not line:
|
||||||
@ -249,9 +269,10 @@ def internet_available():
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
urlopen('https://google.com', timeout=5)
|
requests.get('https://google.com', timeout=5)
|
||||||
return True
|
return True
|
||||||
except:
|
except:
|
||||||
|
print(f"Network could not be reached: {traceback.print_exc()}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
43
inkycal/custom/openweathermap_wrapper.py
Normal file
43
inkycal/custom/openweathermap_wrapper.py
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import logging
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class WEATHER_OPTIONS(Enum):
|
||||||
|
CURRENT_WEATHER = "weather"
|
||||||
|
|
||||||
|
class FORECAST_INTERVAL(Enum):
|
||||||
|
THREE_HOURS = "3h"
|
||||||
|
FIVE_DAYS = "5d"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class OpenWeatherMap:
|
||||||
|
def __init__(self, api_key:str, city_id:int, units:str) -> None:
|
||||||
|
self.api_key = api_key
|
||||||
|
self.city_id = city_id
|
||||||
|
assert (units in ["metric", "imperial"] )
|
||||||
|
self.units = units
|
||||||
|
self._api_version = "2.5"
|
||||||
|
self._base_url = f"https://api.openweathermap.org/data/{self._api_version}"
|
||||||
|
|
||||||
|
|
||||||
|
def get_current_weather(self) -> dict:
|
||||||
|
current_weather_url = f"{self._base_url}/weather?id={self.city_id}&appid={self.api_key}&units={self.units}"
|
||||||
|
response = requests.get(current_weather_url)
|
||||||
|
if not response.ok:
|
||||||
|
raise AssertionError(f"Failure getting the current weather: code {response.status_code}. Reason: {response.text}")
|
||||||
|
data = json.loads(response.text)
|
||||||
|
return data
|
||||||
|
|
||||||
|
def get_weather_forecast(self) -> dict:
|
||||||
|
forecast_url = f"{self._base_url}/forecast?id={self.city_id}&appid={self.api_key}&units={self.units}"
|
||||||
|
response = requests.get(forecast_url)
|
||||||
|
if not response.ok:
|
||||||
|
raise AssertionError(f"Failure getting the current weather: code {response.status_code}. Reason: {response.text}")
|
||||||
|
data = json.loads(response.text)["list"]
|
||||||
|
return data
|
||||||
|
|
@ -98,7 +98,9 @@ class Agenda(inkycal_module):
|
|||||||
|
|
||||||
# Calculate the max number of lines that can fit on the image
|
# Calculate the max number of lines that can fit on the image
|
||||||
line_spacing = 1
|
line_spacing = 1
|
||||||
line_height = int(self.font.getsize('hg')[1]) + line_spacing
|
|
||||||
|
text_bbox_height = self.font.getbbox("hg")
|
||||||
|
line_height = text_bbox_height[3] - text_bbox_height[1] + line_spacing
|
||||||
line_width = im_width
|
line_width = im_width
|
||||||
max_lines = im_height // line_height
|
max_lines = im_height // line_height
|
||||||
logger.debug(f'max lines: {max_lines}')
|
logger.debug(f'max lines: {max_lines}')
|
||||||
@ -133,8 +135,8 @@ class Agenda(inkycal_module):
|
|||||||
# parser.show_events()
|
# parser.show_events()
|
||||||
|
|
||||||
# Set the width for date, time and event titles
|
# Set the width for date, time and event titles
|
||||||
date_width = int(max([self.font.getsize(
|
date_width = int(max([self.font.getlength(
|
||||||
dates['begin'].format(self.date_format, locale=self.language))[0]
|
dates['begin'].format(self.date_format, locale=self.language))
|
||||||
for dates in agenda_events]) * 1.2)
|
for dates in agenda_events]) * 1.2)
|
||||||
logger.debug(f'date_width: {date_width}')
|
logger.debug(f'date_width: {date_width}')
|
||||||
|
|
||||||
@ -147,8 +149,9 @@ class Agenda(inkycal_module):
|
|||||||
logger.info('Managed to parse events from urls')
|
logger.info('Managed to parse events from urls')
|
||||||
|
|
||||||
# Find out how much space the event times take
|
# Find out how much space the event times take
|
||||||
time_width = int(max([self.font.getsize(
|
|
||||||
events['begin'].format(self.time_format, locale=self.language))[0]
|
time_width = int(max([self.font.getlength(
|
||||||
|
events['begin'].format(self.time_format, locale=self.language))
|
||||||
for events in upcoming_events]) * 1.2)
|
for events in upcoming_events]) * 1.2)
|
||||||
logger.debug(f'time_width: {time_width}')
|
logger.debug(f'time_width: {time_width}')
|
||||||
|
|
||||||
|
@ -110,7 +110,8 @@ class Calendar(inkycal_module):
|
|||||||
|
|
||||||
# Allocate space for month-names, weekdays etc.
|
# Allocate space for month-names, weekdays etc.
|
||||||
month_name_height = int(im_height * 0.10)
|
month_name_height = int(im_height * 0.10)
|
||||||
weekdays_height = int(self.font.getsize('hg')[1] * 1.25)
|
text_bbox_height = self.font.getbbox("hg")
|
||||||
|
weekdays_height = int((text_bbox_height[3] - text_bbox_height[1])* 1.25)
|
||||||
logger.debug(f"month_name_height: {month_name_height}")
|
logger.debug(f"month_name_height: {month_name_height}")
|
||||||
logger.debug(f"weekdays_height: {weekdays_height}")
|
logger.debug(f"weekdays_height: {weekdays_height}")
|
||||||
|
|
||||||
@ -182,15 +183,15 @@ class Calendar(inkycal_module):
|
|||||||
]
|
]
|
||||||
logger.debug(f'weekday names: {weekday_names}')
|
logger.debug(f'weekday names: {weekday_names}')
|
||||||
|
|
||||||
for idx, weekday in enumerate(weekday_pos):
|
for index, weekday in enumerate(weekday_pos):
|
||||||
write(
|
write(
|
||||||
im_black,
|
im_black,
|
||||||
weekday,
|
weekday,
|
||||||
(icon_width, weekdays_height),
|
(icon_width, weekdays_height),
|
||||||
weekday_names[idx],
|
weekday_names[index],
|
||||||
font=self.font,
|
font=self.font,
|
||||||
autofit=True,
|
autofit=True,
|
||||||
fill_height=1.0,
|
fill_height=0.9,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create a calendar template and flatten (remove nestings)
|
# Create a calendar template and flatten (remove nestings)
|
||||||
@ -207,6 +208,10 @@ class Calendar(inkycal_module):
|
|||||||
# remove zeros from calendar since they are not required
|
# remove zeros from calendar since they are not required
|
||||||
calendar_flat = [num for num in calendar_flat if num != 0]
|
calendar_flat = [num for num in calendar_flat if num != 0]
|
||||||
|
|
||||||
|
# ensure all numbers have the same size
|
||||||
|
fontsize_numbers = int(min(icon_width, icon_height) * 0.5)
|
||||||
|
number_font = ImageFont.truetype(self.font.path, fontsize_numbers)
|
||||||
|
|
||||||
# Add the numbers on the correct positions
|
# Add the numbers on the correct positions
|
||||||
for number in calendar_flat:
|
for number in calendar_flat:
|
||||||
if number != int(now.day):
|
if number != int(now.day):
|
||||||
@ -215,9 +220,7 @@ class Calendar(inkycal_module):
|
|||||||
grid[number],
|
grid[number],
|
||||||
(icon_width, icon_height),
|
(icon_width, icon_height),
|
||||||
str(number),
|
str(number),
|
||||||
font=self.num_font,
|
font=number_font,
|
||||||
fill_height=0.5,
|
|
||||||
fill_width=0.5,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Draw a red/black circle with the current day of month in white
|
# Draw a red/black circle with the current day of month in white
|
||||||
@ -262,10 +265,10 @@ class Calendar(inkycal_module):
|
|||||||
from inkycal.modules.ical_parser import iCalendar
|
from inkycal.modules.ical_parser import iCalendar
|
||||||
|
|
||||||
# find out how many lines can fit at max in the event section
|
# find out how many lines can fit at max in the event section
|
||||||
line_spacing = 0
|
line_spacing = 2
|
||||||
max_event_lines = events_height // (
|
text_bbox_height = self.font.getbbox("hg")
|
||||||
self.font.getsize('hg')[1] + line_spacing
|
line_height = text_bbox_height[3] - text_bbox_height[1] + line_spacing
|
||||||
)
|
max_event_lines = events_height // (line_height + line_spacing)
|
||||||
|
|
||||||
# generate list of coordinates for each line
|
# generate list of coordinates for each line
|
||||||
events_offset = im_height - events_height
|
events_offset = im_height - events_height
|
||||||
@ -329,31 +332,18 @@ class Calendar(inkycal_module):
|
|||||||
# Find out how much space (width) the date format requires
|
# Find out how much space (width) the date format requires
|
||||||
lang = self.language
|
lang = self.language
|
||||||
|
|
||||||
date_width = int(
|
date_width = int(max((
|
||||||
max(
|
self.font.getlength(events['begin'].format(self.date_format, locale=lang))
|
||||||
(
|
for events in upcoming_events))* 1.1
|
||||||
self.font.getsize(
|
|
||||||
events['begin'].format(self.date_format, locale=lang)
|
|
||||||
)[0]
|
|
||||||
for events in upcoming_events
|
|
||||||
)
|
|
||||||
)
|
|
||||||
* 1.1
|
|
||||||
)
|
)
|
||||||
|
|
||||||
time_width = int(
|
time_width = int(max((
|
||||||
max(
|
self.font.getlength(events['begin'].format(self.time_format, locale=lang))
|
||||||
(
|
for events in upcoming_events))* 1.1
|
||||||
self.font.getsize(
|
|
||||||
events['begin'].format(self.time_format, locale=lang)
|
|
||||||
)[0]
|
|
||||||
for events in upcoming_events
|
|
||||||
)
|
|
||||||
)
|
|
||||||
* 1.1
|
|
||||||
)
|
)
|
||||||
|
|
||||||
line_height = self.font.getsize('hg')[1] + line_spacing
|
text_bbox_height = self.font.getbbox("hg")
|
||||||
|
line_height = text_bbox_height[3] - text_bbox_height[1] + line_spacing
|
||||||
|
|
||||||
event_width_s = im_width - date_width - time_width
|
event_width_s = im_width - date_width - time_width
|
||||||
event_width_l = im_width - date_width
|
event_width_l = im_width - date_width
|
||||||
@ -411,12 +401,13 @@ class Calendar(inkycal_module):
|
|||||||
cursor += 1
|
cursor += 1
|
||||||
else:
|
else:
|
||||||
symbol = '- '
|
symbol = '- '
|
||||||
while self.font.getsize(symbol)[0] < im_width * 0.9:
|
|
||||||
|
while self.font.getlength(symbol) < im_width * 0.9:
|
||||||
symbol += ' -'
|
symbol += ' -'
|
||||||
write(
|
write(
|
||||||
im_black,
|
im_black,
|
||||||
event_lines[0],
|
event_lines[0],
|
||||||
(im_width, self.font.getsize(symbol)[1]),
|
(im_width, line_height),
|
||||||
symbol,
|
symbol,
|
||||||
font=self.font,
|
font=self.font,
|
||||||
)
|
)
|
||||||
|
@ -91,9 +91,11 @@ class Feeds(inkycal_module):
|
|||||||
|
|
||||||
# Set some parameters for formatting feeds
|
# Set some parameters for formatting feeds
|
||||||
line_spacing = 1
|
line_spacing = 1
|
||||||
line_height = self.font.getsize('hg')[1] + line_spacing
|
|
||||||
line_width = im_width
|
line_width = im_width
|
||||||
max_lines = (im_height // (self.font.getsize('hg')[1] + line_spacing))
|
text_bbox_height = self.font.getbbox("hg")
|
||||||
|
line_height = text_bbox_height[3] - text_bbox_height[1] + line_spacing
|
||||||
|
max_lines = (im_height // (line_height + line_spacing))
|
||||||
|
|
||||||
# Calculate padding from top so the lines look centralised
|
# Calculate padding from top so the lines look centralised
|
||||||
spacing_top = int(im_height % line_height / 2)
|
spacing_top = int(im_height % line_height / 2)
|
||||||
|
@ -54,10 +54,11 @@ class Jokes(inkycal_module):
|
|||||||
raise NetworkNotReachableError
|
raise NetworkNotReachableError
|
||||||
|
|
||||||
# Set some parameters for formatting feeds
|
# Set some parameters for formatting feeds
|
||||||
line_spacing = 1
|
line_spacing = 5
|
||||||
line_height = self.font.getsize('hg')[1] + line_spacing
|
text_bbox = self.font.getbbox("hg")
|
||||||
|
line_height = text_bbox[3] - text_bbox[1] + line_spacing
|
||||||
line_width = im_width
|
line_width = im_width
|
||||||
max_lines = (im_height // (self.font.getsize('hg')[1] + line_spacing))
|
max_lines = (im_height // (line_height + line_spacing))
|
||||||
|
|
||||||
logger.debug(f"max_lines: {max_lines}")
|
logger.debug(f"max_lines: {max_lines}")
|
||||||
|
|
||||||
|
@ -96,9 +96,10 @@ class Stocks(inkycal_module):
|
|||||||
|
|
||||||
# Set some parameters for formatting feeds
|
# Set some parameters for formatting feeds
|
||||||
line_spacing = 1
|
line_spacing = 1
|
||||||
line_height = self.font.getsize('hg')[1] + line_spacing
|
text_bbox_height = self.font.getbbox("hg")
|
||||||
|
line_height = text_bbox_height[3] - text_bbox_height[1] + line_spacing
|
||||||
line_width = im_width
|
line_width = im_width
|
||||||
max_lines = (im_height // (self.font.getsize('hg')[1] + line_spacing))
|
max_lines = (im_height // line_height)
|
||||||
|
|
||||||
logger.debug(f"max_lines: {max_lines}")
|
logger.debug(f"max_lines: {max_lines}")
|
||||||
|
|
||||||
|
@ -73,10 +73,11 @@ class TextToDisplay(inkycal_module):
|
|||||||
raise NetworkNotReachableError
|
raise NetworkNotReachableError
|
||||||
|
|
||||||
# Set some parameters for formatting feeds
|
# Set some parameters for formatting feeds
|
||||||
line_spacing = 1
|
line_spacing = 4
|
||||||
line_height = self.font.getsize('hg')[1] + line_spacing
|
text_bbox_height = self.font.getbbox("hg")
|
||||||
|
line_height = text_bbox_height[3] - text_bbox_height[1] + line_spacing
|
||||||
line_width = im_width
|
line_width = im_width
|
||||||
max_lines = (im_height // (self.font.getsize('hg')[1] + line_spacing))
|
max_lines = im_height // line_height
|
||||||
|
|
||||||
# Calculate padding from top so the lines look centralised
|
# Calculate padding from top so the lines look centralised
|
||||||
spacing_top = int(im_height % line_height / 2)
|
spacing_top = int(im_height % line_height / 2)
|
||||||
|
@ -86,9 +86,10 @@ class Todoist(inkycal_module):
|
|||||||
|
|
||||||
# Set some parameters for formatting todos
|
# Set some parameters for formatting todos
|
||||||
line_spacing = 1
|
line_spacing = 1
|
||||||
line_height = self.font.getsize('hg')[1] + line_spacing
|
text_bbox_height = self.font.getbbox("hg")
|
||||||
|
line_height = text_bbox_height[3] - text_bbox_height[1] + line_spacing
|
||||||
line_width = im_width
|
line_width = im_width
|
||||||
max_lines = (im_height // (self.font.getsize('hg')[1] + line_spacing))
|
max_lines = im_height // line_height
|
||||||
|
|
||||||
# Calculate padding from top so the lines look centralised
|
# Calculate padding from top so the lines look centralised
|
||||||
spacing_top = int(im_height % line_height / 2)
|
spacing_top = int(im_height % line_height / 2)
|
||||||
|
@ -12,7 +12,7 @@ import math
|
|||||||
import decimal
|
import decimal
|
||||||
import arrow
|
import arrow
|
||||||
|
|
||||||
from pyowm.owm import OWM
|
from inkycal.custom import OpenWeatherMap
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -95,7 +95,7 @@ class Weather(inkycal_module):
|
|||||||
self.use_beaufort = config['use_beaufort']
|
self.use_beaufort = config['use_beaufort']
|
||||||
|
|
||||||
# additional configuration
|
# additional configuration
|
||||||
self.owm = OWM(self.api_key).weather_manager()
|
self.owm = OpenWeatherMap(api_key=self.api_key, city_id=self.location, units=config['units'])
|
||||||
self.timezone = get_system_tz()
|
self.timezone = get_system_tz()
|
||||||
self.locale = config['language']
|
self.locale = config['language']
|
||||||
self.weatherfont = ImageFont.truetype(
|
self.weatherfont = ImageFont.truetype(
|
||||||
@ -104,6 +104,42 @@ class Weather(inkycal_module):
|
|||||||
# give an OK message
|
# give an OK message
|
||||||
print(f"{__name__} loaded")
|
print(f"{__name__} loaded")
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def mps_to_beaufort(meters_per_second:float) -> int:
|
||||||
|
"""Map meters per second to the beaufort scale.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
meters_per_second:
|
||||||
|
float representing meters per seconds
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
an integer of the beaufort scale mapping the input
|
||||||
|
"""
|
||||||
|
thresholds = [0.3, 1.6, 3.4, 5.5, 8.0, 10.8, 13.9, 17.2, 20.7, 24.5, 28.4]
|
||||||
|
return next((i for i, threshold in enumerate(thresholds) if meters_per_second < threshold), 11)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def mps_to_mph(meters_per_second:float) -> float:
|
||||||
|
"""Map meters per second to miles per hour, rounded to one decimal place.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
meters_per_second:
|
||||||
|
float representing meters per seconds.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
float representing the input value in miles per hour.
|
||||||
|
"""
|
||||||
|
# 1 m/s is approximately equal to 2.23694 mph
|
||||||
|
miles_per_hour = meters_per_second * 2.23694
|
||||||
|
return round(miles_per_hour, 1)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def celsius_to_fahrenheit(celsius:int or float):
|
||||||
|
"""Converts the given temperate from degrees Celsius to Fahrenheit."""
|
||||||
|
fahrenheit = (celsius * 9 / 5) + 32
|
||||||
|
return fahrenheit
|
||||||
|
|
||||||
def generate_image(self):
|
def generate_image(self):
|
||||||
"""Generate image for this module"""
|
"""Generate image for this module"""
|
||||||
|
|
||||||
@ -124,7 +160,11 @@ class Weather(inkycal_module):
|
|||||||
raise NetworkNotReachableError
|
raise NetworkNotReachableError
|
||||||
|
|
||||||
def get_moon_phase():
|
def get_moon_phase():
|
||||||
"""Calculate the current (approximate) moon phase"""
|
"""Calculate the current (approximate) moon phase
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The corresponding moonphase-icon.
|
||||||
|
"""
|
||||||
|
|
||||||
dec = decimal.Decimal
|
dec = decimal.Decimal
|
||||||
diff = now - arrow.get(2001, 1, 1)
|
diff = now - arrow.get(2001, 1, 1)
|
||||||
@ -154,7 +194,7 @@ class Weather(inkycal_module):
|
|||||||
return answer
|
return answer
|
||||||
|
|
||||||
# Lookup-table for weather icons and weather codes
|
# Lookup-table for weather icons and weather codes
|
||||||
weathericons = {
|
weather_icons = {
|
||||||
'01d': '\uf00d',
|
'01d': '\uf00d',
|
||||||
'02d': '\uf002',
|
'02d': '\uf002',
|
||||||
'03d': '\uf013',
|
'03d': '\uf013',
|
||||||
@ -227,26 +267,26 @@ class Weather(inkycal_module):
|
|||||||
# Increase fontsize to fit specified height and width of text box
|
# Increase fontsize to fit specified height and width of text box
|
||||||
size = 8
|
size = 8
|
||||||
font = ImageFont.truetype(font.path, size)
|
font = ImageFont.truetype(font.path, size)
|
||||||
text_width, text_height = font.getsize(text)
|
text_width, text_height = font.getbbox(text)[2:]
|
||||||
|
|
||||||
while (text_width < int(box_width * 0.9) and
|
while (text_width < int(box_width * 0.9) and
|
||||||
text_height < int(box_height * 0.9)):
|
text_height < int(box_height * 0.9)):
|
||||||
size += 1
|
size += 1
|
||||||
font = ImageFont.truetype(font.path, size)
|
font = ImageFont.truetype(font.path, size)
|
||||||
text_width, text_height = font.getsize(text)
|
text_width, text_height = font.getbbox(text)[2:]
|
||||||
|
|
||||||
text_width, text_height = font.getsize(text)
|
text_width, text_height = font.getbbox(text)[2:]
|
||||||
|
|
||||||
# Align text to desired position
|
# Align text to desired position
|
||||||
x = int((box_width / 2) - (text_width / 2))
|
x = int((box_width / 2) - (text_width / 2))
|
||||||
y = int((box_height / 2) - (text_height / 2) - (icon_size_correction[icon] * size) / 2)
|
y = int((box_height / 2) - (text_height / 2))
|
||||||
|
|
||||||
# Draw the text in the text-box
|
# Draw the text in the text-box
|
||||||
draw = ImageDraw.Draw(image)
|
draw = ImageDraw.Draw(image)
|
||||||
space = Image.new('RGBA', (box_width, box_height))
|
space = Image.new('RGBA', (box_width, box_height))
|
||||||
ImageDraw.Draw(space).text((x, y), text, fill='black', font=font)
|
ImageDraw.Draw(space).text((x, y), text, fill='black', font=font)
|
||||||
|
|
||||||
if rotation != None:
|
if rotation:
|
||||||
space.rotate(rotation, expand=True)
|
space.rotate(rotation, expand=True)
|
||||||
|
|
||||||
# Update only region with text (add text with transparent background)
|
# Update only region with text (add text with transparent background)
|
||||||
@ -350,14 +390,9 @@ class Weather(inkycal_module):
|
|||||||
temp_fc4 = (col7, row3)
|
temp_fc4 = (col7, row3)
|
||||||
|
|
||||||
# Create current-weather and weather-forecast objects
|
# Create current-weather and weather-forecast objects
|
||||||
if self.location.isdigit():
|
logging.debug('looking up location by ID')
|
||||||
logging.debug('looking up location by ID')
|
weather = self.owm.get_current_weather()
|
||||||
weather = self.owm.weather_at_id(int(self.location)).weather
|
forecast = self.owm.get_weather_forecast()
|
||||||
forecast = self.owm.forecast_at_id(int(self.location), '3h')
|
|
||||||
else:
|
|
||||||
logging.debug('looking up location by string')
|
|
||||||
weather = self.owm.weather_at_place(self.location).weather
|
|
||||||
forecast = self.owm.forecast_at_place(self.location, '3h')
|
|
||||||
|
|
||||||
# Set decimals
|
# Set decimals
|
||||||
dec_temp = None if self.round_temperature == True else 1
|
dec_temp = None if self.round_temperature == True else 1
|
||||||
@ -369,12 +404,14 @@ class Weather(inkycal_module):
|
|||||||
elif self.units == 'imperial':
|
elif self.units == 'imperial':
|
||||||
temp_unit = 'fahrenheit'
|
temp_unit = 'fahrenheit'
|
||||||
|
|
||||||
logging.debug(f'temperature unit: {temp_unit}')
|
logging.debug(f'temperature unit: {self.units}')
|
||||||
logging.debug(f'decimals temperature: {dec_temp} | decimals wind: {dec_wind}')
|
logging.debug(f'decimals temperature: {dec_temp} | decimals wind: {dec_wind}')
|
||||||
|
|
||||||
# Get current time
|
# Get current time
|
||||||
now = arrow.utcnow()
|
now = arrow.utcnow()
|
||||||
|
|
||||||
|
fc_data = {}
|
||||||
|
|
||||||
if self.forecast_interval == 'hourly':
|
if self.forecast_interval == 'hourly':
|
||||||
|
|
||||||
logger.debug("getting hourly forecasts")
|
logger.debug("getting hourly forecasts")
|
||||||
@ -386,21 +423,22 @@ class Weather(inkycal_module):
|
|||||||
else:
|
else:
|
||||||
hour_gap = 3
|
hour_gap = 3
|
||||||
|
|
||||||
# Create timings for hourly forcasts
|
# Create timings for hourly forecasts
|
||||||
forecast_timings = [now.shift(hours=+ hour_gap + _).floor('hour')
|
forecast_timings = [now.shift(hours=+ hour_gap + _).floor('hour')
|
||||||
for _ in range(0, 12, 3)]
|
for _ in range(0, 12, 3)]
|
||||||
|
|
||||||
# Create forecast objects for given timings
|
# Create forecast objects for given timings
|
||||||
forecasts = [forecast.get_weather_at(forecast_time.datetime) for
|
forecasts = [_ for _ in forecast if arrow.get(_["dt"]) in forecast_timings]
|
||||||
forecast_time in forecast_timings]
|
|
||||||
|
|
||||||
# Add forecast-data to fc_data dictionary
|
# Add forecast-data to fc_data dictionary
|
||||||
fc_data = {}
|
fc_data = {}
|
||||||
for forecast in forecasts:
|
for forecast in forecasts:
|
||||||
temp = '{}°'.format(round(
|
if self.units == "metric":
|
||||||
forecast.temperature(unit=temp_unit)['temp'], ndigits=dec_temp))
|
temp = f"{round(weather['main']['temp'], ndigits=dec_temp)}°C"
|
||||||
|
else:
|
||||||
|
temp = f"{round(self.celsius_to_fahrenheit(weather['weather']['main']['temp']), ndigits=dec_temp)}°F"
|
||||||
|
|
||||||
icon = forecast.weather_icon_name
|
icon = forecast["weather"][0]["icon"]
|
||||||
fc_data['fc' + str(forecasts.index(forecast) + 1)] = {
|
fc_data['fc' + str(forecasts.index(forecast) + 1)] = {
|
||||||
'temp': temp,
|
'temp': temp,
|
||||||
'icon': icon,
|
'icon': icon,
|
||||||
@ -412,38 +450,35 @@ class Weather(inkycal_module):
|
|||||||
|
|
||||||
logger.debug("getting daily forecasts")
|
logger.debug("getting daily forecasts")
|
||||||
|
|
||||||
def calculate_forecast(days_from_today):
|
def calculate_forecast(days_from_today) -> dict:
|
||||||
"""Get temperature range and most frequent icon code for forecast
|
"""Get temperature range and most frequent icon code for forecast
|
||||||
days_from_today should be int from 1-4: e.g. 2 -> 2 days from today
|
days_from_today should be int from 1-4: e.g. 2 -> 2 days from today
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Create a list containing time-objects for every 3rd hour of the day
|
# Create a list containing time-objects for every 3rd hour of the day
|
||||||
time_range = list(arrow.Arrow.range('hour',
|
time_range = list(
|
||||||
now.shift(days=days_from_today).floor('day'),
|
arrow.Arrow.range('hour',
|
||||||
now.shift(days=days_from_today).ceil('day')
|
now.shift(days=days_from_today).floor('day'),now.shift(days=days_from_today).ceil('day')
|
||||||
))[::3]
|
))[::3]
|
||||||
|
|
||||||
# Get forecasts for each time-object
|
# Get forecasts for each time-object
|
||||||
forecasts = [forecast.get_weather_at(_.datetime) for _ in time_range]
|
forecasts = [_ for _ in forecast if arrow.get(_["dt"]) in time_range]
|
||||||
|
|
||||||
# Get all temperatures for this day
|
# Get all temperatures for this day
|
||||||
daily_temp = [round(_.temperature(unit=temp_unit)['temp'],
|
daily_temp = [round(_["main"]["temp"]) for _ in forecasts]
|
||||||
ndigits=dec_temp) for _ in forecasts]
|
|
||||||
# Calculate min. and max. temp for this day
|
# Calculate min. and max. temp for this day
|
||||||
temp_range = f'{max(daily_temp)}°/{min(daily_temp)}°'
|
temp_range = f'{min(daily_temp)}°/{max(daily_temp)}°'
|
||||||
|
|
||||||
# Get all weather icon codes for this day
|
# Get all weather icon codes for this day
|
||||||
daily_icons = [_.weather_icon_name for _ in forecasts]
|
daily_icons = [_["weather"][0]["icon"] for _ in forecasts]
|
||||||
# Find most common element from all weather icon codes
|
# Find most common element from all weather icon codes
|
||||||
status = max(set(daily_icons), key=daily_icons.count)
|
status = max(set(daily_icons), key=daily_icons.count)
|
||||||
|
|
||||||
weekday = now.shift(days=days_from_today).format('ddd', locale=
|
weekday = now.shift(days=days_from_today).format('ddd', locale=self.locale)
|
||||||
self.locale)
|
|
||||||
return {'temp': temp_range, 'icon': status, 'stamp': weekday}
|
return {'temp': temp_range, 'icon': status, 'stamp': weekday}
|
||||||
|
|
||||||
forecasts = [calculate_forecast(days) for days in range(1, 5)]
|
forecasts = [calculate_forecast(days) for days in range(1, 5)]
|
||||||
|
|
||||||
fc_data = {}
|
|
||||||
for forecast in forecasts:
|
for forecast in forecasts:
|
||||||
fc_data['fc' + str(forecasts.index(forecast) + 1)] = {
|
fc_data['fc' + str(forecasts.index(forecast) + 1)] = {
|
||||||
'temp': forecast['temp'],
|
'temp': forecast['temp'],
|
||||||
@ -455,13 +490,15 @@ class Weather(inkycal_module):
|
|||||||
logger.debug((key, val))
|
logger.debug((key, val))
|
||||||
|
|
||||||
# Get some current weather details
|
# Get some current weather details
|
||||||
temperature = '{}°'.format(round(
|
if dec_temp != 0:
|
||||||
weather.temperature(unit=temp_unit)['temp'], ndigits=dec_temp))
|
temperature = f"{round(weather['main']['temp'])}°"
|
||||||
|
else:
|
||||||
|
temperature = f"{round(weather['main']['temp'],ndigits=dec_temp)}°"
|
||||||
|
|
||||||
weather_icon = weather.weather_icon_name
|
weather_icon = weather["weather"][0]["icon"]
|
||||||
humidity = str(weather.humidity)
|
humidity = str(weather["main"]["humidity"])
|
||||||
sunrise_raw = arrow.get(weather.sunrise_time()).to(self.timezone)
|
sunrise_raw = arrow.get(weather["sys"]["sunrise"]).to(self.timezone)
|
||||||
sunset_raw = arrow.get(weather.sunset_time()).to(self.timezone)
|
sunset_raw = arrow.get(weather["sys"]["sunset"]).to(self.timezone)
|
||||||
|
|
||||||
logger.debug(f'weather_icon: {weather_icon}')
|
logger.debug(f'weather_icon: {weather_icon}')
|
||||||
|
|
||||||
@ -469,33 +506,29 @@ class Weather(inkycal_module):
|
|||||||
logger.debug('using 12 hour format for sunrise/sunset')
|
logger.debug('using 12 hour format for sunrise/sunset')
|
||||||
sunrise = sunrise_raw.format('h:mm a')
|
sunrise = sunrise_raw.format('h:mm a')
|
||||||
sunset = sunset_raw.format('h:mm a')
|
sunset = sunset_raw.format('h:mm a')
|
||||||
|
else:
|
||||||
elif self.hour_format == 24:
|
# 24 hours format
|
||||||
logger.debug('using 24 hour format for sunrise/sunset')
|
logger.debug('using 24 hour format for sunrise/sunset')
|
||||||
sunrise = sunrise_raw.format('H:mm')
|
sunrise = sunrise_raw.format('H:mm')
|
||||||
sunset = sunset_raw.format('H:mm')
|
sunset = sunset_raw.format('H:mm')
|
||||||
|
|
||||||
# Format the windspeed to user preference
|
# Format the wind-speed to user preference
|
||||||
if self.use_beaufort:
|
if self.use_beaufort:
|
||||||
logger.debug("using beaufort for wind")
|
logger.debug("using beaufort for wind")
|
||||||
wind = str(weather.wind(unit='beaufort')['speed'])
|
wind = str(self.mps_to_beaufort(weather["wind"]["speed"]))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
if self.units == 'metric':
|
if self.units == 'metric':
|
||||||
logging.debug('getting windspeed in metric unit')
|
logging.debug('getting wind speed in meters per second')
|
||||||
wind = str(weather.wind(unit='meters_sec')['speed']) + 'm/s'
|
wind = f"{weather['wind']['speed']} m/s"
|
||||||
|
else:
|
||||||
|
logging.debug('getting wind speed in imperial unit')
|
||||||
|
wind = f"{self.mps_to_mph(weather['wind']['speed'])} miles/h"
|
||||||
|
|
||||||
elif self.units == 'imperial':
|
moon_phase = get_moon_phase()
|
||||||
logging.debug('getting windspeed in imperial unit')
|
|
||||||
wind = str(weather.wind(unit='miles_hour')['speed']) + 'miles/h'
|
|
||||||
|
|
||||||
dec = decimal.Decimal
|
|
||||||
moonphase = get_moon_phase()
|
|
||||||
|
|
||||||
# Fill weather details in col 1 (current weather icon)
|
# Fill weather details in col 1 (current weather icon)
|
||||||
draw_icon(im_colour, weather_icon_pos, (col_width, im_height),
|
draw_icon(im_colour, weather_icon_pos, (col_width, im_height),
|
||||||
weathericons[weather_icon])
|
weather_icons[weather_icon])
|
||||||
|
|
||||||
# Fill weather details in col 2 (temp, humidity, wind)
|
# Fill weather details in col 2 (temp, humidity, wind)
|
||||||
draw_icon(im_colour, temperature_icon_pos, (icon_small, row_height),
|
draw_icon(im_colour, temperature_icon_pos, (icon_small, row_height),
|
||||||
@ -521,7 +554,7 @@ class Weather(inkycal_module):
|
|||||||
wind, font=self.font)
|
wind, font=self.font)
|
||||||
|
|
||||||
# Fill weather details in col 3 (moonphase, sunrise, sunset)
|
# Fill weather details in col 3 (moonphase, sunrise, sunset)
|
||||||
draw_icon(im_colour, moonphase_pos, (col_width, row_height), moonphase)
|
draw_icon(im_colour, moonphase_pos, (col_width, row_height), moon_phase)
|
||||||
|
|
||||||
draw_icon(im_colour, sunrise_icon_pos, (icon_small, icon_small), '\uf051')
|
draw_icon(im_colour, sunrise_icon_pos, (icon_small, icon_small), '\uf051')
|
||||||
write(im_black, sunrise_time_pos, (col_width - icon_small, row_height),
|
write(im_black, sunrise_time_pos, (col_width - icon_small, row_height),
|
||||||
@ -535,7 +568,7 @@ class Weather(inkycal_module):
|
|||||||
for pos in range(1, len(fc_data) + 1):
|
for pos in range(1, len(fc_data) + 1):
|
||||||
stamp = fc_data[f'fc{pos}']['stamp']
|
stamp = fc_data[f'fc{pos}']['stamp']
|
||||||
|
|
||||||
icon = weathericons[fc_data[f'fc{pos}']['icon']]
|
icon = weather_icons[fc_data[f'fc{pos}']['icon']]
|
||||||
temp = fc_data[f'fc{pos}']['temp']
|
temp = fc_data[f'fc{pos}']['temp']
|
||||||
|
|
||||||
write(im_black, eval(f'stamp_fc{pos}'), (col_width, row_height),
|
write(im_black, eval(f'stamp_fc{pos}'), (col_width, row_height),
|
||||||
@ -548,7 +581,7 @@ class Weather(inkycal_module):
|
|||||||
border_h = row3 + row_height
|
border_h = row3 + row_height
|
||||||
border_w = col_width - 3 # leave 3 pixels gap
|
border_w = col_width - 3 # leave 3 pixels gap
|
||||||
|
|
||||||
# Add borders around each sub-section
|
# Add borders around each subsection
|
||||||
draw_border(im_black, (col1, row1), (col_width * 3 - 3, border_h),
|
draw_border(im_black, (col1, row1), (col_width * 3 - 3, border_h),
|
||||||
shrinkage=(0, 0))
|
shrinkage=(0, 0))
|
||||||
|
|
||||||
|
@ -1 +1 @@
|
|||||||
from config import Config
|
from .config import Config
|
||||||
|
@ -57,8 +57,6 @@ class module_test(unittest.TestCase):
|
|||||||
print('OK')
|
print('OK')
|
||||||
if Config.USE_PREVIEW:
|
if Config.USE_PREVIEW:
|
||||||
preview(merge(im_black, im_colour))
|
preview(merge(im_black, im_colour))
|
||||||
im = merge(im_black, im_colour)
|
|
||||||
im.show()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
@ -112,8 +112,6 @@ class TestTextToDisplay(unittest.TestCase):
|
|||||||
print('OK')
|
print('OK')
|
||||||
if Config.USE_PREVIEW:
|
if Config.USE_PREVIEW:
|
||||||
preview(merge(im_black, im_colour))
|
preview(merge(im_black, im_colour))
|
||||||
im = merge(im_black, im_colour)
|
|
||||||
im.show()
|
|
||||||
|
|
||||||
if delete_file_after_parse:
|
if delete_file_after_parse:
|
||||||
print("cleaning up temp file")
|
print("cleaning up temp file")
|
||||||
|
@ -46,7 +46,6 @@ class module_test(unittest.TestCase):
|
|||||||
print('OK')
|
print('OK')
|
||||||
if Config.USE_PREVIEW:
|
if Config.USE_PREVIEW:
|
||||||
preview(merge(im_black, im_colour))
|
preview(merge(im_black, im_colour))
|
||||||
merge(im_black, im_colour).show()
|
|
||||||
else:
|
else:
|
||||||
print('No api key given, omitting test')
|
print('No api key given, omitting test')
|
||||||
|
|
@ -13,7 +13,7 @@ preview = Inkyimage.preview
|
|||||||
merge = Inkyimage.merge
|
merge = Inkyimage.merge
|
||||||
|
|
||||||
owm_api_key = Config.OPENWEATHERMAP_API_KEY
|
owm_api_key = Config.OPENWEATHERMAP_API_KEY
|
||||||
location = 'Stuttgart, DE'
|
location = '2825297'
|
||||||
|
|
||||||
tests = [
|
tests = [
|
||||||
{
|
{
|
||||||
@ -184,7 +184,8 @@ class module_test(unittest.TestCase):
|
|||||||
im_black, im_colour = module.generate_image()
|
im_black, im_colour = module.generate_image()
|
||||||
print('OK')
|
print('OK')
|
||||||
if Config.USE_PREVIEW:
|
if Config.USE_PREVIEW:
|
||||||
preview(merge(im_black, im_colour))
|
merged = merge(im_black, im_colour)
|
||||||
|
preview(merged)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,30 +1,28 @@
|
|||||||
arrow==1.2.3
|
arrow==1.3.0
|
||||||
certifi==2023.7.22
|
certifi==2023.7.22
|
||||||
cycler==0.11.0
|
cycler==0.12.1
|
||||||
feedparser==6.0.10
|
feedparser==6.0.10
|
||||||
fonttools==4.40.0
|
fonttools==4.44.0
|
||||||
geojson==2.3.0
|
icalendar==5.0.11
|
||||||
icalendar==5.0.7
|
kiwisolver==1.4.5
|
||||||
kiwisolver==1.4.4
|
lxml==4.9.3
|
||||||
lxml==4.9.2
|
matplotlib==3.8.1
|
||||||
matplotlib==3.7.1
|
|
||||||
multitasking==0.0.11
|
multitasking==0.0.11
|
||||||
numpy==1.25.0
|
numpy==1.26.1
|
||||||
packaging==23.1
|
packaging==23.2
|
||||||
pandas==2.0.2
|
pandas==2.1.2
|
||||||
Pillow==9.5.0
|
Pillow==10.1.0
|
||||||
pyowm==3.3.0
|
pyparsing==3.1.1
|
||||||
pyparsing==3.1.0
|
|
||||||
PySocks==1.7.1
|
PySocks==1.7.1
|
||||||
python-dateutil==2.8.2
|
python-dateutil==2.8.2
|
||||||
pytz==2023.3
|
pytz==2023.3.post1
|
||||||
recurring-ical-events==2.0.2
|
recurring-ical-events==2.1.0
|
||||||
requests==2.31.0
|
requests==2.31.0
|
||||||
sgmllib3k==1.0.0
|
sgmllib3k==1.0.0
|
||||||
six==1.16.0
|
six==1.16.0
|
||||||
todoist-api-python==2.0.2
|
todoist-api-python==2.1.3
|
||||||
typing_extensions==4.6.3
|
typing_extensions==4.8.0
|
||||||
urllib3==2.0.7
|
urllib3==2.0.7
|
||||||
yfinance==0.2.21
|
yfinance==0.2.31
|
||||||
python-dotenv==1.0.0
|
python-dotenv==1.0.0
|
||||||
setuptools==68.0.0
|
setuptools==68.2.2
|
||||||
|
Loading…
Reference in New Issue
Block a user