Multi-view pagination with GPIO buttons

This commit is contained in:
Joan
2025-12-31 13:06:17 +01:00
parent bfb2290634
commit 6364f63f15
3 changed files with 193 additions and 115 deletions

222
matrix.py
View File

@@ -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)