diff --git a/protocol_prototype/DryBox/UI/gsm_status_widget.py b/protocol_prototype/DryBox/UI/gsm_status_widget.py new file mode 100644 index 0000000..e71c497 --- /dev/null +++ b/protocol_prototype/DryBox/UI/gsm_status_widget.py @@ -0,0 +1,330 @@ +from PyQt5.QtWidgets import ( + QWidget, QVBoxLayout, QHBoxLayout, QLabel, QFrame, + QGridLayout, QProgressBar +) +from PyQt5.QtCore import Qt, pyqtSignal, QTimer +from PyQt5.QtGui import QFont, QPalette, QColor, QPainter, QBrush, QLinearGradient +import math + +class SignalStrengthWidget(QWidget): + """Custom widget to display signal strength bars""" + def __init__(self, parent=None): + super().__init__(parent) + self.signal_strength = -70 # dBm + self.setMinimumSize(60, 40) + self.setMaximumSize(80, 50) + + def set_signal_strength(self, dbm): + self.signal_strength = dbm + self.update() + + def paintEvent(self, event): + painter = QPainter(self) + painter.setRenderHint(QPainter.Antialiasing) + + # Calculate number of bars based on signal strength + # -50 dBm or better = 5 bars + # -60 dBm = 4 bars + # -70 dBm = 3 bars + # -80 dBm = 2 bars + # -90 dBm = 1 bar + # < -90 dBm = 0 bars + + if self.signal_strength >= -50: + active_bars = 5 + elif self.signal_strength >= -60: + active_bars = 4 + elif self.signal_strength >= -70: + active_bars = 3 + elif self.signal_strength >= -80: + active_bars = 2 + elif self.signal_strength >= -90: + active_bars = 1 + else: + active_bars = 0 + + bar_width = 10 + bar_spacing = 3 + max_height = 35 + + for i in range(5): + x = i * (bar_width + bar_spacing) + 5 + bar_height = (i + 1) * 7 + y = max_height - bar_height + 10 + + if i < active_bars: + # Active bar - gradient from green to yellow to red based on strength + if self.signal_strength >= -60: + color = QColor(76, 175, 80) # Green + elif self.signal_strength >= -75: + color = QColor(255, 193, 7) # Amber + else: + color = QColor(244, 67, 54) # Red + else: + # Inactive bar + color = QColor(60, 60, 60) + + painter.fillRect(x, y, bar_width, bar_height, color) + +class GSMStatusWidget(QFrame): + """Widget to display GSM network status and parameters""" + + def __init__(self, parent=None): + super().__init__(parent) + self.setObjectName("gsmStatusWidget") + self.setFrameStyle(QFrame.StyledPanel) + + # Default settings + self.settings = { + 'signal_strength': -70, + 'signal_quality': 75, + 'noise_level': 10, + 'codec_mode': 'AMR-NB', + 'bitrate': 12.2, + 'packet_loss': 0, + 'jitter': 20, + 'latency': 100, + 'fading_enabled': False, + 'fading_speed': 'slow', + 'interference_enabled': False, + 'handover_enabled': False + } + + self.init_ui() + self.update_display() + + def init_ui(self): + """Initialize the UI components""" + main_layout = QVBoxLayout() + main_layout.setSpacing(10) + main_layout.setContentsMargins(15, 15, 15, 15) + + # Title + title_label = QLabel("📡 GSM Network Status") + title_label.setObjectName("gsmStatusTitle") + title_label.setAlignment(Qt.AlignCenter) + main_layout.addWidget(title_label) + + # Main status grid + status_grid = QGridLayout() + status_grid.setSpacing(12) + + # Row 0: Signal strength visualization + status_grid.addWidget(QLabel("Signal:"), 0, 0) + self.signal_widget = SignalStrengthWidget() + status_grid.addWidget(self.signal_widget, 0, 1) + self.signal_dbm_label = QLabel("-70 dBm") + self.signal_dbm_label.setObjectName("signalDbmLabel") + status_grid.addWidget(self.signal_dbm_label, 0, 2) + + # Row 1: Signal quality bar + status_grid.addWidget(QLabel("Quality:"), 1, 0) + self.quality_bar = QProgressBar() + self.quality_bar.setTextVisible(True) + self.quality_bar.setRange(0, 100) + self.quality_bar.setObjectName("qualityBar") + status_grid.addWidget(self.quality_bar, 1, 1, 1, 2) + + # Row 2: Noise level + status_grid.addWidget(QLabel("Noise:"), 2, 0) + self.noise_bar = QProgressBar() + self.noise_bar.setTextVisible(True) + self.noise_bar.setRange(0, 50) + self.noise_bar.setObjectName("noiseBar") + status_grid.addWidget(self.noise_bar, 2, 1, 1, 2) + + main_layout.addLayout(status_grid) + + # Separator + separator1 = QFrame() + separator1.setFrameShape(QFrame.HLine) + separator1.setObjectName("gsmSeparator") + main_layout.addWidget(separator1) + + # Codec info section + codec_layout = QHBoxLayout() + codec_layout.setSpacing(15) + + codec_icon = QLabel("🎤") + codec_layout.addWidget(codec_icon) + + self.codec_label = QLabel("AMR-NB @ 12.2 kbps") + self.codec_label.setObjectName("codecLabel") + codec_layout.addWidget(self.codec_label) + codec_layout.addStretch() + + main_layout.addLayout(codec_layout) + + # Network conditions section + network_grid = QGridLayout() + network_grid.setSpacing(8) + + # Packet loss indicator + network_grid.addWidget(QLabel("📉"), 0, 0) + self.packet_loss_label = QLabel("Loss: 0%") + self.packet_loss_label.setObjectName("networkParam") + network_grid.addWidget(self.packet_loss_label, 0, 1) + + # Jitter indicator + network_grid.addWidget(QLabel("📊"), 0, 2) + self.jitter_label = QLabel("Jitter: 20ms") + self.jitter_label.setObjectName("networkParam") + network_grid.addWidget(self.jitter_label, 0, 3) + + # Latency indicator + network_grid.addWidget(QLabel("⏱"), 1, 0) + self.latency_label = QLabel("Latency: 100ms") + self.latency_label.setObjectName("networkParam") + network_grid.addWidget(self.latency_label, 1, 1) + + # Features status + network_grid.addWidget(QLabel("🌊"), 1, 2) + self.features_label = QLabel("Standard") + self.features_label.setObjectName("networkParam") + network_grid.addWidget(self.features_label, 1, 3) + + main_layout.addLayout(network_grid) + + # Connection status + separator2 = QFrame() + separator2.setFrameShape(QFrame.HLine) + separator2.setObjectName("gsmSeparator") + main_layout.addWidget(separator2) + + self.connection_status = QLabel("🟢 Connected to GSM Network") + self.connection_status.setObjectName("connectionStatus") + self.connection_status.setAlignment(Qt.AlignCenter) + main_layout.addWidget(self.connection_status) + + main_layout.addStretch() + self.setLayout(main_layout) + + # Apply styling + self.setStyleSheet(""" + #gsmStatusWidget { + background-color: #2A2A2A; + border: 2px solid #0078D4; + border-radius: 10px; + } + #gsmStatusTitle { + font-size: 16px; + font-weight: bold; + color: #00A2E8; + padding: 5px; + } + #signalDbmLabel { + font-size: 14px; + font-weight: bold; + color: #4CAF50; + } + #qualityBar { + background-color: #1E1E1E; + border: 1px solid #444; + border-radius: 3px; + text-align: center; + color: white; + } + #qualityBar::chunk { + background-color: qlineargradient(x1:0, y1:0, x2:1, y2:0, + stop:0 #4CAF50, stop:0.5 #8BC34A, stop:1 #CDDC39); + border-radius: 3px; + } + #noiseBar { + background-color: #1E1E1E; + border: 1px solid #444; + border-radius: 3px; + text-align: center; + color: white; + } + #noiseBar::chunk { + background-color: qlineargradient(x1:0, y1:0, x2:1, y2:0, + stop:0 #FFC107, stop:0.5 #FF9800, stop:1 #FF5722); + border-radius: 3px; + } + #codecLabel { + font-size: 13px; + color: #E0E0E0; + font-weight: bold; + } + #networkParam { + font-size: 12px; + color: #B0B0B0; + } + #gsmSeparator { + background-color: #444; + max-height: 1px; + margin: 5px 0; + } + #connectionStatus { + font-size: 13px; + color: #4CAF50; + font-weight: bold; + padding: 5px; + } + QLabel { + color: #E0E0E0; + } + """) + + def update_settings(self, settings): + """Update the displayed settings""" + self.settings = settings + self.update_display() + + def update_display(self): + """Update all display elements based on current settings""" + # Update signal strength + self.signal_widget.set_signal_strength(self.settings['signal_strength']) + self.signal_dbm_label.setText(f"{self.settings['signal_strength']} dBm") + + # Color code the dBm label + if self.settings['signal_strength'] >= -60: + self.signal_dbm_label.setStyleSheet("color: #4CAF50;") # Green + elif self.settings['signal_strength'] >= -75: + self.signal_dbm_label.setStyleSheet("color: #FFC107;") # Amber + else: + self.signal_dbm_label.setStyleSheet("color: #FF5722;") # Red + + # Update quality bar + self.quality_bar.setValue(self.settings['signal_quality']) + self.quality_bar.setFormat(f"{self.settings['signal_quality']}%") + + # Update noise bar + self.noise_bar.setValue(self.settings['noise_level']) + self.noise_bar.setFormat(f"{self.settings['noise_level']}%") + + # Update codec info + self.codec_label.setText(f"{self.settings['codec_mode']} @ {self.settings['bitrate']} kbps") + + # Update network parameters + self.packet_loss_label.setText(f"Loss: {self.settings['packet_loss']}%") + self.jitter_label.setText(f"Jitter: {self.settings['jitter']}ms") + self.latency_label.setText(f"Latency: {self.settings['latency']}ms") + + # Update features + features = [] + if self.settings['fading_enabled']: + features.append(f"Fading({self.settings['fading_speed']})") + if self.settings['interference_enabled']: + features.append("Interference") + if self.settings['handover_enabled']: + features.append("Handover") + + if features: + self.features_label.setText(", ".join(features)) + else: + self.features_label.setText("Standard") + + # Update connection status based on signal quality + if self.settings['signal_quality'] >= 80: + self.connection_status.setText("🟢 Excellent Connection") + self.connection_status.setStyleSheet("color: #4CAF50;") + elif self.settings['signal_quality'] >= 60: + self.connection_status.setText("🟡 Good Connection") + self.connection_status.setStyleSheet("color: #FFC107;") + elif self.settings['signal_quality'] >= 40: + self.connection_status.setText("🟠 Fair Connection") + self.connection_status.setStyleSheet("color: #FF9800;") + else: + self.connection_status.setText("🔴 Poor Connection") + self.connection_status.setStyleSheet("color: #FF5722;") \ No newline at end of file diff --git a/protocol_prototype/DryBox/UI/main.py b/protocol_prototype/DryBox/UI/main.py index 0b2822f..4092554 100644 --- a/protocol_prototype/DryBox/UI/main.py +++ b/protocol_prototype/DryBox/UI/main.py @@ -11,6 +11,8 @@ import threading from phone_manager import PhoneManager from waveform_widget import WaveformWidget from phone_state import PhoneState +from gsm_settings_dialog import GSMSettingsDialog +from gsm_status_widget import GSMStatusWidget class PhoneUI(QMainWindow): debug_signal = pyqtSignal(str) @@ -27,45 +29,106 @@ class PhoneUI(QMainWindow): self.auto_test_running = False self.auto_test_timer = None self.test_step = 0 + + # GSM settings dialog (will be created on demand) + self.gsm_settings_dialog = None + self.gsm_settings = None + + # GSM simulation timer + self.gsm_simulation_timer = None self.setStyleSheet(""" - QMainWindow { background-color: #333333; } - QLabel { color: #E0E0E0; font-size: 14px; } - QPushButton { - background-color: #0078D4; color: white; border: none; - padding: 10px 15px; border-radius: 5px; font-size: 14px; - min-height: 30px; + QMainWindow { + background-color: #1a1a1a; + } + QLabel { + color: #E0E0E0; + font-size: 14px; + } + QPushButton { + background-color: #0078D4; + color: white; + border: none; + padding: 10px 15px; + border-radius: 5px; + font-size: 14px; + min-height: 30px; + font-weight: bold; + } + QPushButton:hover { + background-color: #005A9E; + } + QPushButton:pressed { + background-color: #003C6B; + } + QPushButton#settingsButton { + background-color: #555555; + } + QPushButton#settingsButton:hover { + background-color: #777777; } - QPushButton:hover { background-color: #005A9E; } - QPushButton:pressed { background-color: #003C6B; } - QPushButton#settingsButton { background-color: #555555; } - QPushButton#settingsButton:hover { background-color: #777777; } QFrame#phoneDisplay { - background-color: #1E1E1E; border: 2px solid #0078D4; - border-radius: 10px; + background: qlineargradient(x1:0, y1:0, x2:0, y2:1, + stop:0 #2A2A2A, stop:1 #1E1E1E); + border: 2px solid #0078D4; + border-radius: 12px; } QLabel#phoneTitleLabel { - font-size: 18px; font-weight: bold; padding-bottom: 5px; + font-size: 20px; + font-weight: bold; + padding: 10px; color: #FFFFFF; + background: qlineargradient(x1:0, y1:0, x2:1, y2:0, + stop:0 #0078D4, stop:0.5 #00A2E8, stop:1 #0078D4); + border-radius: 8px; + margin-bottom: 5px; } QLabel#mainTitleLabel { - font-size: 24px; font-weight: bold; color: #00A2E8; - padding: 15px; + font-size: 28px; + font-weight: bold; + color: #00A2E8; + padding: 20px; + text-align: center; + letter-spacing: 1px; } QWidget#phoneWidget { - border: 2px solid #4A4A4A; border-radius: 10px; - background-color: #3A3A3A; + border: 2px solid #0078D4; + border-radius: 15px; + background: qlineargradient(x1:0, y1:0, x2:0, y2:1, + stop:0 #3A3A3A, stop:0.5 #2F2F2F, stop:1 #252525); min-width: 400px; - padding: 10px; + padding: 15px; + margin: 5px; + } + QWidget#phoneWidget:hover { + border: 2px solid #00A2E8; } QTextEdit#debugConsole { - background-color: #1E1E1E; color: #00FF00; - font-family: monospace; font-size: 12px; - border: 2px solid #0078D4; border-radius: 5px; + background-color: #0a0a0a; + color: #00FF00; + font-family: 'Consolas', 'Monaco', monospace; + font-size: 12px; + border: 2px solid #0078D4; + border-radius: 8px; + padding: 10px; } QPushButton#autoTestButton { - background-color: #FF8C00; min-height: 35px; + background: qlineargradient(x1:0, y1:0, x2:1, y2:0, + stop:0 #FF8C00, stop:1 #FFA500); + min-height: 35px; + font-size: 15px; + } + QPushButton#autoTestButton:hover { + background: qlineargradient(x1:0, y1:0, x2:1, y2:0, + stop:0 #FF7F00, stop:1 #FF9500); + } + QSplitter::handle { + background-color: #0078D4; + width: 3px; + height: 3px; + } + QSplitter::handle:hover { + background-color: #00A2E8; } - QPushButton#autoTestButton:hover { background-color: #FF7F00; } """) # Setup debug signal early @@ -81,9 +144,19 @@ class PhoneUI(QMainWindow): main_layout = QVBoxLayout() main_widget.setLayout(main_layout) - # Create splitter for phones and debug console + # Create main horizontal splitter for GSM status and phones/debug + self.main_h_splitter = QSplitter(Qt.Horizontal) + main_layout.addWidget(self.main_h_splitter) + + # GSM Status Panel (left side) + self.gsm_status_widget = GSMStatusWidget() + self.gsm_status_widget.setMinimumWidth(280) + self.gsm_status_widget.setMaximumWidth(350) + self.main_h_splitter.addWidget(self.gsm_status_widget) + + # Create vertical splitter for phones and debug console (right side) self.splitter = QSplitter(Qt.Vertical) - main_layout.addWidget(self.splitter) + self.main_h_splitter.addWidget(self.splitter) # Top widget for phones phones_widget = QWidget() @@ -188,8 +261,9 @@ class PhoneUI(QMainWindow): self.debug_console.append(msg) del self._debug_queue - # Set splitter sizes (70% phones, 30% debug) - self.splitter.setSizes([600, 300]) + # Set splitter sizes + self.splitter.setSizes([600, 300]) # 70% phones, 30% debug + self.main_h_splitter.setSizes([300, 1100]) # GSM panel: 300px, rest: 1100px # Initialize UI for phone in self.manager.phones: @@ -343,25 +417,69 @@ class PhoneUI(QMainWindow): status_label = phone['status_label'] if state == PhoneState.IDLE: - button.setText("Call") + button.setText("📞 Call") button.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay)) - status_label.setText("Idle") - button.setStyleSheet("background-color: #0078D4;") + status_label.setText("📱 Idle") + status_label.setStyleSheet("font-size: 18px; color: #888888;") + button.setStyleSheet(""" + QPushButton { + background: qlineargradient(x1:0, y1:0, x2:0, y2:1, + stop:0 #0078D4, stop:1 #005A9E); + font-weight: bold; + } + QPushButton:hover { + background: qlineargradient(x1:0, y1:0, x2:0, y2:1, + stop:0 #00A2E8, stop:1 #0078D4); + } + """) elif state == PhoneState.CALLING: - button.setText("Cancel") + button.setText("❌ Cancel") button.setIcon(self.style().standardIcon(QStyle.SP_MediaStop)) - status_label.setText(f"Calling {phone_number}...") - button.setStyleSheet("background-color: #E81123;") + status_label.setText(f"📲 Calling {phone_number}...") + status_label.setStyleSheet("font-size: 18px; color: #FFC107;") + button.setStyleSheet(""" + QPushButton { + background: qlineargradient(x1:0, y1:0, x2:0, y2:1, + stop:0 #E81123, stop:1 #B20F1F); + font-weight: bold; + } + QPushButton:hover { + background: qlineargradient(x1:0, y1:0, x2:0, y2:1, + stop:0 #FF1133, stop:1 #E81123); + } + """) elif state == PhoneState.IN_CALL: - button.setText("Hang Up") + button.setText("📵 Hang Up") button.setIcon(self.style().standardIcon(QStyle.SP_DialogCancelButton)) - status_label.setText(f"In Call with {phone_number}") - button.setStyleSheet("background-color: #E81123;") + status_label.setText(f"📞 In Call with {phone_number}") + status_label.setStyleSheet("font-size: 18px; color: #4CAF50;") + button.setStyleSheet(""" + QPushButton { + background: qlineargradient(x1:0, y1:0, x2:0, y2:1, + stop:0 #E81123, stop:1 #B20F1F); + font-weight: bold; + } + QPushButton:hover { + background: qlineargradient(x1:0, y1:0, x2:0, y2:1, + stop:0 #FF1133, stop:1 #E81123); + } + """) elif state == PhoneState.RINGING: - button.setText("Answer") + button.setText("✅ Answer") button.setIcon(self.style().standardIcon(QStyle.SP_DialogApplyButton)) - status_label.setText(f"Incoming Call from {phone_number}") - button.setStyleSheet("background-color: #107C10;") + status_label.setText(f"📱 Incoming Call from {phone_number}") + status_label.setStyleSheet("font-size: 18px; color: #4CAF50; font-weight: bold;") + button.setStyleSheet(""" + QPushButton { + background: qlineargradient(x1:0, y1:0, x2:0, y2:1, + stop:0 #107C10, stop:1 #0B5C0B); + font-weight: bold; + } + QPushButton:hover { + background: qlineargradient(x1:0, y1:0, x2:0, y2:1, + stop:0 #20AC20, stop:1 #107C10); + } + """) def set_phone_state(self, client_id, state_str, number): self.debug(f"Phone {client_id + 1} state change: {state_str}") @@ -370,18 +488,26 @@ class PhoneUI(QMainWindow): if state_str == "HANDSHAKE_COMPLETE": phone = self.manager.phones[client_id] phone['status_label'].setText("🔒 Secure Channel Established") + phone['status_label'].setStyleSheet("font-size: 16px; color: #00A2E8; font-weight: bold;") self.debug(f"Phone {client_id + 1} secure channel established") self.manager.start_audio(client_id, parent=self) return elif state_str == "VOICE_START": phone = self.manager.phones[client_id] phone['status_label'].setText("🎤 Voice Active (Encrypted)") + phone['status_label'].setStyleSheet("font-size: 16px; color: #4CAF50; font-weight: bold;") self.debug(f"Phone {client_id + 1} voice session started") + # Start simulating real-time GSM parameter updates if we have settings + if hasattr(self, 'gsm_settings') and self.gsm_settings: + self.start_gsm_simulation() return elif state_str == "VOICE_END": phone = self.manager.phones[client_id] phone['status_label'].setText("🔒 Secure Channel") + phone['status_label'].setStyleSheet("font-size: 16px; color: #00A2E8;") self.debug(f"Phone {client_id + 1} voice session ended") + # Stop GSM simulation + self.stop_gsm_simulation() return # Handle regular states @@ -409,8 +535,61 @@ class PhoneUI(QMainWindow): self.update_phone_ui(client_id) def settings_action(self): - print("Settings clicked") - self.debug("Settings clicked") + """Show GSM settings dialog""" + self.debug("Opening GSM settings dialog") + + # Create dialog if not exists (lazy initialization) + if self.gsm_settings_dialog is None: + self.gsm_settings_dialog = GSMSettingsDialog(self) + # Connect signal to handle settings changes + self.gsm_settings_dialog.settings_changed.connect(self.on_gsm_settings_changed) + + # Show dialog + if self.gsm_settings_dialog.exec_(): + # User clicked OK, get the settings + self.gsm_settings = self.gsm_settings_dialog.get_settings() + self.debug("GSM settings updated:") + self.debug(f" Signal strength: {self.gsm_settings['signal_strength']} dBm") + self.debug(f" Signal quality: {self.gsm_settings['signal_quality']}%") + self.debug(f" Noise level: {self.gsm_settings['noise_level']}%") + self.debug(f" Codec: {self.gsm_settings['codec_mode']} @ {self.gsm_settings['bitrate']} kbps") + self.debug(f" Network conditions - Packet loss: {self.gsm_settings['packet_loss']}%, Jitter: {self.gsm_settings['jitter']}ms, Latency: {self.gsm_settings['latency']}ms") + + # Apply settings to phones if needed + self.apply_gsm_settings() + # Update GSM status widget + self.gsm_status_widget.update_settings(self.gsm_settings) + else: + self.debug("GSM settings dialog cancelled") + + def on_gsm_settings_changed(self, settings): + """Handle real-time settings changes if needed""" + self.gsm_settings = settings + self.debug("GSM settings changed (real-time update)") + # Update GSM status widget in real-time + self.gsm_status_widget.update_settings(settings) + + def apply_gsm_settings(self): + """Apply GSM settings to the phone simulation""" + if self.gsm_settings: + # Here you can apply the settings to your phone manager or simulation + # For example, update signal quality indicators, adjust codec parameters, etc. + self.debug("Applying GSM settings to simulation...") + + # Update UI to reflect signal conditions + for phone_id, phone in enumerate(self.manager.phones): + signal_quality = self.gsm_settings['signal_quality'] + if signal_quality >= 80: + signal_icon = "📶" # Full signal + elif signal_quality >= 60: + signal_icon = "📶" # Good signal + elif signal_quality >= 40: + signal_icon = "📵" # Fair signal + else: + signal_icon = "📵" # Poor signal + + # You can update status labels or other UI elements here + self.debug(f"Phone {phone_id + 1} signal quality: {signal_quality}% {signal_icon}") def debug(self, message): """Thread-safe debug logging to both console and UI""" @@ -714,6 +893,7 @@ class PhoneUI(QMainWindow): QShortcut(QKeySequence("Space"), self, self.toggle_auto_test) QShortcut(QKeySequence("Ctrl+L"), self, self.clear_debug) QShortcut(QKeySequence("Ctrl+A"), self, self.show_audio_menu) + QShortcut(QKeySequence("Ctrl+G"), self, self.settings_action) self.debug("Keyboard shortcuts enabled:") self.debug(" 1/2: Phone action (call/answer/hangup)") @@ -722,15 +902,84 @@ class PhoneUI(QMainWindow): self.debug(" Space: Toggle auto test") self.debug(" Ctrl+L: Clear debug") self.debug(" Ctrl+A: Audio options menu") + self.debug(" Ctrl+G: GSM settings") + + def start_gsm_simulation(self): + """Start simulating GSM parameter changes during call""" + if not self.gsm_simulation_timer: + self.gsm_simulation_timer = QTimer() + self.gsm_simulation_timer.timeout.connect(self.update_gsm_simulation) + self.gsm_simulation_timer.start(1000) # Update every second + self.debug("Started GSM parameter simulation") + + def stop_gsm_simulation(self): + """Stop GSM parameter simulation""" + if self.gsm_simulation_timer: + self.gsm_simulation_timer.stop() + self.gsm_simulation_timer = None + self.debug("Stopped GSM parameter simulation") + + def update_gsm_simulation(self): + """Simulate realistic GSM parameter variations during call""" + if not hasattr(self, 'gsm_settings') or not self.gsm_settings: + return + + import random + + # Simulate signal strength variation (±2 dBm) + current_strength = self.gsm_settings['signal_strength'] + variation = random.uniform(-2, 2) + new_strength = max(-120, min(-40, current_strength + variation)) + self.gsm_settings['signal_strength'] = int(new_strength) + + # Simulate signal quality variation (±3%) + current_quality = self.gsm_settings['signal_quality'] + quality_variation = random.uniform(-3, 3) + new_quality = max(0, min(100, current_quality + quality_variation)) + self.gsm_settings['signal_quality'] = int(new_quality) + + # Simulate noise level variation (±1%) + current_noise = self.gsm_settings['noise_level'] + noise_variation = random.uniform(-1, 2) + new_noise = max(0, min(50, current_noise + noise_variation)) + self.gsm_settings['noise_level'] = int(new_noise) + + # Simulate packet loss variation (occasional spikes) + if random.random() < 0.1: # 10% chance of packet loss spike + self.gsm_settings['packet_loss'] = random.randint(1, 5) + else: + self.gsm_settings['packet_loss'] = 0 + + # Simulate jitter variation (±5ms) + current_jitter = self.gsm_settings['jitter'] + jitter_variation = random.uniform(-5, 5) + new_jitter = max(0, min(200, current_jitter + jitter_variation)) + self.gsm_settings['jitter'] = int(new_jitter) + + # Update the GSM status widget + self.gsm_status_widget.update_settings(self.gsm_settings) def closeEvent(self, event): if self.auto_test_running: self.stop_auto_test() + + # Stop GSM simulation + self.stop_gsm_simulation() + + # Clean up GSM settings dialog + if self.gsm_settings_dialog is not None: + self.gsm_settings_dialog.close() + self.gsm_settings_dialog.deleteLater() + self.gsm_settings_dialog = None + # Clean up audio player if hasattr(self.manager, 'audio_player'): self.manager.audio_player.cleanup() + + # Stop all phone clients for phone in self.manager.phones: phone['client'].stop() + event.accept() if __name__ == "__main__":