diff --git a/matrix.py b/matrix.py index bbb457e..e8a140a 100644 --- a/matrix.py +++ b/matrix.py @@ -1,18 +1,23 @@ #!/usr/bin/env python3 """ -Matrix64 LED Display +Matrix64 LED Display with Multi-View Pagination -Layout: - [Weather 24x24] [HDD temps] - [HDD temps] - 12:37:25 - Out 21.5° 65% - In 22.3° 58% +Views: + 0: Main (Weather + Clock + Temps) + 1: HDD Details + 2: Date + Weather Forecast + +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 threading from config import LED_ROWS, LED_COLS from weather_icons import draw_weather_icon @@ -23,6 +28,10 @@ from home_assistant import ( from netdata import get_hdd_temps +# Number of views +NUM_VIEWS = 3 + + def format_temp(value): """Format temperature to one decimal place.""" try: @@ -61,6 +70,8 @@ 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 @@ -68,6 +79,41 @@ class Matrix64Display(SampleBase): 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 + + # 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 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 + print(f"Auto-cycle: {self.auto_cycle}") def update_data(self): try: @@ -95,6 +141,97 @@ class Matrix64Display(SampleBase): def update_hdd_temps(self): self.hdd_temps = get_hdd_temps() + 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 + draw_weather_icon(canvas, 0, 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.""" + time_font, data_font, small_font = fonts + time_color, humidity_color, hdd_color, label_color, line_color = colors + + # Title + graphics.DrawText(canvas, data_font, 8, 8, time_color, "HDD Temps") + graphics.DrawLine(canvas, 0, 10, 63, 10, line_color) + + if self.hdd_temps and len(self.hdd_temps) > 1: + y = 22 + for i, t in enumerate(self.hdd_temps[1:7]): + if t: + temp_val = int(t[0]) + temp_color = get_temperature_color(temp_val) + graphics.DrawText(canvas, data_font, 4, y, label_color, f"HDD{i+1}") + graphics.DrawText(canvas, data_font, 36, y, temp_color, f"{temp_val}C") + y += 10 + + def draw_view_2(self, canvas, fonts, colors): + """Date + Info view.""" + time_font, data_font, small_font = fonts + time_color, humidity_color, hdd_color, label_color, line_color = colors + + now = datetime.datetime.now() + + # Date + date_str = now.strftime("%d %b %Y") + day_str = now.strftime("%A") + + graphics.DrawText(canvas, time_font, 2, 16, time_color, date_str) + graphics.DrawText(canvas, data_font, 8, 28, label_color, day_str) + + graphics.DrawLine(canvas, 0, 32, 63, 32, line_color) + + # Weather summary + draw_weather_icon(canvas, 4, 36, self.weather_desc) + if self.temperature is not None: + temp_color = get_temperature_color(self.temperature) + graphics.DrawText(canvas, time_font, 32, 52, temp_color, f"{self.temperature:.0f}C") + + # Auto-cycle indicator + if self.auto_cycle: + graphics.DrawText(canvas, small_font, 56, 62, label_color, "A") + def run(self): canvas = self.matrix.CreateFrameCanvas() @@ -107,11 +244,16 @@ class Matrix64Display(SampleBase): 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) # Warm tan, easier to read + 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() @@ -119,67 +261,29 @@ class Matrix64Display(SampleBase): self.update_hdd_temps() while True: - now = datetime.datetime.now() - time_str = f"{now.hour:02d}:{now.minute:02d}:{now.second:02d}" 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 canvas.Clear() - # Line color for separators - line_color = graphics.Color(40, 40, 40) - - # === Weather Icon (top-left, 24x24) === - draw_weather_icon(canvas, 0, 0, self.weather_desc) - - # === Vertical separator between weather and HDD === - graphics.DrawLine(canvas, 28, 0, 28, 25, line_color) - - # === HDD Temps (top-right, 5x8 font, 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: - row1 = " ".join(temps[:2]) - graphics.DrawText(canvas, data_font, 34, 8, hdd_color, row1) - if len(temps) >= 4: - row2 = " ".join(temps[2:4]) - graphics.DrawText(canvas, data_font, 34, 16, hdd_color, row2) - if len(temps) > 4: - row3 = " ".join(temps[4:6]) - graphics.DrawText(canvas, data_font, 34, 24, hdd_color, row3) - - # === Horizontal line above clock === - graphics.DrawLine(canvas, 0, 26, 63, 26, line_color) - - # === Clock (centered) === - time_x = (64 - len(time_str) * 7) // 2 - graphics.DrawText(canvas, time_font, time_x, 39, time_color, time_str) - - # === Horizontal line below clock === - graphics.DrawLine(canvas, 0, 42, 63, 42, line_color) - - # === Outdoor: Out + temp + humidity (y=52) === - graphics.DrawText(canvas, data_font, 0, 52, label_color, "Out") - if self.temperature is not None: - temp_str = format_temp(self.temperature) - temp_color = get_temperature_color(self.temperature) - graphics.DrawText(canvas, data_font, 20, 52, temp_color, temp_str) - if self.humidity is not None: - graphics.DrawText(canvas, data_font, 48, 52, humidity_color, format_humidity(self.humidity)) - - # === Indoor: In + temp + humidity (y=62) === - graphics.DrawText(canvas, data_font, 0, 62, label_color, "In") - if self.interior_temperature is not None: - temp_str = format_temp(self.interior_temperature) - temp_color = get_temperature_color(self.interior_temperature) - graphics.DrawText(canvas, data_font, 20, 62, temp_color, temp_str) - if self.interior_humidity is not None: - graphics.DrawText(canvas, data_font, 48, 62, humidity_color, format_humidity(self.interior_humidity)) + # 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) time.sleep(0.5) canvas = self.matrix.SwapOnVSync(canvas) diff --git a/requirements.txt b/requirements.txt index 0ba7f26..f788a8a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ python-dotenv>=1.0.0 requests>=2.28.0 +gpiozero>=1.6.0 diff --git a/test_buttons.py b/test_buttons.py index 1bddabc..ae28208 100644 --- a/test_buttons.py +++ b/test_buttons.py @@ -1,62 +1,35 @@ #!/usr/bin/env python3 """ -GPIO Button Test Script -Run this on the Raspberry Pi to identify which GPIO pins the buttons are on. -Press Ctrl+C to exit. +GPIO Button Test Script using gpiozero """ +from gpiozero import Button import time +button1 = Button(0, pull_up=True) +button2 = Button(1, pull_up=True) + +print("Button test - press buttons to test") +print("Press Ctrl+C to exit\n") + +def on_button1_press(): + print(">>> Button 1 (GPIO 0) PRESSED <<<") + +def on_button1_release(): + print(" Button 1 (GPIO 0) released") + +def on_button2_press(): + print(">>> Button 2 (GPIO 1) PRESSED <<<") + +def on_button2_release(): + print(" Button 2 (GPIO 1) released") + +button1.when_pressed = on_button1_press +button1.when_released = on_button1_release +button2.when_pressed = on_button2_press +button2.when_released = on_button2_release + try: - import RPi.GPIO as GPIO -except ImportError: - print("RPi.GPIO not found. Install with: sudo apt install python3-rpi.gpio") - exit(1) - -# Common GPIO pins to test (BCM numbering) -# Excluding pins used by the LED matrix (typically 2-27 are used by the matrix) -# These are additional GPIO pins that might be available -TEST_PINS = [4, 5, 6, 12, 13, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27] - -def main(): - print("=" * 50) - print("GPIO Capacitive Button Test") - print("=" * 50) - print("\nSetting up GPIO pins for input with pull-up resistors...") - print("Press buttons to see which GPIO pin responds.") - print("Press Ctrl+C to exit.\n") - - GPIO.setmode(GPIO.BCM) - GPIO.setwarnings(False) - - # Set up all test pins as inputs with pull-up - active_pins = [] - for pin in TEST_PINS: - try: - GPIO.setup(pin, GPIO.IN, pull_up_down=GPIO.PUD_UP) - active_pins.append(pin) - except Exception as e: - print(f" Pin {pin}: Could not configure ({e})") - - print(f"Testing pins: {active_pins}\n") - - # Store previous states to detect changes - prev_states = {pin: GPIO.input(pin) for pin in active_pins} - - try: - while True: - for pin in active_pins: - current = GPIO.input(pin) - if current != prev_states[pin]: - if current == GPIO.LOW: - print(f">>> Button PRESSED on GPIO {pin} <<<") - else: - print(f" Button released on GPIO {pin}") - prev_states[pin] = current - time.sleep(0.05) - except KeyboardInterrupt: - print("\n\nTest complete.") - finally: - GPIO.cleanup() - -if __name__ == "__main__": - main() + while True: + time.sleep(0.1) +except KeyboardInterrupt: + print("\nTest complete.")