| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  | """
 | 
					
						
							|  |  |  | Inkycal fullscreen weather module | 
					
						
							|  |  |  | Copyright by mrbwburns | 
					
						
							|  |  |  | """
 | 
					
						
							| 
									
										
										
										
											2024-01-20 15:23:24 +01:00
										 |  |  | import io | 
					
						
							| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  | import locale | 
					
						
							|  |  |  | import logging | 
					
						
							|  |  |  | import math | 
					
						
							|  |  |  | import os | 
					
						
							|  |  |  | from datetime import datetime | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import matplotlib.dates as mdates | 
					
						
							|  |  |  | import matplotlib.pyplot as plt | 
					
						
							|  |  |  | import matplotlib.ticker as ticker | 
					
						
							|  |  |  | import numpy as np | 
					
						
							| 
									
										
										
										
											2024-01-26 14:23:21 -08:00
										 |  |  | from dateutil import tz | 
					
						
							| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  | from PIL import Image | 
					
						
							|  |  |  | from PIL import ImageDraw | 
					
						
							|  |  |  | from PIL import ImageFont | 
					
						
							|  |  |  | from PIL import ImageOps | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | from icons.weather_icons.weather_icons import get_weather_icon | 
					
						
							|  |  |  | from inkycal.custom.functions import fonts | 
					
						
							| 
									
										
										
										
											2024-01-26 14:23:21 -08:00
										 |  |  | from inkycal.custom.functions import get_system_tz | 
					
						
							| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  | from inkycal.custom.functions import internet_available | 
					
						
							|  |  |  | from inkycal.custom.functions import top_level | 
					
						
							|  |  |  | from inkycal.custom.inkycal_exceptions import NetworkNotReachableError | 
					
						
							| 
									
										
										
										
											2024-01-26 14:23:21 -08:00
										 |  |  | from inkycal.custom.openweathermap_wrapper import OpenWeatherMap | 
					
						
							| 
									
										
										
										
											2024-01-20 17:15:03 +01:00
										 |  |  | from inkycal.modules.inky_image import image_to_palette | 
					
						
							| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  | from inkycal.modules.template import inkycal_module | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | logger = logging.getLogger(__name__) | 
					
						
							| 
									
										
										
										
											2024-01-26 14:23:21 -08:00
										 |  |  | logger.setLevel(logging.INFO) | 
					
						
							| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | icons_dir = os.path.join(top_level, "icons", "ui-icons") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def outline(image: Image, size: int, color: tuple) -> Image: | 
					
						
							|  |  |  |     # Create a canvas for the outline image | 
					
						
							|  |  |  |     outlined = Image.new("RGBA", image.size, (0, 0, 0, 0)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Make a black outline | 
					
						
							|  |  |  |     for x in range(image.width): | 
					
						
							|  |  |  |         for y in range(image.height): | 
					
						
							|  |  |  |             pixel = image.getpixel((x, y)) | 
					
						
							|  |  |  |             if pixel[0] != 0 or pixel[1] != 0 or pixel[2] != 0: | 
					
						
							|  |  |  |                 outlined.putpixel((x, y), color) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Enlarge the outlined image, and paste the original image on top to create a shadow effect | 
					
						
							|  |  |  |     outlined = outlined.resize((outlined.width + size, outlined.height + size)) | 
					
						
							|  |  |  |     paste_position = ((outlined.width - image.width) // 2, (outlined.height - image.height) // 2) | 
					
						
							|  |  |  |     outlined.paste(image, paste_position, image) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Create a mask to prevent transparent pixels from overwriting | 
					
						
							|  |  |  |     mask = Image.new("L", outlined.size, 255) | 
					
						
							|  |  |  |     outlined = Image.composite(outlined, Image.new("RGBA", outlined.size, (0, 0, 0, 0)), mask) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return outlined | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-20 15:23:24 +01:00
										 |  |  | def get_image_from_plot(fig: plt) -> Image: | 
					
						
							|  |  |  |     buf = io.BytesIO() | 
					
						
							|  |  |  |     fig.savefig(buf) | 
					
						
							|  |  |  |     buf.seek(0) | 
					
						
							|  |  |  |     return Image.open(buf) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  | class Fullweather(inkycal_module): | 
					
						
							|  |  |  |     """Fullscreen Weather class
 | 
					
						
							|  |  |  |     gets weather details from openweathermap and plots a nice fullscreen forecast picture | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     name = "Fullscreen weather (openweathermap) - Get weather forecasts from openweathermap" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     requires = { | 
					
						
							|  |  |  |         "api_key": { | 
					
						
							|  |  |  |             "label": "Please enter openweathermap api-key. You can create one for free on openweathermap", | 
					
						
							|  |  |  |         }, | 
					
						
							| 
									
										
										
										
											2024-02-04 10:01:49 -08:00
										 |  |  |         "latitude": {"label": "Please enter your location' geographical latitude. E.g. 51.51 for London."}, | 
					
						
							|  |  |  |         "longitude": {"label": "Please enter your location' geographical longitude. E.g. -0.13 for London."}, | 
					
						
							| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     optional = { | 
					
						
							| 
									
										
										
										
											2024-02-04 10:01:49 -08:00
										 |  |  |         "api_version": { | 
					
						
							|  |  |  |             "label": "Please enter openweathermap api version. Default is '2.5'.", | 
					
						
							|  |  |  |             "options": ["2.5", "3.0"], | 
					
						
							|  |  |  |         }, | 
					
						
							| 
									
										
										
										
											2024-01-20 15:23:24 +01:00
										 |  |  |         "orientation": {"label": "Please select the desired orientation", "options": ["vertical", "horizontal"]}, | 
					
						
							| 
									
										
										
										
											2024-01-22 20:52:19 +01:00
										 |  |  |         "temp_unit": { | 
					
						
							| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  |             "label": "Which temperature unit should be used?", | 
					
						
							|  |  |  |             "options": ["celsius", "fahrenheit"], | 
					
						
							|  |  |  |         }, | 
					
						
							| 
									
										
										
										
											2024-01-22 20:52:19 +01:00
										 |  |  |         "wind_unit": { | 
					
						
							| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  |             "label": "Which wind speed unit should be used?", | 
					
						
							|  |  |  |             "options": ["beaufort", "knots", "miles_hour", "km_hour", "meters_sec"], | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         "wind_gusts": { | 
					
						
							|  |  |  |             "label": "Should current wind gust speed also be displayed?", | 
					
						
							|  |  |  |             "options": [True, False], | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         "keep_history": { | 
					
						
							|  |  |  |             "label": "Should the weather data be written to local json files (one per query)?", | 
					
						
							|  |  |  |             "options": [True, False], | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         "min_max_annotations": { | 
					
						
							|  |  |  |             "label": "Should the temperature plot have min/max annotation labels?", | 
					
						
							|  |  |  |             "options": [True, False], | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         "locale": { | 
					
						
							|  |  |  |             "label": "Your locale", | 
					
						
							|  |  |  |             "options": ["de_DE.UTF-8", "en_GB.UTF-8"], | 
					
						
							|  |  |  |         }, | 
					
						
							| 
									
										
										
										
											2024-01-20 15:23:24 +01:00
										 |  |  |         "font": { | 
					
						
							| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  |             "label": "Font family to use for the entire screen", | 
					
						
							| 
									
										
										
										
											2024-01-22 20:52:19 +01:00
										 |  |  |             "options": ["NotoSans", "Roboto", "Poppins"], | 
					
						
							| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  |         }, | 
					
						
							|  |  |  |         "chart_title": { | 
					
						
							|  |  |  |             "label": "Title of the temperature and precipitation plot", | 
					
						
							|  |  |  |             "options": ["Temperatur und Niederschlag", "Temperature and precipitation"], | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         "weekly_title": { | 
					
						
							|  |  |  |             "label": "Title of the weekly weather forecast", | 
					
						
							|  |  |  |             "options": ["Tageswerte", "Weekly forecast"], | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         "icon_outline": { | 
					
						
							|  |  |  |             "label": "Should the weather icons have outlines?", | 
					
						
							|  |  |  |             "options": [True, False], | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __init__(self, config): | 
					
						
							|  |  |  |         """Initialize inkycal_weather module""" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         super().__init__(config) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         config = config["config"] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-26 14:23:21 -08:00
										 |  |  |         self.tz = get_system_tz() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  |         # Check if all required parameters are present | 
					
						
							|  |  |  |         for param in self.requires: | 
					
						
							|  |  |  |             if not param in config: | 
					
						
							|  |  |  |                 raise Exception(f"config is missing {param}") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # required parameters | 
					
						
							|  |  |  |         self.api_key = config["api_key"] | 
					
						
							| 
									
										
										
										
											2024-02-04 10:01:49 -08:00
										 |  |  |         self.location_lat = float(config["latitude"]) | 
					
						
							|  |  |  |         self.location_lon = float(config["longitude"]) | 
					
						
							| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  |         self.font_size = int(config["fontsize"]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # optional parameters | 
					
						
							| 
									
										
										
										
											2024-02-04 10:01:49 -08:00
										 |  |  |         if "api_version" in config and config["api_version"] == "3.0": | 
					
						
							|  |  |  |             self.owm_api_version = "3.0" | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             self.owm_api_version = "2.5" | 
					
						
							| 
									
										
										
										
											2024-01-20 15:23:24 +01:00
										 |  |  |         if "orientation" in config: | 
					
						
							|  |  |  |             self.orientation = config["orientation"] | 
					
						
							|  |  |  |             assert self.orientation in ["horizontal", "vertical"] | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             self.orientation = "horizontal" | 
					
						
							| 
									
										
										
										
											2024-01-22 20:52:19 +01:00
										 |  |  |         if "wind_unit" in config: | 
					
						
							|  |  |  |             self.wind_unit = config["wind_unit"] | 
					
						
							| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  |         else: | 
					
						
							| 
									
										
										
										
											2024-01-22 20:52:19 +01:00
										 |  |  |             self.wind_unit = "meters_sec" | 
					
						
							|  |  |  |         if self.wind_unit == "beaufort": | 
					
						
							| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  |             self.windDispUnit = "bft" | 
					
						
							| 
									
										
										
										
											2024-01-22 20:52:19 +01:00
										 |  |  |         elif self.wind_unit == "knots": | 
					
						
							| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  |             self.windDispUnit = "kn" | 
					
						
							| 
									
										
										
										
											2024-01-22 20:52:19 +01:00
										 |  |  |         elif self.wind_unit == "km_hour": | 
					
						
							| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  |             self.windDispUnit = "km/h" | 
					
						
							| 
									
										
										
										
											2024-01-22 20:52:19 +01:00
										 |  |  |         elif self.wind_unit == "miles_hour": | 
					
						
							| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  |             self.windDispUnit = "mph" | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             self.windDispUnit = "m/s" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if "wind_gusts" in config: | 
					
						
							|  |  |  |             self.wind_gusts = bool(config["wind_gusts"]) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             self.wind_gusts = True | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-22 20:52:19 +01:00
										 |  |  |         if "temp_unit" in config: | 
					
						
							|  |  |  |             self.temp_unit = config["temp_unit"] | 
					
						
							| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  |         else: | 
					
						
							| 
									
										
										
										
											2024-01-22 20:52:19 +01:00
										 |  |  |             self.temp_unit = "celsius" | 
					
						
							|  |  |  |         if self.temp_unit == "fahrenheit": | 
					
						
							| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  |             self.tempDispUnit = "F" | 
					
						
							| 
									
										
										
										
											2024-01-22 20:52:19 +01:00
										 |  |  |         elif self.temp_unit == "celsius": | 
					
						
							| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  |             self.tempDispUnit = "°" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if "weekly_title" in config: | 
					
						
							|  |  |  |             self.weekly_title = config["weekly_title"] | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             self.weekly_title = "Weekly forecast" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if "chart_title" in config: | 
					
						
							|  |  |  |             self.chart_title = config["chart_title"] | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             self.chart_title = "Temperature and precipitation" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if "keep_history" in config: | 
					
						
							|  |  |  |             self.keep_history = config["keep_history"] | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             self.keep_history = False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if "min_max_annotations" in config: | 
					
						
							|  |  |  |             self.min_max_annotations = bool(config["min_max_annotations"]) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             self.min_max_annotations = False | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if "locale" in config: | 
					
						
							|  |  |  |             self.locale = config["locale"] | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             self.locale = "en_GB.UTF-8" | 
					
						
							|  |  |  |         locale.setlocale(locale.LC_TIME, self.locale) | 
					
						
							|  |  |  |         self.language = self.locale.split("_")[0] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if "icon_outline" in config: | 
					
						
							|  |  |  |             self.icon_outline = config["icon_outline"] | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             self.icon_outline = True | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-20 15:23:24 +01:00
										 |  |  |         if "font" in config: | 
					
						
							|  |  |  |             self.font = config["font"] | 
					
						
							| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  |         else: | 
					
						
							| 
									
										
										
										
											2024-01-20 15:23:24 +01:00
										 |  |  |             self.font = "Roboto" | 
					
						
							| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # some calculations for scalability | 
					
						
							|  |  |  |         # TODO: make this work for all sizes | 
					
						
							| 
									
										
										
										
											2024-01-20 15:23:24 +01:00
										 |  |  |         if self.orientation == "horizontal": | 
					
						
							|  |  |  |             self.width, self.height = self.height, self.width | 
					
						
							| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  |         self.screen_width_in = 163 / 25.4  # 163 mm for 7in5 | 
					
						
							|  |  |  |         self.screen_height_in = 98 / 25.4  # 98 mm for 7in5 | 
					
						
							|  |  |  |         self.dpi = math.sqrt( | 
					
						
							|  |  |  |             (float(self.width) ** 2 + float(self.height) ** 2) | 
					
						
							|  |  |  |             / (self.screen_width_in**2 + self.screen_height_in**2) | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         self.left_section_width = int(self.width / 4) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # give an OK message | 
					
						
							|  |  |  |         print(f"{__name__} loaded") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def createBaseImage(self): | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         Creates background and adds current date | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         # Create white image | 
					
						
							|  |  |  |         self.image = Image.new("RGB", (self.width, self.height), (255, 255, 255)) | 
					
						
							|  |  |  |         image_draw = ImageDraw.Draw(self.image) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Create black rectangle for the current weather section | 
					
						
							|  |  |  |         rect_width = int(self.width / 4) | 
					
						
							|  |  |  |         image_draw.rectangle((0, 0, rect_width, self.height), fill=0) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Add text with current date | 
					
						
							| 
									
										
										
										
											2024-01-26 14:23:21 -08:00
										 |  |  |         tz_info = tz.gettz(self.tz) | 
					
						
							|  |  |  |         dateString = datetime.now(tz=tz_info).strftime("%d. %B") | 
					
						
							| 
									
										
										
										
											2024-01-20 15:23:24 +01:00
										 |  |  |         dateFont = self.get_font(style="Bold", size=self.font_size) | 
					
						
							| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  |         # Get the width of the text | 
					
						
							|  |  |  |         dateStringbbox = dateFont.getbbox(dateString) | 
					
						
							|  |  |  |         dateW = dateStringbbox[2] - dateStringbbox[0] | 
					
						
							|  |  |  |         # Draw the current date centered | 
					
						
							|  |  |  |         image_draw.text(((rect_width - dateW) / 2, 5), dateString, font=dateFont, fill=(255, 255, 255)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def addUserSection(self): | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         Adds user-defined section to the given image | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         ## Create drawing object for image | 
					
						
							|  |  |  |         image_draw = ImageDraw.Draw(self.image) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if False:  # self.mqtt_sub == True: | 
					
						
							|  |  |  |             # Add icon for Home | 
					
						
							|  |  |  |             homeTempIcon = Image.open(os.path.join(icons_dir, "home_temp.png")) | 
					
						
							|  |  |  |             homeTempIcon = ImageOps.invert(homeTempIcon) | 
					
						
							|  |  |  |             homeTempIcon = homeTempIcon.resize((40, 40)) | 
					
						
							|  |  |  |             homeTemp_y = int(self.height * 0.8125) | 
					
						
							|  |  |  |             self.image.paste(homeTempIcon, (15, homeTemp_y)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # Home temperature | 
					
						
							|  |  |  |             # my_home = mqtt_temperature(host=mqtt_host, port=mqtt_port, user=mqtt_user, password=mqtt_pass, topic=mqtt_topic) | 
					
						
							|  |  |  |             # homeTemp = None | 
					
						
							|  |  |  |             # while homeTemp == None: | 
					
						
							|  |  |  |             #    homeTemp = my_home.get_temperature() | 
					
						
							|  |  |  |             # homeTempString = f"{homeTemp:.1f} {tempDispUnit}" | 
					
						
							|  |  |  |             # homeTempFont = font.font(font_family, "Bold", 28) | 
					
						
							|  |  |  |             # image_draw.text((65, homeTemp_y), homeTempString, font=homeTempFont, fill=(255, 255, 255)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # Add icon for rH | 
					
						
							|  |  |  |             humidityIcon = Image.open(os.path.join(icons_dir, "humidity.bmp")) | 
					
						
							|  |  |  |             humidityIcon = humidityIcon.resize((40, 40)) | 
					
						
							|  |  |  |             humidity_y = int(self.height * 0.90625) | 
					
						
							|  |  |  |             self.image.paste(humidityIcon, (15, humidity_y)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # rel. humidity | 
					
						
							|  |  |  |             # rH = None | 
					
						
							|  |  |  |             # while rH == None: | 
					
						
							|  |  |  |             #    rH = my_home.get_rH() | 
					
						
							|  |  |  |             # humidityString = f"{rH:.0f} %" | 
					
						
							|  |  |  |             # humidityFont = font.font(font_family, "Bold", 28) | 
					
						
							|  |  |  |             # image_draw.text((65, humidity_y), humidityString, font=humidityFont, fill=(255, 255, 255)) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             # Add icon for Humidity | 
					
						
							|  |  |  |             humidityIcon = Image.open(os.path.join(icons_dir, "humidity.bmp")) | 
					
						
							|  |  |  |             humidityIcon = humidityIcon.resize((40, 40)) | 
					
						
							|  |  |  |             humidity_y = int(self.height * 0.8125) | 
					
						
							|  |  |  |             self.image.paste(humidityIcon, (15, humidity_y)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # Humidity | 
					
						
							| 
									
										
										
										
											2024-01-22 20:52:19 +01:00
										 |  |  |             humidityString = f"{self.current_weather['humidity']} %" | 
					
						
							| 
									
										
										
										
											2024-01-20 15:23:24 +01:00
										 |  |  |             humidityFont = self.get_font("Bold", self.font_size + 8) | 
					
						
							| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  |             image_draw.text((65, humidity_y), humidityString, font=humidityFont, fill=(255, 255, 255)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # Add icon for uv | 
					
						
							|  |  |  |             uvIcon = Image.open(os.path.join(icons_dir, "uv.bmp")) | 
					
						
							|  |  |  |             uvIcon = uvIcon.resize((40, 40)) | 
					
						
							|  |  |  |             ux_y = int(self.height * 0.90625) | 
					
						
							|  |  |  |             self.image.paste(uvIcon, (15, ux_y)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # uvindex | 
					
						
							| 
									
										
										
										
											2024-02-04 10:01:49 -08:00
										 |  |  |             uvi = self.current_weather["uvi"] if self.current_weather["uvi"] else 0.0 | 
					
						
							|  |  |  |             uvString = f"{uvi:.1f}" | 
					
						
							| 
									
										
										
										
											2024-01-20 15:23:24 +01:00
										 |  |  |             uvFont = self.get_font("Bold", self.font_size + 8) | 
					
						
							| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  |             image_draw.text((65, ux_y), uvString, font=uvFont, fill=(255, 255, 255)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def addCurrentWeather(self): | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         Adds current weather situation to the left section of the image | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         ## Create drawing object for image | 
					
						
							|  |  |  |         image_draw = ImageDraw.Draw(self.image) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         ## Add detailed weather status text to the image | 
					
						
							| 
									
										
										
										
											2024-01-22 20:52:19 +01:00
										 |  |  |         sumString = self.current_weather["detailed_status"].replace(" ", "\n ") | 
					
						
							| 
									
										
										
										
											2024-01-20 15:23:24 +01:00
										 |  |  |         sumFont = self.get_font("Regular", self.font_size + 8) | 
					
						
							| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  |         maxW = 0 | 
					
						
							|  |  |  |         totalH = 0 | 
					
						
							|  |  |  |         for word in sumString.split("\n "): | 
					
						
							|  |  |  |             sumStringbbox = sumFont.getbbox(word) | 
					
						
							|  |  |  |             sumW = sumStringbbox[2] - sumStringbbox[0] | 
					
						
							|  |  |  |             sumH = sumStringbbox[3] - sumStringbbox[1] | 
					
						
							|  |  |  |             maxW = max(maxW, sumW) | 
					
						
							|  |  |  |             totalH += sumH | 
					
						
							|  |  |  |         sumtext_x = int((self.left_section_width - maxW) / 2) | 
					
						
							|  |  |  |         sumtext_y = int(self.height * 0.19) - totalH | 
					
						
							|  |  |  |         image_draw.multiline_text((sumtext_x, sumtext_y), sumString, font=sumFont, fill=(255, 255, 255), align="center") | 
					
						
							|  |  |  |         logger.debug(f"Added current weather detailed status text: {sumString} at x:{sumtext_x}/y:{sumtext_y}.") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         ## Add current weather icon to the image | 
					
						
							| 
									
										
										
										
											2024-01-22 20:52:19 +01:00
										 |  |  |         icon = get_weather_icon(icon_name=self.current_weather["weather_icon_name"], size=150) | 
					
						
							| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  |         # Create a mask from the alpha channel of the weather icon | 
					
						
							|  |  |  |         if len(icon.split()) == 4: | 
					
						
							|  |  |  |             mask = icon.split()[-1] | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             mask = None | 
					
						
							|  |  |  |         # Paste the foreground of the icon onto the background with the help of the mask | 
					
						
							|  |  |  |         icon_x = int((self.left_section_width - icon.width) / 2) | 
					
						
							|  |  |  |         icon_y = int(self.height * 0.2) | 
					
						
							|  |  |  |         self.image.paste(icon, (icon_x, icon_y), mask) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         ## Add current temperature to the image | 
					
						
							| 
									
										
										
										
											2024-01-22 20:52:19 +01:00
										 |  |  |         tempString = f"{self.current_weather['temp_feels_like']:.0f}{self.tempDispUnit}" | 
					
						
							| 
									
										
										
										
											2024-01-20 15:23:24 +01:00
										 |  |  |         tempFont = self.get_font("Bold", 68) | 
					
						
							| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  |         # Get the width of the text | 
					
						
							|  |  |  |         tempStringbbox = tempFont.getbbox(tempString) | 
					
						
							|  |  |  |         tempW = tempStringbbox[2] - tempStringbbox[0] | 
					
						
							|  |  |  |         temp_x = int((self.left_section_width - tempW) / 2) | 
					
						
							|  |  |  |         temp_y = int(self.height * 0.4375) | 
					
						
							|  |  |  |         # Draw the current temp centered | 
					
						
							|  |  |  |         image_draw.text((temp_x, temp_y), tempString, font=tempFont, fill=(255, 255, 255)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Add icon for rain forecast | 
					
						
							|  |  |  |         rainIcon = Image.open(os.path.join(icons_dir, "rain-chance.bmp")) | 
					
						
							|  |  |  |         rainIcon = rainIcon.resize((40, 40)) | 
					
						
							|  |  |  |         rain_y = int(self.height * 0.625) | 
					
						
							|  |  |  |         self.image.paste(rainIcon, (15, rain_y)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Amount of precipitation within next 3h | 
					
						
							|  |  |  |         rain = self.hourly_forecasts[0]["precip_3h_mm"] | 
					
						
							|  |  |  |         precipString = f"{rain:.1g} mm" if rain > 0.0 else "0 mm" | 
					
						
							| 
									
										
										
										
											2024-01-20 15:23:24 +01:00
										 |  |  |         precipFont = self.get_font("Bold", self.font_size + 8) | 
					
						
							| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  |         image_draw.text((65, rain_y), precipString, font=precipFont, fill=(255, 255, 255)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Add icon for wind speed | 
					
						
							|  |  |  |         windIcon = Image.open(os.path.join(icons_dir, "wind.bmp")) | 
					
						
							|  |  |  |         windIcon = windIcon.resize((40, 40)) | 
					
						
							|  |  |  |         wind_y = int(self.height * 0.719) | 
					
						
							|  |  |  |         self.image.paste(windIcon, (15, wind_y)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Max. wind speed within next 3h | 
					
						
							|  |  |  |         wind_gust = f"{self.hourly_forecasts[0]['wind_gust']:.0f}" | 
					
						
							|  |  |  |         wind = f"{self.hourly_forecasts[0]['wind']:.0f}" | 
					
						
							|  |  |  |         if self.wind_gusts: | 
					
						
							|  |  |  |             if wind == wind_gust: | 
					
						
							|  |  |  |                 windString = f"{wind} {self.windDispUnit}" | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 windString = f"{wind} - {wind_gust} {self.windDispUnit}" | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             windString = f"{wind} {self.windDispUnit}" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-20 15:23:24 +01:00
										 |  |  |         windFont = self.get_font("Bold", self.font_size + 8) | 
					
						
							| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  |         image_draw.text((65, wind_y), windString, font=windFont, fill=(255, 255, 255)) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-15 20:45:42 +01:00
										 |  |  |     def addHourlyForecast(self): | 
					
						
							|  |  |  |         """
 | 
					
						
							| 
									
										
										
										
											2024-01-15 21:04:51 +01:00
										 |  |  |         Adds a plot for temperature and amount of rain for the upcoming hours to the upper right section | 
					
						
							| 
									
										
										
										
											2024-01-15 20:45:42 +01:00
										 |  |  |         """
 | 
					
						
							|  |  |  |         ## Create drawing object for image | 
					
						
							|  |  |  |         image_draw = ImageDraw.Draw(self.image) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         ## Draw hourly chart title | 
					
						
							|  |  |  |         title_x = self.left_section_width + 20  # X-coordinate of the title | 
					
						
							|  |  |  |         title_y = 5 | 
					
						
							| 
									
										
										
										
											2024-01-20 15:23:24 +01:00
										 |  |  |         chartTitleFont = self.get_font("ExtraBold", self.font_size) | 
					
						
							| 
									
										
										
										
											2024-01-15 20:45:42 +01:00
										 |  |  |         image_draw.text((title_x, title_y), self.chart_title, font=chartTitleFont, fill=0) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         ## Plot the data | 
					
						
							|  |  |  |         # Define the chart parameters | 
					
						
							|  |  |  |         w, h = int(0.75 * self.width), int(0.45 * self.height)  # Width and height of the graph | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Length of our time axis | 
					
						
							|  |  |  |         num_ticks_x = 22  # ticks*3 hours | 
					
						
							|  |  |  |         timestamps = [item["datetime"] for item in self.hourly_forecasts][:num_ticks_x] | 
					
						
							|  |  |  |         temperatures = np.array([item["temp"] for item in self.hourly_forecasts])[:num_ticks_x] | 
					
						
							|  |  |  |         precipitation = np.array([item["precip_3h_mm"] for item in self.hourly_forecasts])[:num_ticks_x] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Create the figure | 
					
						
							|  |  |  |         fig, ax1 = plt.subplots(figsize=(w / self.dpi, h / self.dpi), dpi=self.dpi) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Plot Temperature as line plot in red | 
					
						
							|  |  |  |         ax1.plot(timestamps, temperatures, marker=".", linestyle="-", color="r") | 
					
						
							| 
									
										
										
										
											2024-01-22 20:52:19 +01:00
										 |  |  |         temp_base = 3 if self.temp_unit == "celsius" else 5 | 
					
						
							| 
									
										
										
										
											2024-01-15 20:45:42 +01:00
										 |  |  |         fig.gca().yaxis.set_major_locator(ticker.MultipleLocator(base=temp_base)) | 
					
						
							|  |  |  |         ax1.tick_params(axis="y", colors="red") | 
					
						
							|  |  |  |         ax1.set_yticks(ax1.get_yticks()) | 
					
						
							|  |  |  |         ax1.set_yticklabels([f"{int(value)}{self.tempDispUnit}" for value in ax1.get_yticks()]) | 
					
						
							|  |  |  |         ax1.grid(visible=True, axis="both")  # Adding grid | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if self.min_max_annotations == True: | 
					
						
							|  |  |  |             # Calculate min_temp and max_temp values based on the minimum and maximum temperatures in the hourly data | 
					
						
							|  |  |  |             min_temp = np.min(temperatures) | 
					
						
							|  |  |  |             max_temp = np.max(temperatures) | 
					
						
							|  |  |  |             # Find positions of min and max values | 
					
						
							|  |  |  |             min_temp_index = np.argmin(temperatures) | 
					
						
							|  |  |  |             max_temp_index = np.argmax(temperatures) | 
					
						
							|  |  |  |             ax1.text( | 
					
						
							|  |  |  |                 timestamps[min_temp_index], | 
					
						
							|  |  |  |                 min_temp, | 
					
						
							|  |  |  |                 f"Min: {min_temp:.1f}{self.tempDispUnit}", | 
					
						
							|  |  |  |                 ha="left", | 
					
						
							|  |  |  |                 va="top", | 
					
						
							|  |  |  |                 color="blue", | 
					
						
							|  |  |  |                 fontsize=12, | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |             ax1.text( | 
					
						
							|  |  |  |                 timestamps[max_temp_index], | 
					
						
							|  |  |  |                 max_temp, | 
					
						
							|  |  |  |                 f"Max: {max_temp:.1f}{self.tempDispUnit}", | 
					
						
							|  |  |  |                 ha="left", | 
					
						
							|  |  |  |                 va="bottom", | 
					
						
							|  |  |  |                 color="red", | 
					
						
							|  |  |  |                 fontsize=12, | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Create the second part of the plot as a bar chart for amount of precipitation | 
					
						
							|  |  |  |         ax2 = ax1.twinx() | 
					
						
							|  |  |  |         width = np.min(np.diff(mdates.date2num(timestamps))) | 
					
						
							|  |  |  |         ax2.bar(timestamps, precipitation, color="blue", width=width, alpha=0.2) | 
					
						
							|  |  |  |         ax2.tick_params(axis="y", colors="blue") | 
					
						
							|  |  |  |         ax2.set_ylim([0, 10]) | 
					
						
							|  |  |  |         ax2.set_yticks(ax2.get_yticks()) | 
					
						
							|  |  |  |         ax2.set_yticklabels([f"{value:.0f}" for value in ax2.get_yticks()]) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-26 14:23:21 -08:00
										 |  |  |         fig.gca().xaxis.set_major_locator(mdates.DayLocator(interval=1, tz=self.tz)) | 
					
						
							|  |  |  |         fig.gca().xaxis.set_major_formatter(mdates.DateFormatter(fmt="%a", tz=self.tz)) | 
					
						
							|  |  |  |         fig.gca().xaxis.set_minor_locator(mdates.HourLocator(interval=3, tz=self.tz)) | 
					
						
							| 
									
										
										
										
											2024-01-15 20:45:42 +01:00
										 |  |  |         fig.tight_layout()  # Adjust layout to prevent clipping of labels | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Get image from plot and add it to the image | 
					
						
							|  |  |  |         hourly_forecast_plot = get_image_from_plot(plt) | 
					
						
							|  |  |  |         plot_x = self.left_section_width + 5 | 
					
						
							|  |  |  |         plot_y = title_y + 30 | 
					
						
							|  |  |  |         self.image.paste(hourly_forecast_plot, (plot_x, plot_y)) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-15 21:04:51 +01:00
										 |  |  |     def addDailyForecast(self): | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         Adds daily weather forecasts to the lower right section | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         ## Create drawing object for image | 
					
						
							|  |  |  |         image_draw = ImageDraw.Draw(self.image) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         ## Draw daily chart title | 
					
						
							|  |  |  |         title_y = int(self.height / 2)  # Y-coordinate of the title | 
					
						
							| 
									
										
										
										
											2024-01-20 15:23:24 +01:00
										 |  |  |         chartTitleFont = self.get_font("Bold", self.font_size) | 
					
						
							| 
									
										
										
										
											2024-01-15 21:04:51 +01:00
										 |  |  |         image_draw.text((self.left_section_width + 20, title_y), self.weekly_title, font=chartTitleFont, fill=0) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Define the parameters | 
					
						
							|  |  |  |         number_of_forecast_days = 5  # including today | 
					
						
							|  |  |  |         # Spread evenly, starting from title width | 
					
						
							|  |  |  |         rectangle_width = int((self.width - (self.left_section_width + 40)) / number_of_forecast_days) | 
					
						
							|  |  |  |         # Maximum height for each rectangle (avoid overlapping with title) | 
					
						
							|  |  |  |         rectangle_height = int(self.height / 2 - 20) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Rain icon is static | 
					
						
							|  |  |  |         rainIcon = Image.open(os.path.join(icons_dir, "rain-chance.bmp")) | 
					
						
							|  |  |  |         rainIcon.convert("L") | 
					
						
							|  |  |  |         rainIcon = ImageOps.invert(rainIcon) | 
					
						
							|  |  |  |         weeklyRainIcon = rainIcon.resize((20, 20)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Loop through the upcoming days' data and create rectangles | 
					
						
							|  |  |  |         for i in range(number_of_forecast_days): | 
					
						
							|  |  |  |             x_rect = self.left_section_width + 20 + i * rectangle_width  # Start from the title width | 
					
						
							|  |  |  |             y_rect = int(self.height / 2 + 30) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-22 20:52:19 +01:00
										 |  |  |             day_data = self.my_owm.get_forecast_for_day(days_from_today=i) | 
					
						
							| 
									
										
										
										
											2024-01-15 21:04:51 +01:00
										 |  |  |             rect = Image.new("RGBA", (int(rectangle_width), int(rectangle_height)), (255, 255, 255)) | 
					
						
							|  |  |  |             rect_draw = ImageDraw.Draw(rect) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # Date string: Day of week on line 1, date on line 2 | 
					
						
							| 
									
										
										
										
											2024-01-20 15:23:24 +01:00
										 |  |  |             short_day_font = self.get_font("ExtraBold", self.font_size + 4) | 
					
						
							|  |  |  |             short_month_day_font = self.get_font("Bold", self.font_size - 4) | 
					
						
							| 
									
										
										
										
											2024-01-26 14:23:21 -08:00
										 |  |  |             short_day_name = day_data["datetime"].strftime("%a") | 
					
						
							|  |  |  |             short_month_day = day_data["datetime"].strftime("%b %d") | 
					
						
							| 
									
										
										
										
											2024-01-15 21:04:51 +01:00
										 |  |  |             short_day_name_text = rect_draw.textbbox((0, 0), short_day_name, font=short_day_font) | 
					
						
							|  |  |  |             short_month_day_text = rect_draw.textbbox((0, 0), short_month_day, font=short_month_day_font) | 
					
						
							|  |  |  |             day_name_x = (rectangle_width - short_day_name_text[2] + short_day_name_text[0]) / 2 | 
					
						
							|  |  |  |             short_month_day_x = (rectangle_width - short_month_day_text[2] + short_month_day_text[0]) / 2 | 
					
						
							|  |  |  |             rect_draw.text((day_name_x, 0), short_day_name, fill=0, font=short_day_font) | 
					
						
							|  |  |  |             rect_draw.text( | 
					
						
							|  |  |  |                 (short_month_day_x, 30), | 
					
						
							|  |  |  |                 short_month_day, | 
					
						
							|  |  |  |                 fill=0, | 
					
						
							|  |  |  |                 font=short_month_day_font, | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             ## Min and max temperature split into diagonal placement | 
					
						
							|  |  |  |             min_temp = day_data["temp_min"] | 
					
						
							|  |  |  |             max_temp = day_data["temp_max"] | 
					
						
							|  |  |  |             temp_text_min = f"{min_temp:.0f}{self.tempDispUnit}" | 
					
						
							|  |  |  |             temp_text_max = f"{max_temp:.0f}{self.tempDispUnit}" | 
					
						
							| 
									
										
										
										
											2024-01-20 15:23:24 +01:00
										 |  |  |             rect_temp_font = self.get_font("ExtraBold", self.font_size + 4) | 
					
						
							| 
									
										
										
										
											2024-01-15 21:04:51 +01:00
										 |  |  |             temp_x_offset = 20 | 
					
						
							|  |  |  |             # this is upper left: max temperature | 
					
						
							|  |  |  |             temp_text_max_x = temp_x_offset | 
					
						
							|  |  |  |             temp_text_max_y = int(rectangle_height * 0.25) | 
					
						
							|  |  |  |             # this is lower right: min temperature | 
					
						
							|  |  |  |             temp_text_min_bbox = rect_draw.textbbox((0, 0), temp_text_min, font=rect_temp_font) | 
					
						
							|  |  |  |             temp_text_min_x = ( | 
					
						
							|  |  |  |                 int((rectangle_width - temp_text_min_bbox[2] + temp_text_min_bbox[0]) / 2) + temp_x_offset + 7 | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |             temp_text_min_y = int(rectangle_height * 0.33) | 
					
						
							|  |  |  |             rect_draw.text((temp_text_min_x, temp_text_min_y), temp_text_min, fill=0, font=rect_temp_font) | 
					
						
							|  |  |  |             rect_draw.text( | 
					
						
							|  |  |  |                 (temp_text_max_x, temp_text_max_y), | 
					
						
							|  |  |  |                 temp_text_max, | 
					
						
							|  |  |  |                 fill=0, | 
					
						
							|  |  |  |                 font=rect_temp_font, | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # Weather icon for the day | 
					
						
							|  |  |  |             icon_code = day_data["icon"] | 
					
						
							|  |  |  |             icon = get_weather_icon(icon_name=icon_code, size=90) | 
					
						
							|  |  |  |             if self.icon_outline: | 
					
						
							|  |  |  |                 icon = outline(image=icon, size=8, color=(0, 0, 0, 255)) | 
					
						
							|  |  |  |             icon_x = int((rectangle_width - icon.width) / 2) | 
					
						
							|  |  |  |             icon_y = int(rectangle_height * 0.4) | 
					
						
							|  |  |  |             # Create a mask from the alpha channel of the weather icon | 
					
						
							|  |  |  |             if len(icon.split()) == 4: | 
					
						
							|  |  |  |                 mask = icon.split()[-1] | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 mask = None | 
					
						
							|  |  |  |             # Paste the foreground of the icon onto the background with the help of the mask | 
					
						
							|  |  |  |             rect.paste(icon, (int(icon_x), icon_y), mask) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             ## Precipitation icon and text | 
					
						
							|  |  |  |             rain = day_data["precip_mm"] | 
					
						
							|  |  |  |             if rain: | 
					
						
							|  |  |  |                 rain_text = f"{rain:.0f} mm" | 
					
						
							| 
									
										
										
										
											2024-01-20 15:23:24 +01:00
										 |  |  |                 rain_font = self.get_font("ExtraBold", self.font_size) | 
					
						
							| 
									
										
										
										
											2024-01-15 21:04:51 +01:00
										 |  |  |                 # Icon | 
					
						
							|  |  |  |                 rain_icon_x = int((rectangle_width - icon.width) / 2) | 
					
						
							|  |  |  |                 rain_icon_y = int(rectangle_height * 0.82) | 
					
						
							|  |  |  |                 rect.paste(weeklyRainIcon, (rain_icon_x, rain_icon_y)) | 
					
						
							|  |  |  |                 # Text | 
					
						
							|  |  |  |                 rain_text_y = int(rectangle_height * 0.8) | 
					
						
							|  |  |  |                 rect_draw.text( | 
					
						
							|  |  |  |                     (rain_icon_x + weeklyRainIcon.width + 10, rain_text_y), | 
					
						
							|  |  |  |                     rain_text, | 
					
						
							|  |  |  |                     fill=0, | 
					
						
							|  |  |  |                     font=rain_font, | 
					
						
							|  |  |  |                     align="right", | 
					
						
							|  |  |  |                 ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             self.image.paste(rect, (int(x_rect), int(y_rect))) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  |     def generate_image(self): | 
					
						
							|  |  |  |         """Generate image for this module""" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Define new image size with respect to padding | 
					
						
							|  |  |  |         im_width = int(self.width - (2 * self.padding_left)) | 
					
						
							|  |  |  |         im_height = int(self.height - (2 * self.padding_top)) | 
					
						
							|  |  |  |         im_size = im_width, im_height | 
					
						
							|  |  |  |         logger.info(f"Image size: {im_size}") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Check if internet is available | 
					
						
							|  |  |  |         if internet_available(): | 
					
						
							|  |  |  |             logger.info("Connection test passed") | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             raise NetworkNotReachableError | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Get the weather | 
					
						
							| 
									
										
										
										
											2024-01-26 14:23:21 -08:00
										 |  |  |         self.my_owm = OpenWeatherMap( | 
					
						
							| 
									
										
										
										
											2024-01-22 20:52:19 +01:00
										 |  |  |             api_key=self.api_key, | 
					
						
							| 
									
										
										
										
											2024-02-04 10:01:49 -08:00
										 |  |  |             api_version=self.owm_api_version, | 
					
						
							|  |  |  |             lat=self.location_lat, | 
					
						
							|  |  |  |             lon=self.location_lon, | 
					
						
							| 
									
										
										
										
											2024-01-22 20:52:19 +01:00
										 |  |  |             temp_unit=self.temp_unit, | 
					
						
							|  |  |  |             wind_unit=self.wind_unit, | 
					
						
							| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  |             language=self.language, | 
					
						
							| 
									
										
										
										
											2024-01-26 14:23:21 -08:00
										 |  |  |             tz_name=self.tz, | 
					
						
							| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  |         ) | 
					
						
							| 
									
										
										
										
											2024-01-22 20:52:19 +01:00
										 |  |  |         self.current_weather = self.my_owm.get_current_weather() | 
					
						
							|  |  |  |         self.hourly_forecasts = self.my_owm.get_weather_forecast() | 
					
						
							| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         ## Create Base Image | 
					
						
							|  |  |  |         self.createBaseImage() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         ## Add Current Weather to the left section | 
					
						
							|  |  |  |         self.addCurrentWeather() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         ## Add user-configurable section to the bottom left corner | 
					
						
							|  |  |  |         self.addUserSection() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-15 21:04:51 +01:00
										 |  |  |         ## Add Hourly Forecast to the top right section | 
					
						
							| 
									
										
										
										
											2024-01-15 20:45:42 +01:00
										 |  |  |         self.addHourlyForecast() | 
					
						
							| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-15 21:04:51 +01:00
										 |  |  |         ## Add Daily Forecast to the bottom right section | 
					
						
							|  |  |  |         self.addDailyForecast() | 
					
						
							| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-20 15:23:24 +01:00
										 |  |  |         if self.orientation == "horizontal": | 
					
						
							| 
									
										
										
										
											2024-01-20 16:33:06 +01:00
										 |  |  |             self.image = self.image.rotate(90, expand=True) | 
					
						
							| 
									
										
										
										
											2024-01-20 15:23:24 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  |         logger.info("Fullscreen weather forecast generated successfully.") | 
					
						
							| 
									
										
										
										
											2024-01-20 17:15:03 +01:00
										 |  |  |         # Convert images according to specified palette | 
					
						
							|  |  |  |         im_black, im_colour = image_to_palette(image=self.image, palette="bwr", dither=True) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  |         # Return the images ready for the display | 
					
						
							| 
									
										
										
										
											2024-01-20 17:15:03 +01:00
										 |  |  |         return im_black, im_colour | 
					
						
							| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-20 15:23:24 +01:00
										 |  |  |     def get_font(self, style, size): | 
					
						
							| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  |         # Returns the TrueType font object with the given characteristics | 
					
						
							| 
									
										
										
										
											2024-01-26 14:23:21 -08:00
										 |  |  |         # Some workarounds for typefaces that do not exist in some fonts out there | 
					
						
							| 
									
										
										
										
											2024-01-20 15:23:24 +01:00
										 |  |  |         if self.font == "Roboto" and style == "ExtraBold": | 
					
						
							| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  |             style = "Black" | 
					
						
							| 
									
										
										
										
											2024-01-20 15:23:24 +01:00
										 |  |  |         elif self.font in ["Ubuntu", "NotoSansUI"] and style in ["ExtraBold", "Black"]: | 
					
						
							| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  |             style = "Bold" | 
					
						
							| 
									
										
										
										
											2024-01-20 15:23:24 +01:00
										 |  |  |         elif self.font == "OpenSans" and style == "Black": | 
					
						
							| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  |             style = "ExtraBold" | 
					
						
							| 
									
										
										
										
											2024-01-20 15:23:24 +01:00
										 |  |  |         return ImageFont.truetype(fonts[f"{self.font}-{style}"], size=size) | 
					
						
							| 
									
										
										
										
											2024-01-15 19:08:46 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | if __name__ == "__main__": | 
					
						
							|  |  |  |     print(f"running {__name__} in standalone mode") |