diff --git a/README.md b/README.md index bf1bb5b0be8171b495c6ef0720d3a437e0ba1924..6f53ccd5a1deccf94777119c273e6c3e306112af 100644 --- a/README.md +++ b/README.md @@ -58,3 +58,6 @@ It's designed to be: and the modules can be easily created or modified. - Easily extensible, with a simple API for creating new modules. The clock is also designed to be easily configurable, with a simple configuration file that can be edited to change the appearance of the clock. + +## Attribution +Weather Icons by [Icons8](https://icons8.com/) diff --git a/config.py b/config.py index c171108e35085b262ee42588587b72ea7f4a81ff..7167164158bb20997ffa8e3fb4feef05f1bdbabc 100644 --- a/config.py +++ b/config.py @@ -15,7 +15,12 @@ default_hw_config = { "flask_port": 5000, "show_fps": False, "fps_max": 60, - "main_update_frequency": 500 + "main_update_frequency": 500, + "owm_api_key": "YOUR_API_KEY", + "weather_lat": 0.0, + "weather_long": 0.0, + "weather_update_frequency": 600, + "weather_units": "metric" } hw_config = default_hw_config.copy() @@ -49,7 +54,8 @@ PROP_AVAILABLE_WIDGETS = [ "IPShowWidget", "TestWidget", "AnalogClockWidget", - "DigitalClockWidget" + "DigitalClockWidget", + "WeatherWidget", ] PROP_AVAILABLE_PATTERNS = [ diff --git a/icons/weather/01d@2x.png b/icons/weather/01d@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..ed42ad9e01a8ac5c6a724cce862c79efe64c5113 Binary files /dev/null and b/icons/weather/01d@2x.png differ diff --git a/icons/weather/01n@2x.png b/icons/weather/01n@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..85efa16b13f4a9555e895d290ff14721a9ff1dff Binary files /dev/null and b/icons/weather/01n@2x.png differ diff --git a/icons/weather/02d@2x.png b/icons/weather/02d@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..fabd9c3b31a1032119075253795e9b7ed4116bcd Binary files /dev/null and b/icons/weather/02d@2x.png differ diff --git a/icons/weather/02n@2x.png b/icons/weather/02n@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..288a40ea0fa34005bd83cce484c6639ac53f6baa Binary files /dev/null and b/icons/weather/02n@2x.png differ diff --git a/icons/weather/03d@2x.png b/icons/weather/03d@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..ef2e9f7f955fe1b8da7f30e9ae15b2f707387c48 Binary files /dev/null and b/icons/weather/03d@2x.png differ diff --git a/icons/weather/03n@2x.png b/icons/weather/03n@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..ef2e9f7f955fe1b8da7f30e9ae15b2f707387c48 Binary files /dev/null and b/icons/weather/03n@2x.png differ diff --git a/icons/weather/04d@2x.png b/icons/weather/04d@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9c64ea86b6a93c7fd61543c696956b0657454fe9 Binary files /dev/null and b/icons/weather/04d@2x.png differ diff --git a/icons/weather/04n@2x.png b/icons/weather/04n@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9c64ea86b6a93c7fd61543c696956b0657454fe9 Binary files /dev/null and b/icons/weather/04n@2x.png differ diff --git a/icons/weather/09d@2x.png b/icons/weather/09d@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0f14cb61a53463cf6ca803561716b21a41cd3c36 Binary files /dev/null and b/icons/weather/09d@2x.png differ diff --git a/icons/weather/09n@2x.png b/icons/weather/09n@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0f14cb61a53463cf6ca803561716b21a41cd3c36 Binary files /dev/null and b/icons/weather/09n@2x.png differ diff --git a/icons/weather/10d@2x.png b/icons/weather/10d@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..62304fded6be1c5cea4a6a353cf336a98bec4c3f Binary files /dev/null and b/icons/weather/10d@2x.png differ diff --git a/icons/weather/10n@2x.png b/icons/weather/10n@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..b5e5d101266c3a54b4893e8e38e3c0014f3fdfa2 Binary files /dev/null and b/icons/weather/10n@2x.png differ diff --git a/icons/weather/11d@2x.png b/icons/weather/11d@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..4a885cfa9e79bc113e3d2dc7c7ad7d425e97e1fc Binary files /dev/null and b/icons/weather/11d@2x.png differ diff --git a/icons/weather/11n@2x.png b/icons/weather/11n@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..4a885cfa9e79bc113e3d2dc7c7ad7d425e97e1fc Binary files /dev/null and b/icons/weather/11n@2x.png differ diff --git a/icons/weather/13d@2x.png b/icons/weather/13d@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..7867322e900068a96547ec05d5e1a7b841d4ab64 Binary files /dev/null and b/icons/weather/13d@2x.png differ diff --git a/icons/weather/13n@2x.png b/icons/weather/13n@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..7867322e900068a96547ec05d5e1a7b841d4ab64 Binary files /dev/null and b/icons/weather/13n@2x.png differ diff --git a/icons/weather/50d@2x.png b/icons/weather/50d@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..f04122b30fdcde958c90ca339bb990fb5508ffef Binary files /dev/null and b/icons/weather/50d@2x.png differ diff --git a/icons/weather/50n@2x.png b/icons/weather/50n@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..f04122b30fdcde958c90ca339bb990fb5508ffef Binary files /dev/null and b/icons/weather/50n@2x.png differ diff --git a/icons/weather/icons8-cloud-96.png b/icons/weather/icons8-cloud-96.png new file mode 100644 index 0000000000000000000000000000000000000000..41a851fb7ec77f501eb3a23e72e7a9181f592a99 Binary files /dev/null and b/icons/weather/icons8-cloud-96.png differ diff --git a/icons/weather/icons8-cloud-lightning-96.png b/icons/weather/icons8-cloud-lightning-96.png new file mode 100644 index 0000000000000000000000000000000000000000..11ea0efdd1c014ec8aa29d1454b3dbc4fe4c4f2a Binary files /dev/null and b/icons/weather/icons8-cloud-lightning-96.png differ diff --git a/icons/weather/icons8-clouds-96.png b/icons/weather/icons8-clouds-96.png new file mode 100644 index 0000000000000000000000000000000000000000..3f5d4d4f83f350f50164835cf7eeb2ed07b0dbde Binary files /dev/null and b/icons/weather/icons8-clouds-96.png differ diff --git a/icons/weather/icons8-heavy-rain-96.png b/icons/weather/icons8-heavy-rain-96.png new file mode 100644 index 0000000000000000000000000000000000000000..1f8ad3e629d228e6be393ab15230490b6a1b5a13 Binary files /dev/null and b/icons/weather/icons8-heavy-rain-96.png differ diff --git a/icons/weather/icons8-light-rain-96.png b/icons/weather/icons8-light-rain-96.png new file mode 100644 index 0000000000000000000000000000000000000000..80b3971a63afab9bc569f19c7b68ac9c9b697215 Binary files /dev/null and b/icons/weather/icons8-light-rain-96.png differ diff --git a/icons/weather/icons8-light-snow-96.png b/icons/weather/icons8-light-snow-96.png new file mode 100644 index 0000000000000000000000000000000000000000..14a792fbf2515f1d9c965921480bd6090778f2c0 Binary files /dev/null and b/icons/weather/icons8-light-snow-96.png differ diff --git a/icons/weather/icons8-night-96.png b/icons/weather/icons8-night-96.png new file mode 100644 index 0000000000000000000000000000000000000000..91793582810fe14fbdef162e84094953e6b9e1f6 Binary files /dev/null and b/icons/weather/icons8-night-96.png differ diff --git a/icons/weather/icons8-night-wind-96.png b/icons/weather/icons8-night-wind-96.png new file mode 100644 index 0000000000000000000000000000000000000000..7e80a387879545bdb4b1a320727ea27780467562 Binary files /dev/null and b/icons/weather/icons8-night-wind-96.png differ diff --git a/icons/weather/icons8-partly-cloudy-day-96.png b/icons/weather/icons8-partly-cloudy-day-96.png new file mode 100644 index 0000000000000000000000000000000000000000..7207cab9fba18be0a8b6b8573f5b3f75062fbce0 Binary files /dev/null and b/icons/weather/icons8-partly-cloudy-day-96.png differ diff --git a/icons/weather/icons8-rain-96.png b/icons/weather/icons8-rain-96.png new file mode 100644 index 0000000000000000000000000000000000000000..1c2bd69874e13d2e8eb29d388f14cf5c1e128dc0 Binary files /dev/null and b/icons/weather/icons8-rain-96.png differ diff --git a/icons/weather/icons8-rain-cloud-96.png b/icons/weather/icons8-rain-cloud-96.png new file mode 100644 index 0000000000000000000000000000000000000000..74e3556e6f45cbbd7b3832b50aecaec0397d6b36 Binary files /dev/null and b/icons/weather/icons8-rain-cloud-96.png differ diff --git a/icons/weather/icons8-snow-96.png b/icons/weather/icons8-snow-96.png new file mode 100644 index 0000000000000000000000000000000000000000..9e94bbaec4740238ebec3208ba6239697e11ad3b Binary files /dev/null and b/icons/weather/icons8-snow-96.png differ diff --git a/icons/weather/icons8-storm-96.png b/icons/weather/icons8-storm-96.png new file mode 100644 index 0000000000000000000000000000000000000000..5ac2f7b64c2522c20371f37f2be91107b8cb6cac Binary files /dev/null and b/icons/weather/icons8-storm-96.png differ diff --git a/icons/weather/icons8-storm-with-heavy-rain-96.png b/icons/weather/icons8-storm-with-heavy-rain-96.png new file mode 100644 index 0000000000000000000000000000000000000000..7fe09cfa179312fe6d3ac88f6e1815bd38403d61 Binary files /dev/null and b/icons/weather/icons8-storm-with-heavy-rain-96.png differ diff --git a/icons/weather/icons8-thermometer-96.png b/icons/weather/icons8-thermometer-96.png new file mode 100644 index 0000000000000000000000000000000000000000..fb19b5bcc1f98682363f5ca52927749c4aed575f Binary files /dev/null and b/icons/weather/icons8-thermometer-96.png differ diff --git a/main.py b/main.py index dd13857ec595d4272dd5f69947049be3a1e5cac0..0e605cbfc7155b0f0221f8e183cf0693e4b68ad1 100644 --- a/main.py +++ b/main.py @@ -436,6 +436,8 @@ if __name__ == "__main__": try: message = mainthread_queue.get_nowait() parts = message.split(":") + print("Main Message Received") + print(message) if message == "EXIT": display_queue.put("EXIT") led_queue.put("EXIT") diff --git a/module_defaults.json b/module_defaults.json index c25c3ba950faa9a18c7f7da670d72aa6bb6a82f1..14c9c1c1f6467122c45af82133d43c2386ef6ee7 100644 --- a/module_defaults.json +++ b/module_defaults.json @@ -274,6 +274,63 @@ "type": "string" } }, + "WeatherWidget": { + "scalable_font_size": { + "name": "Font size", + "description": "The font size of the clock.", + "default": 15, + "type": "integer" + }, + "font_color": { + "name": "Font colour", + "description": "The colour of the font.", + "default": "#ffffff", + "type": "color" + }, + "font_name": { + "name": "Font name", + "description": "The name of the font to use.", + "default": null, + "type": "font" + }, + "font_bold": { + "name": "Font bold", + "description": "Whether the font should be bold.", + "default": false, + "type": "boolean" + }, + "font_italic": { + "name": "Font italic", + "description": "Whether the font should be italic.", + "default": false, + "type": "boolean" + }, + "draw_background": { + "name": "Draw background", + "description": "Draw the background of the widget.", + "default": true, + "type": "boolean" + }, + "background_type": { + "name": "Background type", + "description": "The type of background to use (`color` or `image`).", + "default": "color", + "type": "enum", + "allowed_values": ["color", "image"] + }, + "background_color": { + "name": "Background colour", + "description": "The colour of the background.", + "default": "#000000", + "type": "color" + }, + "background_image": { + "name": "Background image", + "description": "The image to use as the background.", + "default": "", + "type": "filepath" + } + }, "SolidColorPattern": { "tick_rate": { "name": "Tick rate", diff --git a/requirements.txt b/requirements.txt index e041ca2758dcfacaacf0a77ed2965fc3b34f6463..bec2c92274a5f16fd42afd7501b8b28e54d66310 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ pygame~=2.6.0 -flask~=3.0.3 \ No newline at end of file +flask~=3.0.3 +requests~=2.32.3 \ No newline at end of file diff --git a/screenwidgets.py b/screenwidgets.py index 12884e02e916bfbdf56cc6c4905d17e16931f3c9..a74da57c3cc416336770223304391fb4edd75b00 100644 --- a/screenwidgets.py +++ b/screenwidgets.py @@ -1,10 +1,12 @@ import math +import time from datetime import datetime -from config import PROP_WINDOW_SCALE, PROP_SCREEN_WIDTH, PROP_SCREEN_HEIGHT +from config import PROP_WINDOW_SCALE, PROP_SCREEN_WIDTH, PROP_SCREEN_HEIGHT, hw_config from utils.num import get_screen_centre from utils.net import get_ip from utils.fonts import get_font_path_or_default +from utils.weather import Weather class IWidgetMeta(type): @@ -241,3 +243,78 @@ class IPShowWidget(IWidget): screen.blit(text_surface, text_rect) screen.blit(text_surface_2, text_rect_2) +class WeatherWidget(IWidget): + def __init__(self, config: dict): + super().__init__(config) + self.pygame = config['pygame'] + + # Get the font path + self.font_path = get_font_path_or_default(self.pygame, config.get("font_name", None), + config.get("font_bold", False), config.get("font_italic", False)) + + # Load the font + self.font = self.pygame.font.Font(self.font_path, config.get("font_size", 15 * PROP_WINDOW_SCALE)) + self.font.bold = config.get("font_bold", False) + self.font.italic = config.get("font_italic", False) + + # Set the colours + self.color = config.get("font_color", "white") + self.background_color = config.get("background_color", "black") + + # Set background options + self.draw_background = config.get("draw_background", False) + self.background_type = config.get("background_type", "color") + if self.background_type == "image" and config.get("background_image", "") != "": + self.bg_image = self.pygame.image.load(config.get("background_image", "")) + self.bg_image = self.pygame.transform.scale(self.bg_image, ( + PROP_SCREEN_WIDTH * PROP_WINDOW_SCALE, PROP_SCREEN_HEIGHT * PROP_WINDOW_SCALE)) + + # Set the weather object + self.weather = Weather(hw_config["owm_api_key"], hw_config["weather_lat"], hw_config["weather_long"], hw_config["weather_units"]) + self.last_updated = 0 + self.place_name = "No Data" + self.weather_description = "" + self.weather_icon = "" + self.temperature = 0 + + self.update_frequency = hw_config["weather_update_frequency"] + + def draw(self, screen): + if time.time() - self.last_updated > self.update_frequency: + self.last_updated = time.time() + print("Updating weather") + # Update the weather + weather = self.weather.get_weather() + if weather is not None: + self.place_name = weather["name"] + self.weather_description = weather["description"] + self.weather_icon = weather["icon"] + self.temperature = weather["temp"] + else: + self.place_name = "No Data" + self.weather_description = "" + self.weather_icon = "" + self.temperature = 0 + + + # Fill the background + if self.draw_background: + if self.background_type == "color": + screen.fill(self.background_color) + elif self.background_type == "image": + screen.blit(self.bg_image, (0, 0)) + + # Render the text + if self.weather_icon != "": + wicon = self.pygame.image.load(self.weather_icon) + wicon = self.pygame.transform.scale(wicon, (65 * PROP_WINDOW_SCALE, 65 * PROP_WINDOW_SCALE)) + + screen.blit(wicon, (get_screen_centre()[0] - 50 * PROP_WINDOW_SCALE, get_screen_centre()[1] - 50 * PROP_WINDOW_SCALE)) + + text_surface = self.font.render(f"{self.place_name}", True, self.color) + text_surface_2 = self.font.render(f"{self.weather_description}", True, self.color) + text_surface_3 = self.font.render(f"{self.temperature}", True, self.color) + + screen.blit(text_surface, (get_screen_centre()[0] - 50 * PROP_WINDOW_SCALE, get_screen_centre()[1] + 50 * PROP_WINDOW_SCALE)) + screen.blit(text_surface_2, (get_screen_centre()[0] - 50 * PROP_WINDOW_SCALE, get_screen_centre()[1] + 70 * PROP_WINDOW_SCALE)) + screen.blit(text_surface_3, (get_screen_centre()[0] - 50 * PROP_WINDOW_SCALE, get_screen_centre()[1] + 90 * PROP_WINDOW_SCALE)) \ No newline at end of file diff --git a/utils/weather.py b/utils/weather.py new file mode 100644 index 0000000000000000000000000000000000000000..876b1c79d1c108ed40f90bb4ac032855ac911638 --- /dev/null +++ b/utils/weather.py @@ -0,0 +1,40 @@ +import os.path + +import requests + +# weather['name'] +# weather['weather'][0]['description'] +# weather['weather'][0]['icon'] +# weather['main']['temp'] +# weather['main']['feels_like'] + +class Weather: + def __init__(self, api_key, latitude, longitude, weather_units): + self.api_key = api_key + self.lat = latitude + self.long = longitude + self.weather_units = weather_units + def get_weather(self): + r = requests.get(f"https://api.openweathermap.org/data/2.5/weather?lat={self.lat}&lon={self.long}&appid={self.api_key}&units={self.weather_units}") + + if r.status_code != 200: + return None + jsn = r.json() + + if "weather" not in jsn or len(jsn.get("weather", [])) == 0: + return None + + # Get icon path + icon = jsn['weather'][0]['icon'] + icon_path = f"icons/weather/{icon}@2x.png" + + if not os.path.exists(icon_path): + icon_path = "" + + return { + "name": jsn['name'], + "description": jsn['weather'][0]['description'], + "icon": icon_path, + "temp": jsn['main']['temp'], + "feels_like": jsn['main']['feels_like'] + }