"""GPIO/SPI-Unterbau fuer die vendored Waveshare-Panel-Treiber (epd2in13_V2.py, epd2in13_V3.py), geschrieben gegen gpiozero + spidev statt gegen Waveshares eigenes epdconfig.py, das RPi.GPIO voraussetzt - ein Modul, das auf Debian 13 (trixie) mit Python 3.13 nicht mehr baut/importiert. gpiozero, lgpio (gpiozero's Pin-Factory auf trixie) und spidev sind auf dem Ziel-Pi vorhanden. Stellt genau die Schnittstelle bereit, die die beiden Treiber erwarten: die Pin-Konstanten RST_PIN/DC_PIN/CS_PIN/BUSY_PIN sowie digital_write, digital_read, delay_ms, spi_writebyte, spi_writebyte2, module_init und module_exit als Modulfunktionen (kein Klassen-Wrapper - die Treiber greifen direkt auf `epdconfig.` zu). CS_PIN wird NICHT als GPIO geschaltet: spidev.SpiDev(0, 0) steuert die Hardware-CS0-Leitung selbst bei jeder Übertragung, ein zusaetzliches Software-Toggle wuerde nur mit der Hardware um die Leitung konkurrieren. digital_write()/digital_read() sind fuer CS_PIN deshalb No-ops - genau das Muster, das Waveshares eigene, neuere epdconfig.py-Fassung fuer Raspberry Pi inzwischen ebenfalls verwendet (dort ist die GPIO-Instanziierung fuer CS_PIN auskommentiert). Ein fehlender Import (gpiozero/spidev nicht installiert) oder ein fehlendes /dev/spidev0.0 wirft DisplayUnavailable - display.py faengt das ab, protokolliert eine Zeile und beendet sich sauber (sys.exit(0)), statt dass ein fehlendes Panel eine Migration verhindern koennte (siehe Modul-Docstring von display.py).""" from __future__ import annotations import os import time try: import gpiozero except ImportError: # pragma: no cover - auf dem Pi immer vorhanden, hier nur defensiv gpiozero = None try: import spidev except ImportError: # pragma: no cover - dito spidev = None class DisplayUnavailable(Exception): """Kein Panel ansprechbar: Bibliothek fehlt, /dev/spidev0.0 fehlt, oder der Hardwarezugriff selbst ist fehlgeschlagen (z.B. Berechtigung, Pin belegt).""" # Dieselben Pin-Nummern wie in Waveshares eigenem epdconfig.py fuer den Raspberry Pi. RST_PIN = 17 DC_PIN = 25 CS_PIN = 8 BUSY_PIN = 24 _SPI_BUS = 0 _SPI_DEVICE = 0 _SPI_HZ = 4_000_000 _rst_pin = None _dc_pin = None _busy_pin = None _spi = None def digital_write(pin: int, value: int) -> None: if pin == RST_PIN: _rst_pin.value = bool(value) elif pin == DC_PIN: _dc_pin.value = bool(value) elif pin == CS_PIN: pass # Hardware-CS ueber spidev, siehe Modul-Docstring. # Andere Pins fragen die Treiber nicht ab - kein else-Zweig noetig. def digital_read(pin: int) -> int: if pin == BUSY_PIN: return int(bool(_busy_pin.value)) if pin == RST_PIN: return int(bool(_rst_pin.value)) if pin == DC_PIN: return int(bool(_dc_pin.value)) return 0 def delay_ms(ms: int) -> None: time.sleep(ms / 1000.0) def spi_writebyte(data) -> None: _spi.writebytes(data) def spi_writebyte2(data) -> None: _spi.writebytes2(data) def module_init() -> int: """Oeffnet GPIO-Pins und SPI-Geraet. Wirft DisplayUnavailable statt irgendeiner Bibliotheksausnahme, damit display.py genau eine Ausnahmeklasse abfangen muss.""" global _rst_pin, _dc_pin, _busy_pin, _spi if gpiozero is None or spidev is None: raise DisplayUnavailable("gpiozero/spidev nicht installiert") spi_path = f"/dev/spidev{_SPI_BUS}.{_SPI_DEVICE}" if not os.path.exists(spi_path): raise DisplayUnavailable(f"{spi_path} fehlt - ist SPI aktiviert (raspi-config)?") try: _rst_pin = gpiozero.DigitalOutputDevice(RST_PIN) _dc_pin = gpiozero.DigitalOutputDevice(DC_PIN) _busy_pin = gpiozero.DigitalInputDevice(BUSY_PIN, pull_up=False) _spi = spidev.SpiDev() _spi.open(_SPI_BUS, _SPI_DEVICE) _spi.max_speed_hz = _SPI_HZ _spi.mode = 0b00 except Exception as e: # noqa: BLE001 - alles hier wird zu DisplayUnavailable module_exit() raise DisplayUnavailable(f"GPIO/SPI-Initialisierung fehlgeschlagen: {e}") from e return 0 def module_exit() -> None: """Gibt GPIO-Pins und SPI-Geraet frei. Verzeiht, wenn module_init() nie (vollstaendig) gelaufen ist - display.py ruft das u.a. im Fehlerpfad von module_init() selbst auf.""" global _rst_pin, _dc_pin, _busy_pin, _spi if _spi is not None: try: _spi.close() except Exception: # noqa: BLE001 - Aufraeumen darf nicht seinerseits scheitern pass _spi = None for attr_name in ("_rst_pin", "_dc_pin", "_busy_pin"): device = globals()[attr_name] if device is not None: try: device.close() except Exception: # noqa: BLE001 pass globals()[attr_name] = None