#!/usr/bin/env python3 """ Matrix64 LED Display with Multi-View Pagination Views: 0: Main (Weather + Clock + Temps) 1: HDD Details 2: Date + Weather (Spanish) Buttons (GPIO 0 & 1): - Button 1: Next view - Button 2: Previous view - Long press: Toggle auto-cycle """ from samplebase import SampleBase from rgbmatrix import graphics from gpiozero import Button import time import datetime import locale from config import LED_ROWS, LED_COLS from weather_icons import draw_weather_icon from home_assistant import ( get_weather, get_weather_description, get_interior_weather, get_brightness ) from netdata import get_hdd_temps # Set Spanish locale for dates try: locale.setlocale(locale.LC_TIME, 'es_ES.UTF-8') except: try: locale.setlocale(locale.LC_TIME, 'es_ES') except: pass # Fall back to default # Number of views NUM_VIEWS = 3 # Spanish day names fallback SPANISH_DAYS = { 'Monday': 'Lunes', 'Tuesday': 'Martes', 'Wednesday': 'Miércoles', 'Thursday': 'Jueves', 'Friday': 'Viernes', 'Saturday': 'Sábado', 'Sunday': 'Domingo' } SPANISH_MONTHS = { 'Jan': 'Ene', 'Feb': 'Feb', 'Mar': 'Mar', 'Apr': 'Abr', 'May': 'May', 'Jun': 'Jun', 'Jul': 'Jul', 'Aug': 'Ago', 'Sep': 'Sep', 'Oct': 'Oct', 'Nov': 'Nov', 'Dec': 'Dic' } def format_temp(value): """Format temperature to one decimal place.""" try: return f"{float(value):.1f}°" except (ValueError, TypeError): return "?°" def format_humidity(value): """Format humidity as integer percentage.""" try: return f"{int(float(value))}%" except (ValueError, TypeError): return "?%" def get_temperature_color(temp): """Determine color based on temperature value.""" try: temp_value = float(temp) if isinstance(temp, (int, float)) else float(str(temp).replace('°', '').strip()) if temp_value < 0: return graphics.Color(0, 100, 255) elif temp_value < 10: return graphics.Color(100, 180, 255) elif temp_value < 20: return graphics.Color(0, 255, 100) elif temp_value < 30: return graphics.Color(255, 200, 0) else: return graphics.Color(255, 50, 50) except ValueError: return graphics.Color(255, 255, 255) def get_spanish_day(now): """Get day name in Spanish.""" day_en = now.strftime("%A") return SPANISH_DAYS.get(day_en, day_en) def get_spanish_date(now): """Get date in Spanish format.""" day = now.day month_en = now.strftime("%b") month = SPANISH_MONTHS.get(month_en, month_en) return f"{day} {month}" class Matrix64Display(SampleBase): def __init__(self, *args, **kwargs): super(Matrix64Display, self).__init__(*args, **kwargs) self.parser.add_argument("-t", "--text", help="Text", default="") # Data self.temperature = None self.humidity = None self.interior_temperature = None self.interior_humidity = None self.last_update = time.time() self.weather_desc = "unknown" self.hdd_temps = None # View state self.current_view = 0 self.auto_cycle = False self.last_view_change = time.time() self.view_cycle_interval = 5 # seconds # Feedback message self.feedback_message = None self.feedback_until = 0 # Animation self.animation_frame = 0 # Button setup self.button1 = Button(0, pull_up=True, hold_time=1.5) self.button2 = Button(1, pull_up=True, hold_time=1.5) # Button callbacks self.button1.when_pressed = self.on_button1_press self.button1.when_held = self.on_button_held self.button2.when_pressed = self.on_button2_press self.button2.when_held = self.on_button_held def show_feedback(self, message, duration=2): """Show a feedback message for a duration.""" self.feedback_message = message self.feedback_until = time.time() + duration def on_button1_press(self): """Next view.""" if not self.button1.is_held: self.current_view = (self.current_view + 1) % NUM_VIEWS self.last_view_change = time.time() print(f"View: {self.current_view}") def on_button2_press(self): """Previous view.""" if not self.button2.is_held: self.current_view = (self.current_view - 1) % NUM_VIEWS self.last_view_change = time.time() print(f"View: {self.current_view}") def on_button_held(self): """Toggle auto-cycle on long press.""" self.auto_cycle = not self.auto_cycle if self.auto_cycle: self.show_feedback("AUTO ON", 2) else: self.show_feedback("AUTO OFF", 2) print(f"Auto-cycle: {self.auto_cycle}") def update_data(self): try: weather = get_weather() if weather.get("temperature") is not None: self.temperature = float(weather.get("temperature")) self.humidity = weather.get("humidity") self.weather_desc = get_weather_description() interior = get_interior_weather() if interior.get("temperature") is not None: self.interior_temperature = float(interior.get("temperature")) self.interior_humidity = interior.get("humidity") except Exception as e: print(f"Error updating data: {e}") def update_brightness(self): try: brightness = get_brightness() if brightness: self.matrix.brightness = int(float(brightness)) / 10 + 1 except Exception as e: self.matrix.brightness = 5 def update_hdd_temps(self): self.hdd_temps = get_hdd_temps() def draw_feedback(self, canvas, fonts, colors): """Draw feedback message overlay.""" time_font, data_font, small_font = fonts time_color, humidity_color, hdd_color, label_color, line_color = colors # Dark background for y in range(24, 44): graphics.DrawLine(canvas, 0, y, 63, y, graphics.Color(0, 0, 0)) # Message msg_color = graphics.Color(0, 255, 100) msg_len = len(self.feedback_message) * 7 x = (64 - msg_len) // 2 graphics.DrawText(canvas, time_font, x, 38, msg_color, self.feedback_message) def draw_view_0(self, canvas, fonts, colors): """Main view: Weather + Clock + Temps.""" time_font, data_font, small_font = fonts time_color, humidity_color, hdd_color, label_color, line_color = colors now = datetime.datetime.now() time_str = f"{now.hour:02d}:{now.minute:02d}:{now.second:02d}" # Weather Icon (with animation offset) anim_offset = (self.animation_frame % 4) - 2 # -2 to 1 draw_weather_icon(canvas, anim_offset, 0, self.weather_desc) # Vertical separator graphics.DrawLine(canvas, 28, 0, 28, 25, line_color) # HDD Temps (3 rows of 2) if self.hdd_temps and len(self.hdd_temps) > 1: temps = [str(int(t[0])) for t in self.hdd_temps[1:] if t] if len(temps) >= 2: graphics.DrawText(canvas, data_font, 34, 8, hdd_color, " ".join(temps[:2])) if len(temps) >= 4: graphics.DrawText(canvas, data_font, 34, 16, hdd_color, " ".join(temps[2:4])) if len(temps) > 4: graphics.DrawText(canvas, data_font, 34, 24, hdd_color, " ".join(temps[4:6])) # Horizontal lines around clock graphics.DrawLine(canvas, 0, 26, 63, 26, line_color) time_x = (64 - len(time_str) * 7) // 2 graphics.DrawText(canvas, time_font, time_x, 39, time_color, time_str) graphics.DrawLine(canvas, 0, 42, 63, 42, line_color) # Outdoor temp/humidity graphics.DrawText(canvas, data_font, 0, 52, label_color, "Out") if self.temperature is not None: temp_color = get_temperature_color(self.temperature) graphics.DrawText(canvas, data_font, 20, 52, temp_color, format_temp(self.temperature)) if self.humidity is not None: graphics.DrawText(canvas, data_font, 48, 52, humidity_color, format_humidity(self.humidity)) # Indoor temp/humidity graphics.DrawText(canvas, data_font, 0, 62, label_color, "In") if self.interior_temperature is not None: temp_color = get_temperature_color(self.interior_temperature) graphics.DrawText(canvas, data_font, 20, 62, temp_color, format_temp(self.interior_temperature)) if self.interior_humidity is not None: graphics.DrawText(canvas, data_font, 48, 62, humidity_color, format_humidity(self.interior_humidity)) def draw_view_1(self, canvas, fonts, colors): """HDD Details view - 6 HDDs in 2 columns.""" time_font, data_font, small_font = fonts time_color, humidity_color, hdd_color, label_color, line_color = colors # Title graphics.DrawText(canvas, data_font, 4, 8, time_color, "HDD Temps") graphics.DrawLine(canvas, 0, 10, 63, 10, line_color) if self.hdd_temps and len(self.hdd_temps) > 1: temps = [(i+1, int(t[0])) for i, t in enumerate(self.hdd_temps[1:7]) if t] # Two columns: left (1-3), right (4-6) y = 20 for idx, (num, temp_val) in enumerate(temps[:3]): temp_color = get_temperature_color(temp_val) graphics.DrawText(canvas, small_font, 2, y, label_color, f"{num}:") graphics.DrawText(canvas, small_font, 12, y, temp_color, f"{temp_val}C") y += 9 y = 20 for idx, (num, temp_val) in enumerate(temps[3:6]): temp_color = get_temperature_color(temp_val) graphics.DrawText(canvas, small_font, 34, y, label_color, f"{num}:") graphics.DrawText(canvas, small_font, 44, y, temp_color, f"{temp_val}C") y += 9 # Auto-cycle indicator if self.auto_cycle: graphics.DrawText(canvas, small_font, 58, 62, label_color, "A") def draw_view_2(self, canvas, fonts, colors): """Date + Weather view (Spanish).""" time_font, data_font, small_font = fonts time_color, humidity_color, hdd_color, label_color, line_color = colors now = datetime.datetime.now() # Day name in Spanish day_str = get_spanish_day(now) graphics.DrawText(canvas, data_font, 4, 10, label_color, day_str) # Date in Spanish date_str = get_spanish_date(now) graphics.DrawText(canvas, time_font, 4, 24, time_color, date_str) graphics.DrawLine(canvas, 0, 28, 63, 28, line_color) # Weather icon with animation anim_offset = (self.animation_frame % 4) - 2 draw_weather_icon(canvas, 4 + anim_offset, 32, self.weather_desc) # Temperature if self.temperature is not None: temp_color = get_temperature_color(self.temperature) graphics.DrawText(canvas, time_font, 32, 50, temp_color, f"{self.temperature:.0f}C") # Auto-cycle indicator if self.auto_cycle: graphics.DrawText(canvas, small_font, 58, 62, label_color, "A") def run(self): canvas = self.matrix.CreateFrameCanvas() # Fonts font_dir = "/home/pi/rpi-rgb-led-matrix/fonts" time_font = graphics.Font() time_font.LoadFont(f"{font_dir}/7x13.bdf") data_font = graphics.Font() data_font.LoadFont(f"{font_dir}/5x8.bdf") small_font = graphics.Font() small_font.LoadFont(f"{font_dir}/4x6.bdf") fonts = (time_font, data_font, small_font) # Colors time_color = graphics.Color(30, 90, 220) humidity_color = graphics.Color(80, 160, 255) hdd_color = graphics.Color(140, 120, 100) label_color = graphics.Color(120, 120, 120) line_color = graphics.Color(40, 40, 40) colors = (time_color, humidity_color, hdd_color, label_color, line_color) # Initial fetch self.update_brightness() self.update_data() self.update_hdd_temps() while True: current_time = time.time() # Update data every 60 seconds if current_time - self.last_update >= 60: self.update_data() self.update_brightness() self.update_hdd_temps() self.last_update = current_time # Auto-cycle views if self.auto_cycle and (current_time - self.last_view_change >= self.view_cycle_interval): self.current_view = (self.current_view + 1) % NUM_VIEWS self.last_view_change = current_time # Animation frame self.animation_frame += 1 canvas.Clear() # Draw current view if self.current_view == 0: self.draw_view_0(canvas, fonts, colors) elif self.current_view == 1: self.draw_view_1(canvas, fonts, colors) elif self.current_view == 2: self.draw_view_2(canvas, fonts, colors) # Draw feedback message if active if self.feedback_message and current_time < self.feedback_until: self.draw_feedback(canvas, fonts, colors) else: self.feedback_message = None time.sleep(0.5) canvas = self.matrix.SwapOnVSync(canvas) if __name__ == "__main__": display = Matrix64Display() if not display.process(): display.print_help()