diff --git a/src/display_controller.py b/src/display_controller.py index 8a9ce615..04ecb9b8 100644 --- a/src/display_controller.py +++ b/src/display_controller.py @@ -19,8 +19,10 @@ class DisplayController: self.weather = WeatherManager(self.config, self.display_manager) self.current_display = 'clock' self.last_switch = time.time() - self.hourly_index = 0 - self.last_hourly_update = time.time() + self.scroll_position = 0 + self.scroll_speed = 2 # Pixels per update + self.last_scroll = time.time() + self.scroll_interval = 0.05 # 50ms between scroll updates logger.info("DisplayController initialized with display_manager: %s", id(self.display_manager)) def run(self): @@ -29,7 +31,6 @@ class DisplayController: while True: current_time = time.time() rotation_interval = self.config['display'].get('rotation_interval', 15) - hourly_scroll_interval = 3 # Show each hour for 3 seconds # Track if we're switching modes switching_modes = False @@ -41,8 +42,7 @@ class DisplayController: self.current_display = 'weather' elif self.current_display == 'weather': self.current_display = 'hourly' - self.hourly_index = 0 - self.last_hourly_update = current_time + self.scroll_position = 0 elif self.current_display == 'hourly': self.current_display = 'daily' else: # daily @@ -52,11 +52,11 @@ class DisplayController: self.last_switch = current_time switching_modes = True - # Update hourly forecast index if needed - if self.current_display == 'hourly' and current_time - self.last_hourly_update > hourly_scroll_interval: - self.hourly_index = (self.hourly_index + 1) % 6 # We show 6 hours - self.last_hourly_update = current_time - switching_modes = True # Force clear for new hour + # Update scroll position for hourly forecast + if self.current_display == 'hourly' and current_time - self.last_scroll > self.scroll_interval: + self.scroll_position += self.scroll_speed + self.last_scroll = current_time + switching_modes = True # Force clear for smooth scrolling # Display current screen if self.current_display == 'clock': @@ -67,13 +67,13 @@ class DisplayController: self.weather.display_weather(force_clear=switching_modes) elif self.current_display == 'hourly': logger.debug("Updating hourly forecast display") - self.weather.display_hourly_forecast(self.hourly_index, force_clear=switching_modes) + self.weather.display_hourly_forecast(self.scroll_position, force_clear=switching_modes) else: # daily logger.debug("Updating daily forecast display") self.weather.display_daily_forecast(force_clear=switching_modes) - # Sleep for 0.5 seconds since we only need to check for second changes - time.sleep(0.5) + # Sleep for a shorter interval to make scrolling smoother + time.sleep(0.02) except KeyboardInterrupt: print("\nDisplay stopped by user") diff --git a/src/display_manager.py b/src/display_manager.py index 24ae976c..6b88303f 100644 --- a/src/display_manager.py +++ b/src/display_manager.py @@ -168,6 +168,34 @@ class DisplayManager: # Update the display using double buffering self.update_display() + def draw_scrolling_text(self, text: str, scroll_position: int, force_clear: bool = False) -> None: + """Draw scrolling text on the display.""" + if force_clear: + self.clear() + else: + # Just create a new blank image without updating display + self.image = Image.new('RGB', (self.matrix.width * 2, self.matrix.height)) # Double width for scrolling + self.draw = ImageDraw.Draw(self.image) + + # Calculate text dimensions + bbox = self.draw.textbbox((0, 0), text, font=self.font) + text_width = bbox[2] - bbox[0] + text_height = bbox[3] - bbox[1] + + # Draw text at current scroll position + y = (self.matrix.height - text_height) // 2 + self.draw.text((self.matrix.width - scroll_position, y), text, font=self.font, fill=(255, 255, 255)) + + # If text has scrolled past the left edge, draw it again at the right + if scroll_position > text_width: + self.draw.text((self.matrix.width * 2 - scroll_position, y), text, font=self.font, fill=(255, 255, 255)) + + # Create a cropped version of the image that's the size of our display + visible_portion = self.image.crop((0, 0, self.matrix.width, self.matrix.height)) + + # Update the display with the visible portion + self.matrix.SetImage(visible_portion) + def cleanup(self): """Clean up resources.""" self.matrix.Clear() diff --git a/src/weather_manager.py b/src/weather_manager.py index 61983a13..cde914cc 100644 --- a/src/weather_manager.py +++ b/src/weather_manager.py @@ -5,23 +5,23 @@ from typing import Dict, Any, List from PIL import Image, ImageDraw class WeatherManager: - # Weather condition to emoji mapping + # Weather condition to larger colored icons (we'll use these as placeholders until you provide custom ones) WEATHER_ICONS = { - 'Clear': '☀️', - 'Clouds': '☁️', - 'Rain': '🌧️', - 'Snow': '❄️', - 'Thunderstorm': '⛈️', - 'Drizzle': '🌦️', - 'Mist': '🌫️', - 'Fog': '🌫️', - 'Haze': '🌫️', - 'Smoke': '🌫️', - 'Dust': '🌫️', - 'Sand': '🌫️', - 'Ash': '🌫️', - 'Squall': '💨', - 'Tornado': '🌪️' + 'Clear': '🌞', # Larger sun with rays + 'Clouds': '☁️', # Cloud + 'Rain': '🌧️', # Rain cloud + 'Snow': '❄️', # Snowflake + 'Thunderstorm': '⛈️', # Storm cloud + 'Drizzle': '🌦️', # Sun behind rain cloud + 'Mist': '🌫️', # Fog + 'Fog': '🌫️', # Fog + 'Haze': '🌫️', # Fog + 'Smoke': '🌫️', # Fog + 'Dust': '🌫️', # Fog + 'Sand': '🌫️', # Fog + 'Ash': '🌫️', # Fog + 'Squall': '💨', # Dash symbol + 'Tornado': '🌪️' # Tornado } def __init__(self, config: Dict[str, Any], display_manager): @@ -34,6 +34,7 @@ class WeatherManager: self.forecast_data = None self.hourly_forecast = None self.daily_forecast = None + self.scroll_position = 0 def _fetch_weather(self) -> None: """Fetch current weather and forecast data from OpenWeatherMap API.""" @@ -81,7 +82,7 @@ class WeatherManager: # Process hourly forecast (next 6 hours) self.hourly_forecast = [] for item in self.forecast_data['list'][:6]: # First 6 entries (3 hours each) - hour = datetime.fromtimestamp(item['dt']).strftime('%I%p') + hour = datetime.fromtimestamp(item['dt']).strftime('%I%p').lstrip('0') # Remove leading zero temp = round(item['main']['temp']) condition = item['weather'][0]['main'] icon = self.WEATHER_ICONS.get(condition, '❓') @@ -108,7 +109,6 @@ class WeatherManager: self.daily_forecast = [] for date, data in list(daily_data.items())[:3]: # First 3 days avg_temp = round(sum(data['temps']) / len(data['temps'])) - # Get most common condition for the day condition = max(set(data['conditions']), key=data['conditions'].count) icon = self.WEATHER_ICONS.get(condition, '❓') display_date = datetime.strptime(date, '%Y-%m-%d').strftime('%a %d') @@ -137,27 +137,36 @@ class WeatherManager: condition = weather_data['weather'][0]['main'] icon = self.WEATHER_ICONS.get(condition, '❓') - # Format the display string with temp, icon, and condition - display_text = f"{temp}°F {icon}\n{condition}" + # Format the display string with temp and large icon + display_text = f"{temp}°F\n{icon}" # Draw both lines at once using the multi-line support in draw_text self.display_manager.draw_text(display_text, force_clear=force_clear) - def display_hourly_forecast(self, index: int = 0, force_clear: bool = False) -> None: - """Display hourly forecast information, showing one time slot at a time.""" + def display_hourly_forecast(self, scroll_amount: int = 0, force_clear: bool = False) -> None: + """Display scrolling hourly forecast information.""" if not self.hourly_forecast: self.get_weather() # This will also update forecasts if not self.hourly_forecast: return - # Get the forecast for the current index - forecast = self.hourly_forecast[index % len(self.hourly_forecast)] + # Update scroll position + self.scroll_position = scroll_amount + + # Create the full scrolling text + forecasts = [] + for forecast in self.hourly_forecast: + forecasts.append(f"{forecast['hour']}\n{forecast['temp']}°F\n{forecast['icon']}") - # Format the display string - display_text = f"{forecast['hour']}\n{forecast['temp']}°F {forecast['icon']}" + # Join with some spacing between each forecast + display_text = " | ".join(forecasts) - # Draw the forecast - self.display_manager.draw_text(display_text, force_clear=force_clear) + # Draw the scrolling text + self.display_manager.draw_scrolling_text( + display_text, + self.scroll_position, + force_clear=force_clear + ) def display_daily_forecast(self, force_clear: bool = False) -> None: """Display 3-day forecast information.""" @@ -169,7 +178,7 @@ class WeatherManager: # Create a compact display of all three days lines = [] for day in self.daily_forecast: - lines.append(f"{day['date']}: {day['temp']}°F {day['icon']}") + lines.append(f"{day['date']}\n{day['temp']}°F {day['icon']}") # Join all lines with newlines display_text = "\n".join(lines)