add drybox ui
All checks were successful
/ mirror (push) Successful in 5s

This commit is contained in:
Bartosz 2025-05-11 17:25:51 +01:00
parent 9e2daa7f53
commit 41aff9848a
4 changed files with 418 additions and 2 deletions

View File

@ -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_())

View File

@ -1,5 +1,4 @@
import socket #external_caller.py
import socket import socket
import time import time

View File

@ -1,3 +1,4 @@
#external_receiver.py
import socket import socket
def connect(): def connect():

View File

@ -1,3 +1,4 @@
#gsm_simulator.py
import socket import socket
import threading import threading
import time import time