"""BLF-Panel - Besetzt-Lampenfeld für Extension-Status-Überwachung.""" import pjsua2 as pj from PySide6.QtWidgets import ( QWidget, QVBoxLayout, QGridLayout, QPushButton, QLabel, ) from PySide6.QtCore import Signal, Qt from PySide6.QtGui import QColor class BlfBuddy(pj.Buddy): """PJSUA2-Buddy für Presence-Monitoring einer Extension.""" def __init__(self, extension, callback=None): pj.Buddy.__init__(self) self.extension = extension self._callback = callback def onBuddyState(self): """Wird aufgerufen wenn sich der Presence-Status ändert.""" bi = self.getInfo() if self._callback: self._callback(self.extension, { "status": bi.presMonitorEnabled, "activity": bi.presStatus.activity, "statusText": bi.presStatus.statusText, "online": bi.presStatus.status == pj.PJSUA_BUDDY_STATUS_ONLINE, }) class BlfPanel(QWidget): """Panel mit Status-LEDs für konfigurierte Extensions.""" # Signal: Extension zum Anrufen angeklickt extension_geklickt = Signal(str) # Status-Farben FARBEN = { "frei": "#4CAF50", # Grün "besetzt": "#F44336", # Rot "klingelt": "#FF9800", # Orange "offline": "#9E9E9E", # Grau } def __init__(self, parent=None): super().__init__(parent) self._buddies = {} # extension → BlfBuddy self._buttons = {} # extension → QPushButton self._account = None self._server = "" self._ui_aufbauen() def _ui_aufbauen(self): self._layout = QVBoxLayout(self) self._layout.setContentsMargins(0, 0, 0, 0) titel = QLabel("BLF-Status") titel.setStyleSheet("font-weight: bold; margin-bottom: 4px;") self._layout.addWidget(titel) self._grid = QGridLayout() self._grid.setSpacing(4) self._layout.addLayout(self._grid) self._layout.addStretch() def konfigurieren(self, account, server, extensions): """BLF-Panel mit Extensions konfigurieren. Args: account: MeinAccount-Instanz (PJSUA2-Account) server: FreePBX Server-IP extensions: Liste von Extension-Nummern (z.B. ["200", "201"]) """ self._account = account self._server = server # Bestehende Buddies aufräumen self._buddies_aufraumen() # Bestehende Buttons entfernen for btn in self._buttons.values(): self._grid.removeWidget(btn) btn.deleteLater() self._buttons.clear() # Neue Buttons und Buddies erstellen for i, ext in enumerate(extensions): zeile = i // 4 # 4 Buttons pro Zeile spalte = i % 4 btn = QPushButton(ext) btn.setMinimumSize(60, 40) btn.setStyleSheet( f"background-color: {self.FARBEN['offline']}; " "color: white; font-weight: bold; border-radius: 4px;" ) btn.clicked.connect( lambda checked, e=ext: self.extension_geklickt.emit(e) ) self._grid.addWidget(btn, zeile, spalte) self._buttons[ext] = btn # Buddy für Presence-Monitoring erstellen if account: self._buddy_erstellen(ext) def _buddy_erstellen(self, extension): """PJSUA2-Buddy für eine Extension erstellen.""" try: buddy = BlfBuddy(extension, callback=self._status_update) buddy_cfg = pj.BuddyConfig() buddy_cfg.uri = f"sip:{extension}@{self._server}" buddy_cfg.subscribe = True buddy.create(self._account, buddy_cfg) self._buddies[extension] = buddy except pj.Error: pass # Extension nicht erreichbar def _status_update(self, extension, status): """Callback: Status einer Extension hat sich geändert.""" if extension not in self._buttons: return btn = self._buttons[extension] if status.get("online"): # Online → prüfe Activity activity = status.get("activity", 0) if activity == pj.PJRPID_ACTIVITY_BUSY: farbe = self.FARBEN["besetzt"] else: farbe = self.FARBEN["frei"] else: farbe = self.FARBEN["offline"] btn.setStyleSheet( f"background-color: {farbe}; " "color: white; font-weight: bold; border-radius: 4px;" ) tooltip = status.get("statusText", "") if tooltip: btn.setToolTip(f"{extension}: {tooltip}") def _buddies_aufraumen(self): """Alle Buddies sauber herunterfahren.""" for buddy in self._buddies.values(): try: buddy.subscribePresence(False) except pj.Error: pass self._buddies.clear() def aufraumen(self): """Ressourcen freigeben.""" self._buddies_aufraumen()