diff --git a/protocol_prototype/DryBox/UI/python_ui.py b/protocol_prototype/DryBox/UI/python_ui.py new file mode 100644 index 0000000..97d8aa0 --- /dev/null +++ b/protocol_prototype/DryBox/UI/python_ui.py @@ -0,0 +1,415 @@ +import sys +import random +import socket +import threading +import time +from PyQt5.QtWidgets import ( + QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, + QPushButton, QLabel, QFrame, QSizePolicy, QStyle +) +from PyQt5.QtCore import Qt, QTimer, QSize, QPointF, pyqtSignal, QThread +from PyQt5.QtGui import QPainter, QColor, QPen, QLinearGradient, QBrush, QIcon, QFont + +# --- Phone Client Thread --- +class PhoneClient(QThread): + data_received = pyqtSignal(bytes, int) # Include client_id + state_changed = pyqtSignal(str, str, int) # Include client_id + + def __init__(self, host, port, client_id): + super().__init__() + self.host = host + self.port = port + self.client_id = client_id + self.sock = None + self.running = True + + def run(self): + try: + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) + self.sock.settimeout(15) + self.sock.connect((self.host, self.port)) + print(f"Client {self.client_id} connected to {self.host}:{self.port}") + while self.running: + try: + data = self.sock.recv(1024) + if not data: + print(f"Client {self.client_id} disconnected") + self.state_changed.emit("CALL_END", "", self.client_id) + break + decoded_data = data.decode('utf-8', errors='ignore').strip() + print(f"Client {self.client_id} received raw: {decoded_data}") + if decoded_data in ["RINGING", "CALL_END", "CALL_DROPPED", "IN_CALL"]: + self.state_changed.emit(decoded_data, "", self.client_id) + else: + self.data_received.emit(data, self.client_id) + print(f"Client {self.client_id} received audio: {decoded_data}") + except socket.timeout: + print(f"Client {self.client_id} timed out waiting for data") + continue + except Exception as e: + print(f"Client {self.client_id} error: {e}") + self.state_changed.emit("CALL_END", "", self.client_id) + break + except Exception as e: + print(f"Client {self.client_id} connection failed: {e}") + finally: + if self.sock: + self.sock.close() + + def send(self, message): + if self.sock and self.running: + try: + self.sock.send(message.encode()) + print(f"Client {self.client_id} sent: {message}") + except Exception as e: + print(f"Client {self.client_id} send error: {e}") + + def stop(self): + self.running = False + if self.sock: + self.sock.close() + +# --- Custom Waveform Widget --- +class WaveformWidget(QWidget): + def __init__(self, parent=None, dynamic=False): + super().__init__(parent) + self.dynamic = dynamic + self.setMinimumSize(200, 80) + self.setMaximumHeight(100) + self.waveform_data = [random.randint(10, 90) for _ in range(50)] + if self.dynamic: + self.timer = QTimer(self) + self.timer.timeout.connect(self.update_waveform) + self.timer.start(100) + + def update_waveform(self): + self.waveform_data = self.waveform_data[1:] + [random.randint(10, 90)] + self.update() + + def set_data(self, data): + amplitude = sum(byte for byte in data) % 90 + 10 + self.waveform_data = self.waveform_data[1:] + [amplitude] + self.update() + + def paintEvent(self, event): + painter = QPainter(self) + painter.setRenderHint(QPainter.Antialiasing) + painter.fillRect(self.rect(), QColor("#2D2D2D")) + gradient = QLinearGradient(0, 0, 0, self.height()) + gradient.setColorAt(0.0, QColor("#0078D4")) + gradient.setColorAt(1.0, QColor("#50E6A4")) + pen = QPen(QBrush(gradient), 2) + painter.setPen(pen) + bar_width = self.width() / len(self.waveform_data) + max_h = self.height() - 10 + for i, val in enumerate(self.waveform_data): + bar_height = (val / 100.0) * max_h + x = i * bar_width + y = (self.height() - bar_height) / 2 + painter.drawLine(QPointF(x + bar_width / 2, y), QPointF(x + bar_width / 2, y + bar_height)) + + def resizeEvent(self, event): + super().resizeEvent(event) + self.update() + +# --- Phone State --- +class PhoneState: + IDLE = 0 + CALLING = 1 + IN_CALL = 2 + RINGING = 3 + +class PhoneUI(QMainWindow): + def __init__(self): + super().__init__() + self.setWindowTitle("Enhanced Dual Phone Interface") + self.setGeometry(100, 100, 900, 750) + 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; + } + 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; + } + QLabel#phoneTitleLabel { + font-size: 18px; font-weight: bold; padding-bottom: 5px; + color: #FFFFFF; + } + QLabel#mainTitleLabel { + font-size: 24px; font-weight: bold; color: #00A2E8; + padding: 15px; + } + QWidget#phoneWidget { + border: 1px solid #4A4A4A; border-radius: 8px; + padding: 10px; background-color: #3A3A3A; + } + """) + + # Phone states + self.phone1_state = PhoneState.IDLE + self.phone2_state = PhoneState.IDLE + + # Phone clients + self.phone1_client = PhoneClient("localhost", 12345, 0) + self.phone2_client = PhoneClient("localhost", 12345, 1) + self.phone1_client.data_received.connect(lambda data, cid: self.update_waveform(cid, data)) + self.phone2_client.data_received.connect(lambda data, cid: self.update_waveform(cid, data)) + self.phone1_client.state_changed.connect(lambda state, num, cid: self.set_phone_state(cid, self.map_state(state), num)) + self.phone2_client.state_changed.connect(lambda state, num, cid: self.set_phone_state(cid, self.map_state(state), num)) + self.phone1_client.start() + self.phone2_client.start() + + # Main widget and layout + main_widget = QWidget() + self.setCentralWidget(main_widget) + main_layout = QVBoxLayout() + main_layout.setSpacing(20) + main_layout.setContentsMargins(20, 20, 20, 20) + main_layout.setAlignment(Qt.AlignCenter) + main_widget.setLayout(main_layout) + + # App Title + app_title_label = QLabel("Dual Phone Control Panel") + app_title_label.setObjectName("mainTitleLabel") + app_title_label.setAlignment(Qt.AlignCenter) + main_layout.addWidget(app_title_label) + + # Phone displays layout + phone_controls_layout = QHBoxLayout() + phone_controls_layout.setSpacing(50) + phone_controls_layout.setAlignment(Qt.AlignCenter) + main_layout.addLayout(phone_controls_layout) + + # Phone 1 + phone1_widget_container, self.phone1_display, self.phone1_button, self.phone1_waveform = self._create_phone_ui("Phone 1", self.phone1_action) + phone_controls_layout.addWidget(phone1_widget_container) + + # Phone 2 + phone2_widget_container, self.phone2_display, self.phone2_button, self.phone2_waveform = self._create_phone_ui("Phone 2", self.phone2_action) + phone_controls_layout.addWidget(phone2_widget_container) + + # Spacer + main_layout.addStretch(1) + + # Settings Button + self.settings_button = QPushButton("Settings") + self.settings_button.setObjectName("settingsButton") + self.settings_button.setFixedWidth(180) + self.settings_button.setIcon(self.style().standardIcon(QStyle.SP_FileDialogDetailedView)) + self.settings_button.setIconSize(QSize(20, 20)) + self.settings_button.clicked.connect(self.settings_action) + settings_layout = QHBoxLayout() + settings_layout.addStretch() + settings_layout.addWidget(self.settings_button) + settings_layout.addStretch() + main_layout.addLayout(settings_layout) + + # Initialize button states + self._update_phone_button_ui(self.phone1_button, self.phone1_state) + self._update_phone_button_ui(self.phone2_button, self.phone2_state) + + def _create_phone_ui(self, title, action_slot): + phone_container_widget = QWidget() + phone_container_widget.setObjectName("phoneWidget") + phone_layout = QVBoxLayout() + phone_layout.setAlignment(Qt.AlignCenter) + phone_layout.setSpacing(15) + phone_container_widget.setLayout(phone_layout) + + phone_title_label = QLabel(title) + phone_title_label.setObjectName("phoneTitleLabel") + phone_title_label.setAlignment(Qt.AlignCenter) + phone_layout.addWidget(phone_title_label) + + phone_display_frame = QFrame() + phone_display_frame.setObjectName("phoneDisplay") + phone_display_frame.setFixedSize(250, 350) + phone_display_frame.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + + display_content_layout = QVBoxLayout(phone_display_frame) + display_content_layout.setAlignment(Qt.AlignCenter) + phone_status_label = QLabel("Idle") + phone_status_label.setAlignment(Qt.AlignCenter) + phone_status_label.setFont(QFont("Arial", 16)) + display_content_layout.addWidget(phone_status_label) + phone_layout.addWidget(phone_display_frame, alignment=Qt.AlignCenter) + + phone_button = QPushButton() + phone_button.setFixedWidth(120) + phone_button.setIconSize(QSize(20, 20)) + phone_button.clicked.connect(action_slot) + phone_layout.addWidget(phone_button, alignment=Qt.AlignCenter) + + waveform_label = QLabel(f"{title} Audio") + waveform_label.setAlignment(Qt.AlignCenter) + waveform_label.setStyleSheet("font-size: 14px; color: #E0E0E0;") + phone_layout.addWidget(waveform_label) + waveform_widget = WaveformWidget(dynamic=False) + phone_layout.addWidget(waveform_widget, alignment=Qt.AlignCenter) + + phone_display_frame.setProperty("statusLabel", phone_status_label) + return phone_container_widget, phone_display_frame, phone_button, waveform_widget + + def _update_phone_button_ui(self, button, state, phone_number=""): + parent_widget = button.parentWidget() + if parent_widget: + frame = parent_widget.findChild(QFrame, "phoneDisplay") + if frame: + status_label = frame.property("statusLabel") + if status_label: + if state == PhoneState.IDLE: + button.setText("Call") + button.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay)) + status_label.setText("Idle") + button.setStyleSheet("background-color: #0078D4;") + elif state == PhoneState.CALLING: + button.setText("Cancel") + button.setIcon(self.style().standardIcon(QStyle.SP_MediaStop)) + status_label.setText(f"Calling {phone_number}...") + button.setStyleSheet("background-color: #E81123;") + elif state == PhoneState.IN_CALL: + 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;") + elif state == PhoneState.RINGING: + 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;") + else: + print("Warning: statusLabel property not found") + else: + print("Warning: QFrame not found") + else: + print("Warning: Parent widget not found") + + def update_waveform(self, client_id, data): + print(f"Updating waveform for client_id {client_id}") + waveform = self.phone1_waveform if client_id == 0 else self.phone2_waveform + waveform.set_data(data) + + def map_state(self, state_str): + if state_str == "RINGING": + return PhoneState.RINGING + elif state_str in ["CALL_END", "CALL_DROPPED"]: + return PhoneState.IDLE + elif state_str == "IN_CALL": + return PhoneState.IN_CALL + return PhoneState.IDLE # Default to IDLE + + def set_phone_state(self, client_id, state, number=""): + if client_id == 0: + self.phone1_state = state + self._update_phone_button_ui(self.phone1_button, self.phone1_state, number if number else "123-4567") + if state == PhoneState.IDLE and hasattr(self, 'phone1_audio_timer'): + self.phone1_audio_timer.stop() + elif state == PhoneState.IN_CALL and (not hasattr(self, 'phone1_audio_timer') or not self.phone1_audio_timer.isActive()): + self.phone1_audio_timer = QTimer(self) + self.phone1_audio_timer.timeout.connect(self.send_phone1_audio) + self.phone1_audio_timer.start(1000) + else: + self.phone2_state = state + self._update_phone_button_ui(self.phone2_button, self.phone2_state, number if number else "987-6543") + if state == PhoneState.IDLE and hasattr(self, 'phone2_audio_timer'): + self.phone2_audio_timer.stop() + elif state == PhoneState.IN_CALL and (not hasattr(self, 'phone2_audio_timer') or not self.phone2_audio_timer.isActive()): + self.phone2_audio_timer = QTimer(self) + self.phone2_audio_timer.timeout.connect(self.send_phone2_audio) + self.phone2_audio_timer.start(1000) + + def phone1_action(self): + print("Phone 1 Action") + if self.phone1_state == PhoneState.IDLE: + self.phone1_state = PhoneState.CALLING + self.phone1_client.send("RINGING") + self._update_phone_button_ui(self.phone1_button, self.phone1_state, "123-4567") + elif self.phone1_state == PhoneState.CALLING: + self.phone1_state = PhoneState.IDLE + self.phone1_client.send("CALL_END") + self._update_phone_button_ui(self.phone1_button, self.phone1_state) + if hasattr(self, 'phone1_audio_timer'): + self.phone1_audio_timer.stop() + elif self.phone1_state == PhoneState.RINGING: + self.phone1_state = PhoneState.IN_CALL + self.phone2_state = PhoneState.IN_CALL # Sync both phones + self.phone1_client.send("IN_CALL") + self._update_phone_button_ui(self.phone1_button, self.phone1_state, "123-4567") + self._update_phone_button_ui(self.phone2_button, self.phone2_state, "987-6543") + # Start audio timer + self.phone1_audio_timer = QTimer(self) + self.phone1_audio_timer.timeout.connect(self.send_phone1_audio) + self.phone1_audio_timer.start(1000) + elif self.phone1_state == PhoneState.IN_CALL: + self.phone1_state = PhoneState.IDLE + self.phone2_state = PhoneState.IDLE # Sync both phones + self.phone1_client.send("CALL_END") + self._update_phone_button_ui(self.phone1_button, self.phone1_state) + self._update_phone_button_ui(self.phone2_button, self.phone2_state) + if hasattr(self, 'phone1_audio_timer'): + self.phone1_audio_timer.stop() + + def send_phone1_audio(self): + if self.phone1_state == PhoneState.IN_CALL: + message = f"Audio packet {random.randint(1, 1000)}" + self.phone1_client.send(message) + + def phone2_action(self): + print("Phone 2 Action") + if self.phone2_state == PhoneState.IDLE: + self.phone2_state = PhoneState.CALLING + self.phone2_client.send("RINGING") + self._update_phone_button_ui(self.phone2_button, self.phone2_state, "987-6543") + elif self.phone2_state == PhoneState.CALLING: + self.phone2_state = PhoneState.IDLE + self.phone2_client.send("CALL_END") + self._update_phone_button_ui(self.phone2_button, self.phone2_state) + if hasattr(self, 'phone2_audio_timer'): + self.phone2_audio_timer.stop() + elif self.phone2_state == PhoneState.RINGING: + self.phone2_state = PhoneState.IN_CALL + self.phone1_state = PhoneState.IN_CALL # Sync both phones + self.phone2_client.send("IN_CALL") + self._update_phone_button_ui(self.phone2_button, self.phone2_state, "987-6543") + self._update_phone_button_ui(self.phone1_button, self.phone1_state, "123-4567") + # Start audio timer + self.phone2_audio_timer = QTimer(self) + self.phone2_audio_timer.timeout.connect(self.send_phone2_audio) + self.phone2_audio_timer.start(1000) + elif self.phone2_state == PhoneState.IN_CALL: + self.phone2_state = PhoneState.IDLE + self.phone1_state = PhoneState.IDLE # Sync both phones + self.phone2_client.send("CALL_END") + self._update_phone_button_ui(self.phone2_button, self.phone2_state) + self._update_phone_button_ui(self.phone1_button, self.phone1_state) + if hasattr(self, 'phone2_audio_timer'): + self.phone2_audio_timer.stop() + + def send_phone2_audio(self): + if self.phone2_state == PhoneState.IN_CALL: + message = f"Audio packet {random.randint(1, 1000)}" + self.phone2_client.send(message) + + def settings_action(self): + print("Settings clicked") + + def closeEvent(self, event): + self.phone1_client.stop() + self.phone2_client.stop() + event.accept() + +if __name__ == "__main__": + app = QApplication(sys.argv) + window = PhoneUI() + window.show() + sys.exit(app.exec_()) \ No newline at end of file diff --git a/protocol_prototype/DryBox/external_caller.py b/protocol_prototype/DryBox/external_caller.py index 2b4655d..f96da67 100644 --- a/protocol_prototype/DryBox/external_caller.py +++ b/protocol_prototype/DryBox/external_caller.py @@ -1,5 +1,4 @@ -import socket - +#external_caller.py import socket import time diff --git a/protocol_prototype/DryBox/external_receiver.py b/protocol_prototype/DryBox/external_receiver.py index b5c7fac..3c5f8cd 100644 --- a/protocol_prototype/DryBox/external_receiver.py +++ b/protocol_prototype/DryBox/external_receiver.py @@ -1,3 +1,4 @@ +#external_receiver.py import socket def connect(): diff --git a/protocol_prototype/DryBox/gsm_simulator.py b/protocol_prototype/DryBox/gsm_simulator.py index 3605c0a..cb75e2b 100644 --- a/protocol_prototype/DryBox/gsm_simulator.py +++ b/protocol_prototype/DryBox/gsm_simulator.py @@ -1,3 +1,4 @@ +#gsm_simulator.py import socket import threading import time