| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218 |
- # -*- coding: utf-8 -*-
- """
- WS2812/NeoPixel Hardware-Treiber.
- Abstrahiert den Zugriff auf die LED-Hardware via rpi-ws281x.
- Auf Systemen ohne GPIO (z.B. x86-Server) degradiert der Treiber
- graceful — alle Methoden werden zu No-Ops.
- """
- from __future__ import annotations
- from trixy_core.utils.debug import pdebug, pinfo, perror
- class WS2812Driver:
- """WS2812/NeoPixel Hardware-Treiber via rpi-ws281x.
- Graceful Degradation: Ohne rpi-ws281x (z.B. x86) gibt init() False
- zurueck. Alle Methoden sind dann No-Ops.
- Attributes:
- LED_FREQ_HZ: LED Signal-Frequenz in Hz.
- LED_DMA: DMA-Channel fuer die Datenuebertragung.
- LED_INVERT: Signal invertieren (bei Level-Shifter noetig).
- LED_CHANNEL: PWM-Channel (0 = GPIO 18, 1 = GPIO 13).
- """
- LED_FREQ_HZ: int = 800_000
- LED_DMA: int = 10
- LED_INVERT: bool = False
- LED_CHANNEL: int = 0
- def __init__(
- self,
- gpio_pin: int = 18,
- led_count: int = 1,
- brightness: int = 80,
- ) -> None:
- """Erstellt einen neuen WS2812-Treiber.
- Args:
- gpio_pin: GPIO-Pin fuer das Datensignal (Standard: 18/PWM0).
- led_count: Anzahl der LEDs in der Kette.
- brightness: Globale Helligkeit (0-255).
- """
- self._gpio_pin = gpio_pin
- self._led_count = led_count
- self._brightness = min(255, max(0, brightness))
- self._strip = None # rpi_ws281x.PixelStrip Instanz
- self._available = False
- @property
- def available(self) -> bool:
- """True wenn die LED-Hardware initialisiert und verfuegbar ist."""
- return self._available
- @property
- def led_count(self) -> int:
- """Anzahl der konfigurierten LEDs."""
- return self._led_count
- def init(self) -> bool:
- """Initialisiert die LED-Hardware.
- Versucht rpi_ws281x zu importieren und den LED-Strip zu
- initialisieren. Auf Nicht-Raspberry-Pi-Systemen schlaegt
- dies fehl und der Treiber arbeitet im No-Op-Modus weiter.
- Returns:
- True wenn die Hardware erfolgreich initialisiert wurde,
- False bei fehlendem Treiber oder GPIO-Zugriffsproblemen.
- """
- try:
- import rpi_ws281x # type: ignore[import-untyped]
- self._strip = rpi_ws281x.PixelStrip(
- self._led_count,
- self._gpio_pin,
- self.LED_FREQ_HZ,
- self.LED_DMA,
- self.LED_INVERT,
- self._brightness,
- self.LED_CHANNEL,
- )
- self._strip.begin()
- self._available = True
- pinfo(
- f"WS2812-Treiber initialisiert: {self._led_count} LEDs "
- f"auf GPIO {self._gpio_pin}, Helligkeit {self._brightness}"
- )
- return True
- except ImportError:
- pdebug(
- "rpi_ws281x nicht installiert — "
- "LED-Treiber arbeitet im No-Op-Modus"
- )
- self._available = False
- return False
- except RuntimeError as e:
- perror(
- f"LED-Hardware konnte nicht initialisiert werden: {e} — "
- f"GPIO-Zugriff erfordert Root-Rechte"
- )
- self._available = False
- return False
- except Exception as e:
- perror(f"Unerwarteter Fehler bei LED-Initialisierung: {e}")
- self._available = False
- return False
- def set_pixels(self, pixels: list[tuple[int, int, int]]) -> None:
- """Setzt alle LEDs auf die angegebenen Farben.
- Die Aenderungen werden erst mit show() an die Hardware
- uebertragen. Das erlaubt Batch-Updates ohne Flackern.
- Args:
- pixels: Liste von RGB-Tupeln, ein Tupel pro LED.
- Laenge muss mit led_count uebereinstimmen.
- """
- if not self._available or self._strip is None:
- return
- count = min(len(pixels), self._led_count)
- for i in range(count):
- r, g, b = pixels[i]
- self._strip.setPixelColor(i, self._rgb_to_color(r, g, b))
- def set_pixel(self, index: int, r: int, g: int, b: int) -> None:
- """Setzt eine einzelne LED auf die angegebene Farbe.
- Die Aenderung wird erst mit show() an die Hardware uebertragen.
- Args:
- index: LED-Index (0-basiert).
- r: Rot-Anteil (0-255).
- g: Gruen-Anteil (0-255).
- b: Blau-Anteil (0-255).
- """
- if not self._available or self._strip is None:
- return
- if 0 <= index < self._led_count:
- self._strip.setPixelColor(index, self._rgb_to_color(r, g, b))
- def show(self) -> None:
- """Uebertraegt die aktuellen Pixeldaten an die Hardware.
- Muss nach set_pixels() oder set_pixel() aufgerufen werden,
- damit die Aenderungen sichtbar werden.
- """
- if not self._available or self._strip is None:
- return
- try:
- self._strip.show()
- except Exception as e:
- perror(f"Fehler beim LED-Update: {e}")
- def clear(self) -> None:
- """Schaltet alle LEDs aus (schwarz).
- Setzt alle Pixel auf (0, 0, 0) und uebertraegt sofort.
- """
- if not self._available or self._strip is None:
- return
- for i in range(self._led_count):
- self._strip.setPixelColor(i, 0)
- self._strip.show()
- def set_brightness(self, value: int) -> None:
- """Setzt die globale Helligkeit.
- Args:
- value: Helligkeit von 0 (aus) bis 255 (maximum).
- """
- self._brightness = min(255, max(0, value))
- if not self._available or self._strip is None:
- return
- self._strip.setBrightness(self._brightness)
- self._strip.show()
- pdebug(f"LED-Helligkeit auf {self._brightness} gesetzt")
- def cleanup(self) -> None:
- """Gibt die LED-Hardware frei und schaltet alle LEDs aus.
- Sollte beim Beenden der Anwendung aufgerufen werden.
- """
- if not self._available or self._strip is None:
- pdebug("LED-Treiber Cleanup — keine Hardware aktiv")
- return
- pdebug("LED-Treiber Cleanup — LEDs werden ausgeschaltet")
- self.clear()
- self._available = False
- self._strip = None
- @staticmethod
- def _rgb_to_color(r: int, g: int, b: int) -> int:
- """Konvertiert RGB-Tupel in den von rpi_ws281x erwarteten Integer.
- rpi_ws281x erwartet Farben als 24-Bit Integer im Format 0xRRGGBB.
- Args:
- r: Rot-Anteil (0-255).
- g: Gruen-Anteil (0-255).
- b: Blau-Anteil (0-255).
- Returns:
- Farbwert als 24-Bit Integer.
- """
- return (int(r) & 0xFF) << 16 | (int(g) & 0xFF) << 8 | (int(b) & 0xFF)
|