diff --git a/protocol_prototype/DryBox/UI/python_ui.py b/protocol_prototype/DryBox/UI/python_ui.py index 97d8aa0..67e5a7c 100644 --- a/protocol_prototype/DryBox/UI/python_ui.py +++ b/protocol_prototype/DryBox/UI/python_ui.py @@ -1,8 +1,6 @@ import sys import random import socket -import threading -import time from PyQt5.QtWidgets import ( QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QFrame, QSizePolicy, QStyle @@ -155,20 +153,6 @@ class PhoneUI(QMainWindow): } """) - # 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) @@ -190,13 +174,29 @@ class PhoneUI(QMainWindow): 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) + # Initialize phones + self.phones = [] + for i in range(2): + client = PhoneClient("localhost", 12345, i) + client.data_received.connect(lambda data, cid=i: self.update_waveform(cid, data)) + client.state_changed.connect(lambda state, num, cid=i: self.set_phone_state(cid, self.map_state(state), num)) + client.start() - # 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) + # Corrected lambda to handle 'checked' and capture phone_id + phone_widget_container, phone_display, phone_button, phone_waveform, phone_status_label = self._create_phone_ui( + f"Phone {i+1}", lambda checked, phone_id=i: self.phone_action(phone_id) + ) + self.phones.append({ + 'id': i, + 'client': client, + 'state': PhoneState.IDLE, + 'button': phone_button, + 'waveform': phone_waveform, + 'number': "123-4567" if i == 0 else "987-6543", + 'audio_timer': None, + 'status_label': phone_status_label + }) + phone_controls_layout.addWidget(phone_widget_container) # Spacer main_layout.addStretch(1) @@ -215,8 +215,8 @@ class PhoneUI(QMainWindow): 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) + for phone in self.phones: + self._update_phone_button_ui(phone['button'], phone['status_label'], phone['state']) def _create_phone_ui(self, title, action_slot): phone_container_widget = QWidget() @@ -257,46 +257,78 @@ class PhoneUI(QMainWindow): 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 + return phone_container_widget, phone_display_frame, phone_button, waveform_widget, phone_status_label - 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_phone_button_ui(self, button, status_label, state, phone_number=""): + 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;") + + def phone_action(self, phone_id): + phone = self.phones[phone_id] + other_phone = self.phones[1 - phone_id] + print(f"Phone {phone_id + 1} Action, current state: {phone['state']}") + + if phone['state'] == PhoneState.IDLE: + # Initiate a call + phone['state'] = PhoneState.CALLING + other_phone['state'] = PhoneState.RINGING + self._update_phone_button_ui(phone['button'], phone['status_label'], phone['state'], other_phone['number']) + self._update_phone_button_ui(other_phone['button'], other_phone['status_label'], other_phone['state'], phone['number']) + phone['client'].send("RINGING") + + elif phone['state'] == PhoneState.RINGING: + # Answer the call + phone['state'] = PhoneState.IN_CALL + other_phone['state'] = PhoneState.IN_CALL + self._update_phone_button_ui(phone['button'], phone['status_label'], phone['state'], other_phone['number']) + self._update_phone_button_ui(other_phone['button'], other_phone['status_label'], other_phone['state'], phone['number']) + phone['client'].send("IN_CALL") + # Start audio timers for both phones + for p in [phone, other_phone]: + if not p['audio_timer'] or not p['audio_timer'].isActive(): + p['audio_timer'] = QTimer(self) + p['audio_timer'].timeout.connect(lambda pid=p['id']: self.send_audio(pid)) + p['audio_timer'].start(1000) + + elif phone['state'] == PhoneState.IN_CALL or phone['state'] == PhoneState.CALLING: + # Hang up or cancel + phone['state'] = PhoneState.IDLE + other_phone['state'] = PhoneState.IDLE + self._update_phone_button_ui(phone['button'], phone['status_label'], phone['state'], "") + self._update_phone_button_ui(other_phone['button'], other_phone['status_label'], other_phone['state'], "") + phone['client'].send("CALL_END") + # Stop audio timers for both phones + for p in [phone, other_phone]: + if p['audio_timer']: + p['audio_timer'].stop() + + def send_audio(self, phone_id): + phone = self.phones[phone_id] + if phone['state'] == PhoneState.IN_CALL: + message = f"Audio packet {random.randint(1, 1000)}" + phone['client'].send(message) 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 = self.phones[client_id]['waveform'] waveform.set_data(data) def map_state(self, state_str): @@ -306,106 +338,31 @@ class PhoneUI(QMainWindow): return PhoneState.IDLE elif state_str == "IN_CALL": return PhoneState.IN_CALL - return PhoneState.IDLE # Default to IDLE + return PhoneState.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) + phone = self.phones[client_id] + other_phone = self.phones[1 - client_id] + phone['state'] = state + if state == PhoneState.RINGING: + self._update_phone_button_ui(phone['button'], phone['status_label'], state, other_phone['number']) + elif state == PhoneState.IN_CALL: + self._update_phone_button_ui(phone['button'], phone['status_label'], state, other_phone['number']) 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) + self._update_phone_button_ui(phone['button'], phone['status_label'], state, "") + if state == PhoneState.IDLE and phone['audio_timer']: + phone['audio_timer'].stop() + elif state == PhoneState.IN_CALL and (not phone['audio_timer'] or not phone['audio_timer'].isActive()): + phone['audio_timer'] = QTimer(self) + phone['audio_timer'].timeout.connect(lambda: self.send_audio(client_id)) + phone['audio_timer'].start(1000) def settings_action(self): print("Settings clicked") def closeEvent(self, event): - self.phone1_client.stop() - self.phone2_client.stop() + for phone in self.phones: + phone['client'].stop() event.accept() if __name__ == "__main__": diff --git a/protocol_prototype/DryBox/input_8k_mono.wav.gsm b/protocol_prototype/DryBox/input_8k_mono.wav.gsm deleted file mode 100644 index 4ffd55e..0000000 Binary files a/protocol_prototype/DryBox/input_8k_mono.wav.gsm and /dev/null differ diff --git a/protocol_prototype/DryBox/Dockerfile b/protocol_prototype/DryBox/simulator/Dockerfile similarity index 100% rename from protocol_prototype/DryBox/Dockerfile rename to protocol_prototype/DryBox/simulator/Dockerfile diff --git a/protocol_prototype/DryBox/gsm_simulator.py b/protocol_prototype/DryBox/simulator/gsm_simulator.py similarity index 100% rename from protocol_prototype/DryBox/gsm_simulator.py rename to protocol_prototype/DryBox/simulator/gsm_simulator.py diff --git a/protocol_prototype/DryBox/launch_gsm_simulator.sh b/protocol_prototype/DryBox/simulator/launch_gsm_simulator.sh similarity index 68% rename from protocol_prototype/DryBox/launch_gsm_simulator.sh rename to protocol_prototype/DryBox/simulator/launch_gsm_simulator.sh index 99b0a72..74971d1 100755 --- a/protocol_prototype/DryBox/launch_gsm_simulator.sh +++ b/protocol_prototype/DryBox/simulator/launch_gsm_simulator.sh @@ -5,7 +5,8 @@ # Variables IMAGE_NAME="gsm-simulator" CONTAINER_NAME="gsm-sim" -PORT="5555" +PORT="12345" +LOG_FILE="gsm_simulator.log" # Check if Docker is installed if ! command -v docker &> /dev/null; then @@ -13,7 +14,7 @@ if ! command -v docker &> /dev/null; then exit 1 fi -# Check if the gsm_simulator.py file exists in the current directory +# Check if gsm_simulator.py exists if [ ! -f "gsm_simulator.py" ]; then echo "Error: gsm_simulator.py not found in the current directory." echo "Please ensure gsm_simulator.py is present and try again." @@ -26,11 +27,16 @@ if [ ! -f "Dockerfile" ]; then cat < Dockerfile FROM python:3.9-slim WORKDIR /app -COPY gsm_simulator.py /app +COPY gsm_simulator.py . +EXPOSE 12345 CMD ["python", "gsm_simulator.py"] EOF fi +# Ensure log file is writable +touch $LOG_FILE +chmod 666 $LOG_FILE + # Build the Docker image echo "Building Docker image: $IMAGE_NAME..." docker build -t $IMAGE_NAME . @@ -41,7 +47,7 @@ if [ $? -ne 0 ]; then exit 1 fi -# Stop and remove any existing container with the same name +# Stop and remove any existing container if [ "$(docker ps -q -f name=$CONTAINER_NAME)" ]; then echo "Stopping existing container: $CONTAINER_NAME..." docker stop $CONTAINER_NAME @@ -51,16 +57,12 @@ if [ "$(docker ps -aq -f name=$CONTAINER_NAME)" ]; then docker rm $CONTAINER_NAME fi -# Run the Docker container -echo "Launching GSM Simulator in Docker container: $CONTAINER_NAME..." -docker run -d -p $PORT:$PORT --name $CONTAINER_NAME $IMAGE_NAME +# Clean up dangling images +docker image prune -f -# Check if the container is running -if [ $? -eq 0 ]; then - echo "GSM Simulator is running on port $PORT." - echo "Container ID: $(docker ps -q -f name=$CONTAINER_NAME)" - echo "You can now connect your external Python programs to localhost:$PORT." -else - echo "Error: Failed to launch the container." - exit 1 -fi \ No newline at end of file +# Run the Docker container interactively +echo "Launching GSM Simulator in Docker container: $CONTAINER_NAME..." +docker run -it --rm -p $PORT:$PORT --name $CONTAINER_NAME $IMAGE_NAME | tee $LOG_FILE + +# Note: Script will block here until container exits +echo "GSM Simulator stopped. Logs saved to $LOG_FILE." \ No newline at end of file diff --git a/protocol_prototype/DryBox/external_caller.py b/protocol_prototype/DryBox/unused/external_caller.py similarity index 92% rename from protocol_prototype/DryBox/external_caller.py rename to protocol_prototype/DryBox/unused/external_caller.py index f96da67..9b06026 100644 --- a/protocol_prototype/DryBox/external_caller.py +++ b/protocol_prototype/DryBox/unused/external_caller.py @@ -5,7 +5,7 @@ import time def connect(): caller_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - caller_socket.connect(('localhost', 5555)) + caller_socket.connect(('localhost', 12345)) caller_socket.send("CALLER".encode()) print("Connected to GSM simulator as CALLER") time.sleep(2) # Wait 2 seconds for receiver to connect diff --git a/protocol_prototype/DryBox/external_receiver.py b/protocol_prototype/DryBox/unused/external_receiver.py similarity index 95% rename from protocol_prototype/DryBox/external_receiver.py rename to protocol_prototype/DryBox/unused/external_receiver.py index 3c5f8cd..20c02de 100644 --- a/protocol_prototype/DryBox/external_receiver.py +++ b/protocol_prototype/DryBox/unused/external_receiver.py @@ -4,7 +4,7 @@ import socket def connect(): receiver_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) receiver_socket.settimeout(15) # Increase timeout to 15 seconds - receiver_socket.connect(('localhost', 5555)) + receiver_socket.connect(('localhost', 12345)) receiver_socket.send("RECEIVER".encode()) print("Connected to GSM simulator as RECEIVER") diff --git a/protocol_prototype/DryBox/protocol.py b/protocol_prototype/DryBox/unused/protocol.py similarity index 98% rename from protocol_prototype/DryBox/protocol.py rename to protocol_prototype/DryBox/unused/protocol.py index ee4d82e..4c3cc79 100644 --- a/protocol_prototype/DryBox/protocol.py +++ b/protocol_prototype/DryBox/unused/protocol.py @@ -6,8 +6,8 @@ import subprocess # Configuration HOST = "localhost" PORT = 12345 -INPUT_FILE = "input.wav" -OUTPUT_FILE = "received.wav" +INPUT_FILE = "wav/input.wav" +OUTPUT_FILE = "wav/received.wav" def encrypt_data(data): diff --git a/protocol_prototype/DryBox/input.wav b/protocol_prototype/DryBox/wav/input.wav similarity index 100% rename from protocol_prototype/DryBox/input.wav rename to protocol_prototype/DryBox/wav/input.wav diff --git a/protocol_prototype/DryBox/input_8k_mono.wav b/protocol_prototype/DryBox/wav/input_8k_mono.wav similarity index 100% rename from protocol_prototype/DryBox/input_8k_mono.wav rename to protocol_prototype/DryBox/wav/input_8k_mono.wav diff --git a/protocol_prototype/DryBox/received.wav b/protocol_prototype/DryBox/wav/received.wav similarity index 100% rename from protocol_prototype/DryBox/received.wav rename to protocol_prototype/DryBox/wav/received.wav diff --git a/protocol_prototype/requirements.txt b/protocol_prototype/requirements.txt new file mode 100644 index 0000000..731ba4c --- /dev/null +++ b/protocol_prototype/requirements.txt @@ -0,0 +1,6 @@ +# System install +Docker +Python3 + +# Venv install +PyQt5 \ No newline at end of file