from machine import Pin, RTC from neopixel import NeoPixel from .fonts.fonts_utils import char_width as charwidth from .fonts.font_5x7 import font_5x7 from ..utils.colors import GRAY, RAINBOW, BLACK, WHITE, LIME from ..utils.utils import ( get_german_timestamp_short, get_datetime_string, get_german_time_ticks, get_german_date_ticks, number_to_bitarray_msb, ) import time import math class NeoPixel_64x64(NeoPixel): MATRIX_WIDTH = 64 MATRIX_HEIGHT = 64 def __init__(self, pin=28): """ Initialize LED Display Args: width: Matrix width in pixels height: Matrix height in pixels pin: NeoPixel data pin """ super().__init__(Pin(pin), self.MATRIX_WIDTH * self.MATRIX_HEIGHT) # Font configuration self.set_font(font_5x7) def set_font(self, font): """ Set the current font for text rendering Args: font: Font dictionary from fonts_array """ self.selected_font = font # Höhe des Fonts, Anzahl der Zeilen für jedes Zeichen # wir holen den ersten Wert des Fonts first_char = next(iter(self.selected_font)) self.font_height = len(self.selected_font[first_char]) print(f'Font set: width: per char; height: {self.font_height}') def set_pixel(self, x, y, color): """ Set a single pixel to the specified color Args: x: X coordinate y: Y coordinate color: RGB color tuple """ index = y * self.MATRIX_WIDTH + x if 0 <= x < self.MATRIX_WIDTH and 0 <= y < self.MATRIX_HEIGHT: self[index] = color def clear(self): """Clear the entire display (turn off all pixels)""" for i in range(len(self)): self[i] = BLACK self.write() def clear_row(self, row: int, effect: bool = False) -> None: """ Clear one row of the display (turn off all pixels)\n PRESUMING: font_5x7\n rows: range(0,3) [row-height: 8 pixels, row-length: MATRIX_WIDTH(64)]\n \n row-0: 0 - 511 (Pixel 0 bis Pixel 8 * MATRIX_WIDTH(64)-1)\n row-1: 512 - 1023\n row-2: 1024 - 1535\n row-3: 1536 - 2047\n etc. ... """ start = row * 8 * self.MATRIX_WIDTH ende = start + 8 * self.MATRIX_WIDTH - 1 print(f'clear row: {row} --> pixels {start} to {ende}') for i in range(start, ende): self[i] = BLACK if effect and i % self.MATRIX_WIDTH == 0: self.write() self.write() def draw_letter(self, letter, x, y, color): """ Draw a single letter using current font with optimized width Args: letter: Character to draw x: X position y: Y position color: RGB color tuple """ if letter in self.selected_font: char_data = self.selected_font[letter] char_width = charwidth(char_data) # background for the letter (full font size) [ # print(xpos, ypos) self.set_pixel(xpos, ypos, GRAY) for xpos in range(x, x + char_width) # 8 because full with of character representation for ypos in range(y, y + self.font_height) ] for row in range(self.font_height): row_data = char_data[row] for col in range(char_width): # Check if pixel should be lit (MSB first) # Only check bits within the actual character width if row_data & (1 << ((char_width - 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): """ Draw text with optimized character spacing Args: text: Text to draw 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 char_width = charwidth(self.selected_font[char]) current_x += char_width + 1 def show_hello(self): """Display HELLO with timestamp""" self.clear() # Draw HELLO in rainbow colors self.draw_text('HELLO!', 6, 4, RAINBOW[2]) # Show timestamp datetimestr = get_german_timestamp_short() self.draw_text(datetimestr, 2, 15, RAINBOW[4]) self.write() def vertical_floating_text(self, text, x, color=RAINBOW[0], float_range=3, speed=0.2, duration=10): """ Vertical floating text animation Args: text: Text to display x: X position (fixed) 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: # 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() # Draw each letter vertically for i, char in enumerate(text): char_y = current_y + (i * self.font_height) # Keep text within matrix bounds if 0 <= char_y < self.MATRIX_HEIGHT - self.font_height: self.draw_letter(char, x, char_y, color) self.write() time.sleep(0.05) def horizontal_floating_text(self, text, y, color=RAINBOW[0], float_range=3, speed=0.2, duration=10): """ Horizontal floating text animation Args: text: Text to display y: Y position (fixed) color: Text color float_range: How many pixels to float left/right speed: Animation speed duration: How long to run animation in seconds """ start_time = time.time() counter = 0 while time.time() - start_time < duration: # Calculate floating offset using sine wave offset = math.sin(counter) * float_range current_x = int(offset) # to right self.clear() # Draw text at floating position for i, char in enumerate(text): char_width = charwidth(self.selected_font[char]) char_x = current_x + (i * (char_width + 1)) # Keep text within matrix bounds if 0 <= char_x < self.MATRIX_WIDTH - char_width: self.draw_letter(char, char_x, y, color) self.write() counter += speed time.sleep(0.05) def rotate_text_left_continuous(self, text, y, color=RAINBOW[0], speed=1.0, duration=10): """ Continuous left rotation - text wraps around seamlessly Args: text: Text to display y: Y position (fixed) color: Text color speed: Movement speed duration: How long to run animation in seconds """ 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([charwidth(self.selected_font[char]) for char in text]) position = 0 while time.time() - start_time < duration: self.clear() # Draw text at current position for i, char in enumerate(text): char_width = charwidth(self.selected_font[char]) char_x = int(position + (i * (char_width + 1))) # Handle wrapping if char_x < -char_width: char_x += self.MATRIX_WIDTH + text_width_overall if 0 <= char_x < self.MATRIX_WIDTH: self.draw_letter(char, char_x, y, color) self.write() # Move left and wrap around position -= speed if position < -text_width_overall: position = self.MATRIX_WIDTH time.sleep(0.05) def draw_rectangle(self, x, y, width, height, color, fill=False): """ Draw a rectangle Args: x: Top-left X position y: Top-left Y position width: Rectangle 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): """ Draw a line using Bresenham's algorithm Args: x1, y1: Start coordinates x2, y2: End coordinates 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=WHITE) -> None: self.draw_text(text, xpos, ypos, color) # Pixel setzen self.write() # und anzeigen # Example usage if __name__ == '__main__': # Create display instance display = NeoPixel_64x64() print('LED Matrix Display Initialized') print(get_datetime_string()) print(f'German time: {get_german_time_ticks()}') print(f'German date: {get_german_date_ticks()}') # Demo various functions display.show_hello() time.sleep(3) display.vertical_floating_text('HELLO', 30, RAINBOW[0], 5, 0.15, 5) display.horizontal_floating_text('FLOAT', 28, RAINBOW[1], 10, 0.1, 5) display.rotate_text_left_continuous('ROTATE FLOAT', 28, speed=8.0, duration=5) # Draw some shapes display.clear() display.draw_rectangle(5, 5, 10, 10, RAINBOW[0]) display.draw_rectangle(20, 20, 15, 8, RAINBOW[2], fill=True) display.draw_line(0, 0, 63, 63, RAINBOW[4]) display.write()