From eebfb0b80ded839cc2587d96228bb261f542e10d Mon Sep 17 00:00:00 2001 From: Chuck <33324927+ChuckBuilds@users.noreply.github.com> Date: Mon, 7 Apr 2025 19:13:59 -0500 Subject: [PATCH] adding modularity and Weather Adding weather --- config/config.json | 14 +++++++- requirements.txt | 5 ++- src/clock.py | 67 ++++++++++++++++++++++++++----------- src/display_controller.py | 46 +++++++++++++++++++++++++ src/weather_manager.py | 70 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 180 insertions(+), 22 deletions(-) create mode 100644 src/display_controller.py create mode 100644 src/weather_manager.py diff --git a/config/config.json b/config/config.json index 6a478e39..7d434ab8 100644 --- a/config/config.json +++ b/config/config.json @@ -1,13 +1,25 @@ { "timezone": "America/Chicago", + "location": { + "city": "Dallas", + "state": "Texas", + "country": "US" + }, "display": { "brightness": 50, "rows": 32, "cols": 64, - "chain_length": 2 + "chain_length": 2, + "rotation_interval": 10 }, "clock": { "format": "%H:%M:%S", "update_interval": 1 + }, + "weather": { + "api_key": "YOUR_OPENWEATHERMAP_API_KEY", + "update_interval": 300, + "units": "imperial", + "display_format": "{temp}°F\n{condition}" } } \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index c57cc9d7..e4bb92d9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,6 @@ rpi-rgb-led-matrix==1.0.0 Pillow==10.0.0 -pytz==2023.3 \ No newline at end of file +pytz==2023.3 +requests==2.31.0 +timezonefinder==6.2.0 +geopy==2.4.1 \ No newline at end of file diff --git a/src/clock.py b/src/clock.py index 512e9ca3..cf09e041 100644 --- a/src/clock.py +++ b/src/clock.py @@ -1,38 +1,65 @@ import time from datetime import datetime import pytz +from typing import Dict, Any from config_manager import ConfigManager from display_manager import DisplayManager class Clock: def __init__(self): self.config_manager = ConfigManager() - self.display_manager = DisplayManager(self.config_manager.get_display_config()) - self.timezone = pytz.timezone(self.config_manager.get_timezone()) - self.clock_config = self.config_manager.get_clock_config() + self.config = self.config_manager.config + self.display_manager = DisplayManager(self.config.get('display', {})) + self.location = self.config.get('location', {}) + self.clock_config = self.config.get('clock', {}) + self.timezone = self._get_timezone() + + def _get_timezone(self) -> str: + """Get timezone based on location.""" + from timezonefinder import TimezoneFinder + from geopy.geocoders import Nominatim + + try: + # Get coordinates for the location + geolocator = Nominatim(user_agent="led_matrix_clock") + location_str = f"{self.location['city']}, {self.location['state']}, {self.location['country']}" + location = geolocator.geocode(location_str) + + if location: + # Find timezone from coordinates + tf = TimezoneFinder() + timezone_str = tf.timezone_at(lng=location.longitude, lat=location.latitude) + return pytz.timezone(timezone_str) + except Exception as e: + print(f"Error finding timezone: {e}") + + # Fallback to UTC + return pytz.UTC def get_current_time(self) -> str: """Get the current time in the configured timezone.""" current_time = datetime.now(self.timezone) return current_time.strftime(self.clock_config.get('format', '%H:%M:%S')) - def run(self): - """Run the clock display.""" - try: - while True: - current_time = self.get_current_time() - # Center the text on the display - text_width = self.display_manager.font.getlength(current_time) - x = (self.display_manager.matrix.width - text_width) // 2 - y = (self.display_manager.matrix.height - 24) // 2 - - self.display_manager.draw_text(current_time, x, y) - time.sleep(self.clock_config.get('update_interval', 1)) - except KeyboardInterrupt: - print("Clock stopped by user") - finally: - self.display_manager.cleanup() + def display_time(self) -> None: + """Display the current time.""" + current_time = self.get_current_time() + + # Center the text on the display + text_width = self.display_manager.font.getlength(current_time) + x = (self.display_manager.matrix.width - text_width) // 2 + y = (self.display_manager.matrix.height - 24) // 2 + + self.display_manager.clear() + self.display_manager.draw_text(current_time, x, y) if __name__ == "__main__": clock = Clock() - clock.run() \ No newline at end of file + try: + while True: + clock.display_time() + time.sleep(clock.clock_config.get('update_interval', 1)) + except KeyboardInterrupt: + print("\nClock stopped by user") + finally: + clock.display_manager.cleanup() \ No newline at end of file diff --git a/src/display_controller.py b/src/display_controller.py new file mode 100644 index 00000000..d22c6c47 --- /dev/null +++ b/src/display_controller.py @@ -0,0 +1,46 @@ +import time +from typing import Dict, Any +from clock import Clock +from weather_manager import WeatherManager +from display_manager import DisplayManager +from config_manager import ConfigManager + +class DisplayController: + def __init__(self): + self.config_manager = ConfigManager() + self.config = self.config_manager.config + self.display_manager = DisplayManager(self.config.get('display', {})) + self.clock = Clock() + self.weather = WeatherManager(self.config, self.display_manager) + self.current_display = 'clock' + self.last_switch = time.time() + + def run(self): + """Run the display controller, switching between displays.""" + try: + while True: + current_time = time.time() + rotation_interval = self.config['display'].get('rotation_interval', 10) + + # Switch display if interval has passed + if current_time - self.last_switch > rotation_interval: + self.current_display = 'weather' if self.current_display == 'clock' else 'clock' + self.last_switch = current_time + + # Display current screen + if self.current_display == 'clock': + self.clock.display_time() + else: + self.weather.display_weather() + + # Small delay to prevent CPU overload + time.sleep(0.1) + + except KeyboardInterrupt: + print("\nDisplay stopped by user") + finally: + self.display_manager.cleanup() + +if __name__ == "__main__": + controller = DisplayController() + controller.run() \ No newline at end of file diff --git a/src/weather_manager.py b/src/weather_manager.py new file mode 100644 index 00000000..9fa646df --- /dev/null +++ b/src/weather_manager.py @@ -0,0 +1,70 @@ +import requests +import time +from typing import Dict, Any +from PIL import Image, ImageDraw + +class WeatherManager: + def __init__(self, config: Dict[str, Any], display_manager): + self.config = config + self.display_manager = display_manager + self.weather_config = config.get('weather', {}) + self.location = config.get('location', {}) + self.last_update = 0 + self.weather_data = None + + def _fetch_weather(self) -> None: + """Fetch weather data from OpenWeatherMap API.""" + api_key = self.weather_config['api_key'] + city = self.location['city'] + state = self.location['state'] + country = self.location['country'] + units = self.weather_config.get('units', 'imperial') + + url = f"http://api.openweathermap.org/data/2.5/weather?q={city},{state},{country}&appid={api_key}&units={units}" + + try: + response = requests.get(url) + response.raise_for_status() + self.weather_data = response.json() + self.last_update = time.time() + except Exception as e: + print(f"Error fetching weather data: {e}") + self.weather_data = None + + def get_weather(self) -> Dict[str, Any]: + """Get current weather data, fetching new data if needed.""" + current_time = time.time() + if (not self.weather_data or + current_time - self.last_update > self.weather_config.get('update_interval', 300)): + self._fetch_weather() + return self.weather_data + + def display_weather(self) -> None: + """Display weather information on the LED matrix.""" + weather_data = self.get_weather() + if not weather_data: + return + + temp = round(weather_data['main']['temp']) + condition = weather_data['weather'][0]['main'] + + # Format the display string + display_text = self.weather_config.get('display_format', '{temp}°F\n{condition}') + display_text = display_text.format(temp=temp, condition=condition) + + # Split text into lines + lines = display_text.split('\n') + + # Calculate vertical spacing + total_height = len(lines) * 24 # Assuming 24px font height + start_y = (self.display_manager.matrix.height - total_height) // 2 + + # Clear the display + self.display_manager.clear() + + # Draw each line centered + for i, line in enumerate(lines): + text_width = self.display_manager.font.getlength(line) + x = (self.display_manager.matrix.width - text_width) // 2 + y = start_y + (i * 24) + self.display_manager.draw_text(line, x, y) \ No newline at end of file