v.0.8.3 display clear_cox

This commit is contained in:
tiijay
2025-11-19 10:18:43 +00:00
parent 2be6d888b3
commit 72cd1bbe1b
2 changed files with 344 additions and 314 deletions

View File

@@ -1,390 +1,418 @@
from machine import Pin # type: ignore from machine import Pin # type: ignore
from neopixel import NeoPixel # type: ignore from neopixel import NeoPixel # type: ignore
from ..utils import char_width from app.utils import char_width
from .fonts.font_5x7 import font_5x7 from app.display import fonts
from ..utils.colors import GRAY, RAINBOW, BLACK, WHITE from app.utils import colors
from ..utils.time_utils import ( from app.utils.time_utils import (
get_german_timestamp_short, get_german_timestamp_short,
get_datetime_string, get_datetime_string,
get_german_time_ticks, get_german_time_ticks,
get_german_date_ticks, get_german_date_ticks,
) )
import time import time
import math import math
class NeoPixel_64x64(NeoPixel): class NeoPixel_64x64(NeoPixel):
MATRIX_WIDTH = 64 MATRIX_WIDTH = 64
MATRIX_HEIGHT = 64 MATRIX_HEIGHT = 64
def __init__(self, pin=28): def __init__(self, pin=28):
""" """
Initialize LED Display Initialize LED Display
Args: Args:
width: Matrix width in pixels width: Matrix width in pixels
height: Matrix height in pixels height: Matrix height in pixels
pin: NeoPixel data pin pin: NeoPixel data pin
""" """
super().__init__(Pin(pin), self.MATRIX_WIDTH * self.MATRIX_HEIGHT) super().__init__(Pin(pin), self.MATRIX_WIDTH * self.MATRIX_HEIGHT)
# Font configuration # Font configuration
self.set_font(font_5x7) self.set_font(fonts.font_5x7)
def set_font(self, font): def set_font(self, font):
""" """
Set the current font for text rendering Set the current font for text rendering
Args: Args:
font: Font dictionary from fonts_array font: Font dictionary from fonts_array
""" """
self.selected_font = font self.selected_font = font
# Höhe des Fonts, Anzahl der Zeilen für jedes Zeichen # Höhe des Fonts, Anzahl der Zeilen für jedes Zeichen
# wir holen den ersten Wert des Fonts # wir holen den ersten Wert des Fonts
first_char = next(iter(self.selected_font)) first_char = next(iter(self.selected_font))
self.font_height = len(self.selected_font[first_char]) self.font_height = len(self.selected_font[first_char])
def set_pixel(self, x, y, color): def set_pixel(self, x, y, color):
""" """
Set a single pixel to the specified color Set a single pixel to the specified color
Args: Args:
x: X coordinate x: X coordinate
y: Y coordinate y: Y coordinate
color: RGB color tuple color: RGB color tuple
""" """
index = y * self.MATRIX_WIDTH + x index = y * self.MATRIX_WIDTH + x
if 0 <= x < self.MATRIX_WIDTH and 0 <= y < self.MATRIX_HEIGHT: if 0 <= x < self.MATRIX_WIDTH and 0 <= y < self.MATRIX_HEIGHT:
self[index] = color self[index] = color
def clear(self): def clear(self):
"""Clear the entire display (turn off all pixels)""" """Clear the entire display (turn off all pixels)"""
for i in range(len(self)): for i in range(len(self)):
self[i] = BLACK self[i] = colors.BLACK
self.write() self.write()
def clear_row(self, row: int, effect: bool = False) -> None: def clear_box(
"""löscht eine Zeile im Display entsprechend des eingestellten Fonts self,
from_row: int,
from_col: int,
to_row: int,
to_col: int,
color: tuple[3] = colors.BLACK,
) -> None:
"""löscht einen Bereich (Box) im Display
Args: Args:
row (int): in Pixel start bei 0 !!! from_row (int): Start-Zeile
effect (bool, optional): zeilenweise bzw. pixelweise löschen. Defaults to False. from_col (int): Start-Spalte
""" to_row (int): End-Zeile
start = row * self.MATRIX_WIDTH to_col (int): End-Spalte
ende = start + self.font_height * self.MATRIX_WIDTH - 1 """
for row in range(from_row, to_row):
for col in range(from_col, to_col + 1):
idx: int = row * self.MATRIX_WIDTH + col
self[idx] = color
print(f'clear row: {row} --> pixels {start} to {ende}') self.write()
for i in range(start, ende):
self[i] = BLACK
if effect and i % self.MATRIX_WIDTH == 0:
self.write()
self.write() def clear_row(self, row: int, effect: bool = False) -> None:
"""löscht eine Zeile im Display entsprechend des eingestellten Fonts
def draw_letter(self, letter, x, y, color): Args:
""" row (int): in Pixel start bei 0 !!!
Draw a single letter using current font with optimized width effect (bool, optional): zeilenweise bzw. pixelweise löschen. Defaults to False.
"""
start = row * self.MATRIX_WIDTH
ende = start + self.font_height * self.MATRIX_WIDTH - 1
Args: print(f"clear row: {row} --> pixels {start} to {ende}")
letter: Character to draw for i in range(start, ende):
x: X position self[i] = colors.BLACK
y: Y position if effect and i % self.MATRIX_WIDTH == 0:
color: RGB color tuple self.write()
"""
if letter in self.selected_font: self.write()
char_data = self.selected_font[letter]
charwidth = char_width(char_data)
# background for the letter (full font size) def draw_letter(self, letter, x, y, color):
[ """
# print(xpos, ypos) Draw a single letter using current font with optimized width
self.set_pixel(xpos, ypos, GRAY)
for xpos in range(
x, x + charwidth
) # 8 because full with of character representation
for ypos in range(y, y + self.font_height)
]
for row in range(self.font_height): Args:
row_data = char_data[row] letter: Character to draw
x: X position
y: Y position
color: RGB color tuple
"""
for col in range(charwidth): if letter in self.selected_font:
# Check if pixel should be lit (MSB first) char_data = self.selected_font[letter]
# Only check bits within the actual character width charwidth = char_width(char_data)
if row_data & (1 << ((charwidth - 1) - col)):
self.set_pixel(x + col, y + row, color)
else:
print(f'oops, letter does not exist in the font -> {letter}')
def draw_text(self, text, x, y, color): # background for the letter (full font size)
""" [
Draw text with optimized character spacing # print(xpos, ypos)
self.set_pixel(xpos, ypos, colors.GRAY)
for xpos in range(
x, x + charwidth
) # 8 because full with of character representation
for ypos in range(y, y + self.font_height)
]
Args: for row in range(self.font_height):
text: Text to draw row_data = char_data[row]
x: Starting X position
y: Starting Y position
color: RGB color tuple
"""
current_x = x
for char in text:
self.draw_letter(char, current_x, y, color)
# Move cursor by character width + 1 pixel spacing
charwidth = char_width(self.selected_font[char])
current_x += charwidth + 1
def show_hello(self): for col in range(charwidth):
"""Display HELLO with timestamp""" # Check if pixel should be lit (MSB first)
self.clear() # Only check bits within the actual character width
if row_data & (1 << ((charwidth - 1) - col)):
self.set_pixel(x + col, y + row, color)
else:
print(f"oops, letter does not exist in the font -> {letter}")
# Draw HELLO in rainbow colors def draw_text(self, text, x, y, color):
self.draw_text('HELLO!', 6, 4, RAINBOW[2]) """
Draw text with optimized character spacing
# Show timestamp Args:
datetimestr = get_german_timestamp_short() text: Text to draw
self.draw_text(datetimestr, 2, 15, RAINBOW[4]) x: Starting X position
y: Starting Y position
color: RGB color tuple
"""
current_x = x
for char in text:
self.draw_letter(char, current_x, y, color)
# Move cursor by character width + 1 pixel spacing
charwidth = char_width(self.selected_font[char])
current_x += charwidth + 1
self.write() def show_hello(self):
"""Display HELLO with timestamp"""
self.clear()
def vertical_floating_text( # Draw HELLO in rainbow colors
self, text, x, color=RAINBOW[0], float_range=3, speed=0.2, duration=10 self.draw_text("HELLO!", 6, 4, colors.RAINBOW[2])
):
"""
Vertical floating text animation
Args: # Show timestamp
text: Text to display datetimestr = get_german_timestamp_short()
x: X position (fixed) self.draw_text(datetimestr, 2, 15, colors.RAINBOW[4])
color: Text color
float_range: How many pixels to float up/down
speed: Animation speed
duration: How long to run animation in seconds
"""
start_time = time.time()
while time.time() - start_time < duration: self.write()
# Calculate floating offset using sine wave
offset = math.sin(time.time() * speed) * float_range
current_y = int(self.MATRIX_HEIGHT // 2 + offset - (len(text) * self.font_height) // 2)
self.clear() def vertical_floating_text(
self, text, x, color=colors.RAINBOW[0], float_range=3, speed=0.2, duration=10
):
"""
Vertical floating text animation
# Draw each letter vertically Args:
for i, char in enumerate(text): text: Text to display
char_y = current_y + (i * self.font_height) x: X position (fixed)
# Keep text within matrix bounds color: Text color
if 0 <= char_y < self.MATRIX_HEIGHT - self.font_height: float_range: How many pixels to float up/down
self.draw_letter(char, x, char_y, color) speed: Animation speed
duration: How long to run animation in seconds
"""
start_time = time.time()
self.write() while time.time() - start_time < duration:
time.sleep(0.05) # Calculate floating offset using sine wave
offset = math.sin(time.time() * speed) * float_range
current_y = int(
self.MATRIX_HEIGHT // 2 + offset - (len(text) * self.font_height) // 2
)
def horizontal_floating_text( self.clear()
self, text, y, color=RAINBOW[0], float_range=3, speed=0.2, duration=10
):
"""
Horizontal floating text animation
Args: # Draw each letter vertically
text: Text to display for i, char in enumerate(text):
y: Y position (fixed) char_y = current_y + (i * self.font_height)
color: Text color # Keep text within matrix bounds
float_range: How many pixels to float left/right if 0 <= char_y < self.MATRIX_HEIGHT - self.font_height:
speed: Animation speed self.draw_letter(char, x, char_y, color)
duration: How long to run animation in seconds
"""
start_time = time.time()
counter = 0
while time.time() - start_time < duration: self.write()
# Calculate floating offset using sine wave time.sleep(0.05)
offset = math.sin(counter) * float_range
current_x = int(offset) # to right
self.clear() def horizontal_floating_text(
self, text, y, color=colors.RAINBOW[0], float_range=3, speed=0.2, duration=10
):
"""
Horizontal floating text animation
# Draw text at floating position Args:
for i, char in enumerate(text): text: Text to display
charwidth = char_width(self.selected_font[char]) y: Y position (fixed)
char_x = current_x + (i * (charwidth + 1)) color: Text color
# Keep text within matrix bounds float_range: How many pixels to float left/right
if 0 <= char_x < self.MATRIX_WIDTH - charwidth: speed: Animation speed
self.draw_letter(char, char_x, y, color) duration: How long to run animation in seconds
"""
start_time = time.time()
counter = 0
self.write() while time.time() - start_time < duration:
counter += speed # Calculate floating offset using sine wave
time.sleep(0.05) offset = math.sin(counter) * float_range
current_x = int(offset) # to right
def rotate_text_left_continuous(self, text, y, color=RAINBOW[0], speed=1.0, duration=10): self.clear()
"""
Continuous left rotation - text wraps around seamlessly
Args: # Draw text at floating position
text: Text to display for i, char in enumerate(text):
y: Y position (fixed) charwidth = char_width(self.selected_font[char])
color: Text color char_x = current_x + (i * (charwidth + 1))
speed: Movement speed # Keep text within matrix bounds
duration: How long to run animation in seconds if 0 <= char_x < self.MATRIX_WIDTH - charwidth:
""" self.draw_letter(char, char_x, y, color)
start_time = time.time()
# hier müssen wir die Länge/Breite in Pixel des gesamten Textes berechnen
# text_width_overall = len(text) * (self.font_width + 1)
### TODO: ACHTUNG: noch zu testen !!!
# Breite jedes Zeichens
text_width_overall = sum([char_width(self.selected_font[char]) for char in text])
position = 0 self.write()
counter += speed
time.sleep(0.05)
while time.time() - start_time < duration: def rotate_text_left_continuous(
self.clear() self, text, y, color=colors.RAINBOW[0], speed=1.0, duration=10
):
"""
Continuous left rotation - text wraps around seamlessly
# Draw text at current position Args:
for i, char in enumerate(text): text: Text to display
charwidth = char_width(self.selected_font[char]) y: Y position (fixed)
char_x = int(position + (i * (charwidth + 1))) color: Text color
# Handle wrapping speed: Movement speed
if char_x < -charwidth: duration: How long to run animation in seconds
char_x += self.MATRIX_WIDTH + text_width_overall """
if 0 <= char_x < self.MATRIX_WIDTH: start_time = time.time()
self.draw_letter(char, char_x, y, color) # hier müssen wir die Länge/Breite in Pixel des gesamten Textes berechnen
# text_width_overall = len(text) * (self.font_width + 1)
### TODO: ACHTUNG: noch zu testen !!!
# Breite jedes Zeichens
text_width_overall = sum(
[char_width(self.selected_font[char]) for char in text]
)
self.write() position = 0
# Move left and wrap around while time.time() - start_time < duration:
position -= speed self.clear()
if position < -text_width_overall:
position = self.MATRIX_WIDTH
time.sleep(0.05) # Draw text at current position
for i, char in enumerate(text):
charwidth = char_width(self.selected_font[char])
char_x = int(position + (i * (charwidth + 1)))
# Handle wrapping
if char_x < -charwidth:
char_x += self.MATRIX_WIDTH + text_width_overall
if 0 <= char_x < self.MATRIX_WIDTH:
self.draw_letter(char, char_x, y, color)
def draw_rectangle(self, x, y, width, height, color, fill=False): self.write()
"""
Draw a rectangle
Args: # Move left and wrap around
x: Top-left X position position -= speed
y: Top-left Y position if position < -text_width_overall:
width: Rectangle width position = self.MATRIX_WIDTH
height: Rectangle height
color: RGB color tuple
fill: Whether to fill the rectangle
"""
if fill:
for i in range(height):
for j in range(width):
self.set_pixel(x + j, y + i, color)
else:
# Top and bottom edges
for i in range(width):
self.set_pixel(x + i, y, color)
self.set_pixel(x + i, y + height - 1, color)
# Left and right edges
for i in range(height):
self.set_pixel(x, y + i, color)
self.set_pixel(x + width - 1, y + i, color)
def draw_line(self, x1, y1, x2, y2, color): time.sleep(0.05)
"""
Draw a line using Bresenham's algorithm
Args: def draw_rectangle(self, x, y, width, height, color, fill=False):
x1, y1: Start coordinates """
x2, y2: End coordinates Draw a rectangle
color: RGB color tuple
"""
dx = abs(x2 - x1)
dy = abs(y2 - y1)
sx = 1 if x1 < x2 else -1
sy = 1 if y1 < y2 else -1
err = dx - dy
while True: Args:
self.set_pixel(x1, y1, color) x: Top-left X position
if x1 == x2 and y1 == y2: y: Top-left Y position
break width: Rectangle width
e2 = 2 * err height: Rectangle height
if e2 > -dy: color: RGB color tuple
err -= dy fill: Whether to fill the rectangle
x1 += sx """
if e2 < dx: if fill:
err += dx for i in range(height):
y1 += sy for j in range(width):
self.set_pixel(x + j, y + i, color)
else:
# Top and bottom edges
for i in range(width):
self.set_pixel(x + i, y, color)
self.set_pixel(x + i, y + height - 1, color)
# Left and right edges
for i in range(height):
self.set_pixel(x, y + i, color)
self.set_pixel(x + width - 1, y + i, color)
def write_text(self, text: str, xpos: int, ypos: int, color=WHITE) -> None: def draw_line(self, x1, y1, x2, y2, color):
self.draw_text(text, xpos, ypos, color) # Pixel setzen """
self.write() # und anzeigen Draw a line using Bresenham's algorithm
def screen_text(self, text: str): Args:
"""Text für einen Screen anpassen, x1, y1: Start coordinates
Anzahl der Zeichen je Zeile begrenzen, x2, y2: End coordinates
ebenso wird die Höhe des Screen brücksichtigt color: RGB color tuple
"""
dx = abs(x2 - x1)
dy = abs(y2 - y1)
sx = 1 if x1 < x2 else -1
sy = 1 if y1 < y2 else -1
err = dx - dy
while True:
self.set_pixel(x1, y1, color)
if x1 == x2 and y1 == y2:
break
e2 = 2 * err
if e2 > -dy:
err -= dy
x1 += sx
if e2 < dx:
err += dx
y1 += sy
def write_text(self, text: str, xpos: int, ypos: int, color=colors.WHITE) -> None:
self.draw_text(text, xpos, ypos, color) # Pixel setzen
self.write() # und anzeigen
def screen_text(self, text: str):
"""Text für einen Screen anpassen,
Anzahl der Zeichen je Zeile begrenzen,
ebenso wird die Höhe des Screen brücksichtigt
Args: Args:
text (str): Text der ausgegeben werden soll text (str): Text der ausgegeben werden soll
font: Berechnungen für den Font font: Berechnungen für den Font
height (int): Pixel height (int): Pixel
width (int): Pixel width (int): Pixel
""" """
def text_per_row(txt): def text_per_row(txt):
pixs = 0 pixs = 0
visible_text = '' visible_text = ""
for a in txt: for a in txt:
pixs += char_width(self.selected_font[a]) + 1 pixs += char_width(self.selected_font[a]) + 1
if pixs > self.MATRIX_WIDTH: if pixs > self.MATRIX_WIDTH:
# Zeilenende erreicht # Zeilenende erreicht
break break
visible_text += a visible_text += a
return visible_text return visible_text
# Ganzzahl Division # Ganzzahl Division
max_visible_rows = self.MATRIX_HEIGHT // self.font_height max_visible_rows = self.MATRIX_HEIGHT // self.font_height
print(f'rows_visible: {max_visible_rows}') print(f"rows_visible: {max_visible_rows}")
text_left = text text_left = text
scn_txt = [] scn_txt = []
for _ in range(max_visible_rows): for _ in range(max_visible_rows):
visible_text = text_per_row(text_left) visible_text = text_per_row(text_left)
visible_text_len = len(visible_text) visible_text_len = len(visible_text)
text_left = text_left[visible_text_len:] text_left = text_left[visible_text_len:]
scn_txt.append(visible_text) scn_txt.append(visible_text)
if not text_left: if not text_left:
break break
scr_txt_dict = {'visible': scn_txt, 'invisible': text_left} scr_txt_dict = {"visible": scn_txt, "invisible": text_left}
return scr_txt_dict
return scr_txt_dict
# Example usage # Example usage
if __name__ == '__main__': if __name__ == "__main__":
# Create display instance # Create display instance
display = NeoPixel_64x64() display = NeoPixel_64x64()
print('LED Matrix Display Initialized') print("LED Matrix Display Initialized")
print(get_datetime_string()) print(get_datetime_string())
print(f'German time: {get_german_time_ticks()}') print(f"German time: {get_german_time_ticks()}")
print(f'German date: {get_german_date_ticks()}') print(f"German date: {get_german_date_ticks()}")
# Demo various functions # Demo various functions
display.show_hello() display.show_hello()
time.sleep(3) time.sleep(3)
display.vertical_floating_text('HELLO', 30, RAINBOW[0], 5, 0.15, 5) display.vertical_floating_text("HELLO", 30, colors.RAINBOW[0], 5, 0.15, 5)
display.horizontal_floating_text('FLOAT', 28, RAINBOW[1], 10, 0.1, 5) display.horizontal_floating_text("FLOAT", 28, colors.RAINBOW[1], 10, 0.1, 5)
display.rotate_text_left_continuous('ROTATE FLOAT', 28, speed=8.0, duration=5) display.rotate_text_left_continuous("ROTATE FLOAT", 28, speed=8.0, duration=5)
# Draw some shapes # Draw some shapes
display.clear() display.clear()
display.draw_rectangle(5, 5, 10, 10, RAINBOW[0]) display.draw_rectangle(5, 5, 10, 10, colors.RAINBOW[0])
display.draw_rectangle(20, 20, 15, 8, RAINBOW[2], fill=True) display.draw_rectangle(20, 20, 15, 8, colors.RAINBOW[2], fill=True)
display.draw_line(0, 0, 63, 63, RAINBOW[4]) display.draw_line(0, 0, 63, 63, colors.RAINBOW[4])
display.write() display.write()

View File

@@ -35,10 +35,12 @@ async def print_time_task() -> None:
bottom_ypos = display.MATRIX_HEIGHT - display.font_height bottom_ypos = display.MATRIX_HEIGHT - display.font_height
time_str: str = get_datetime_string("time") time_str: str = get_datetime_string("time")
display.clear_row(bottom_ypos) display.clear_box(bottom_ypos, 0, bottom_ypos + display.font_height, 38)
# display.clear_row(bottom_ypos)
display.write_text(time_str[:8], 0, bottom_ypos, color=colors.NEON_YELLOW) display.write_text(time_str[:8], 0, bottom_ypos, color=colors.NEON_YELLOW)
await asyncio.sleep(10) await asyncio.sleep(5)
async def sync_ntp_time_task() -> None: async def sync_ntp_time_task() -> None: