Compare commits
	
		
			2 Commits
		
	
	
		
			bf1935fd7e
			...
			8542ee81e7
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 8542ee81e7 | |||
| f91a4e8d61 | 
							
								
								
									
										23
									
								
								scripts/recorder.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								scripts/recorder.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| from PyQt6.QtWidgets import QApplication | ||||
|  | ||||
| from src.recorder import RecorderWindow | ||||
|  | ||||
|  | ||||
| def main(): | ||||
|     import sys | ||||
|  | ||||
|     def except_hook(cls, exception, traceback): | ||||
|         sys.__excepthook__(cls, exception, traceback) | ||||
|  | ||||
|     sys.excepthook = except_hook | ||||
|  | ||||
|     app = QApplication(sys.argv) | ||||
|     window = RecorderWindow("localhost", 5000) | ||||
|     app.aboutToQuit.connect(window.shutdown) | ||||
|     window.show() | ||||
|  | ||||
|     app.exec() | ||||
|  | ||||
|  | ||||
| if __name__ == "__main__": | ||||
|     main() | ||||
| @@ -45,3 +45,6 @@ class Camera: | ||||
|         screen_delta: Vec = Vec(dx, dy) * self.zoom * self.UNIT_RATIO | ||||
|         screen_pos: Vec = self.car_screen_pos + screen_delta | ||||
|         return screen_pos | ||||
|  | ||||
|     def size2screen(self, size: float) -> float: | ||||
|         return size * self.zoom * self.UNIT_RATIO | ||||
|   | ||||
| @@ -16,6 +16,7 @@ class Car: | ||||
|     MAX_BACK_SPEED = -3 | ||||
|     ROTATE_SPEED = 1 | ||||
|     COLOR = (230, 150, 80) | ||||
|     CTRL_COLOR = (80, 230, 150) | ||||
|     WIDTH = 0.4 | ||||
|     LENGTH = 0.6 | ||||
|     COLLISION_MARGIN = 0.4 | ||||
| @@ -82,6 +83,14 @@ class Car: | ||||
|         pts = [camera.world2screen(p) for p in pts] | ||||
|         pygame.draw.polygon(surf, self.COLOR, pts) | ||||
|  | ||||
|         if self.controller.is_connected: | ||||
|             pygame.draw.circle( | ||||
|                 surf, | ||||
|                 self.CTRL_COLOR, | ||||
|                 camera.world2screen(self.pos), | ||||
|                 camera.size2screen(self.WIDTH / 4), | ||||
|             ) | ||||
|  | ||||
|     def get_corners(self) -> list[Vec]: | ||||
|         u: Vec = self.direction * self.LENGTH / 2 | ||||
|         v: Vec = self.direction.perp * self.WIDTH / 2 | ||||
|   | ||||
							
								
								
									
										181
									
								
								src/recorder.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										181
									
								
								src/recorder.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,181 @@ | ||||
| import socket | ||||
| import struct | ||||
|  | ||||
| from PyQt6 import uic | ||||
| from PyQt6.QtCore import QObject, Qt, QThread, QTimer, pyqtSignal, pyqtSlot | ||||
| from PyQt6.QtWidgets import QMainWindow | ||||
|  | ||||
| from src.command import CarControl, Command, ControlCommand | ||||
| from src.recorder_ui import Ui_Recorder | ||||
| from src.snapshot import Snapshot | ||||
|  | ||||
|  | ||||
| class RecorderClient(QObject): | ||||
|     DATA_CHUNK_SIZE = 4096 | ||||
|     data_received: pyqtSignal = pyqtSignal(Snapshot) | ||||
|  | ||||
|     def __init__(self, host: str, port: int) -> None: | ||||
|         super().__init__() | ||||
|         self.host: str = host | ||||
|         self.port: int = port | ||||
|         self.socket: socket.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | ||||
|         self.timer: QTimer = QTimer(self) | ||||
|         self.timer.timeout.connect(self.poll_socket) | ||||
|         self.connected: bool = False | ||||
|  | ||||
|     @pyqtSlot() | ||||
|     def start(self): | ||||
|         self.socket.connect((self.host, self.port)) | ||||
|         self.socket.setblocking(False) | ||||
|         self.connected = True | ||||
|         self.timer.start(50) | ||||
|         print(f"Connected to server") | ||||
|  | ||||
|     def poll_socket(self): | ||||
|         buffer: bytes = b"" | ||||
|         if not self.connected: | ||||
|             return | ||||
|  | ||||
|         try: | ||||
|             chunk: bytes = self.socket.recv(self.DATA_CHUNK_SIZE) | ||||
|             if not chunk: | ||||
|                 return | ||||
|             buffer += chunk | ||||
|  | ||||
|             while True: | ||||
|                 if len(buffer) < 4: | ||||
|                     break | ||||
|                 msg_len: int = struct.unpack(">I", buffer[:4])[0] | ||||
|                 msg_end: int = 4 + msg_len | ||||
|                 if len(buffer) < msg_end: | ||||
|                     break | ||||
|  | ||||
|                 message: bytes = buffer[4:msg_end] | ||||
|                 buffer = buffer[msg_end:] | ||||
|                 self.on_message(message) | ||||
|         except BlockingIOError: | ||||
|             pass | ||||
|         except Exception as e: | ||||
|             print(f"Socket error: {e}") | ||||
|             self.shutdown() | ||||
|  | ||||
|     def on_message(self, message: bytes): | ||||
|         snapshot: Snapshot = Snapshot.unpack(message) | ||||
|         self.data_received.emit(snapshot) | ||||
|  | ||||
|     @pyqtSlot(object) | ||||
|     def send_command(self, command): | ||||
|         if self.connected: | ||||
|             try: | ||||
|                 payload: bytes = command.pack() | ||||
|                 self.socket.sendall(struct.pack(">I", len(payload)) + payload) | ||||
|             except Exception as e: | ||||
|                 print(f"An exception occured: {e}") | ||||
|                 self.shutdown() | ||||
|         else: | ||||
|             print("Not connected") | ||||
|  | ||||
|     @pyqtSlot() | ||||
|     def shutdown(self): | ||||
|         print("Shutting down client") | ||||
|         self.timer.stop() | ||||
|         self.connected = False | ||||
|         self.socket.close() | ||||
|  | ||||
|  | ||||
| class RecorderWindow(Ui_Recorder, QMainWindow): | ||||
|     close_signal: pyqtSignal = pyqtSignal() | ||||
|     send_signal: pyqtSignal = pyqtSignal(object) | ||||
|  | ||||
|     def __init__(self, host: str, port: int) -> None: | ||||
|         super().__init__() | ||||
|  | ||||
|         self.host: str = host | ||||
|         self.port: int = port | ||||
|         self.client_thread: QThread = QThread() | ||||
|         self.client: RecorderClient = RecorderClient(self.host, self.port) | ||||
|         self.client.data_received.connect(self.on_snapshot_received) | ||||
|         self.client.moveToThread(self.client_thread) | ||||
|         self.client_thread.started.connect(self.client.start) | ||||
|         self.close_signal.connect(self.client.shutdown) | ||||
|         self.send_signal.connect(self.client.send_command) | ||||
|  | ||||
|         uic.load_ui.loadUi("src/recorder.ui", self) | ||||
|  | ||||
|         self.command_directions = { | ||||
|             "w": CarControl.FORWARD, | ||||
|             "s": CarControl.BACKWARD, | ||||
|             "d": CarControl.RIGHT, | ||||
|             "a": CarControl.LEFT, | ||||
|         } | ||||
|  | ||||
|         self.forwardButton.pressed.connect( | ||||
|             lambda: self.on_car_controlled(CarControl.FORWARD, True) | ||||
|         ) | ||||
|         self.forwardButton.released.connect( | ||||
|             lambda: self.on_car_controlled(CarControl.FORWARD, False) | ||||
|         ) | ||||
|  | ||||
|         self.backwardButton.pressed.connect( | ||||
|             lambda: self.on_car_controlled(CarControl.BACKWARD, True) | ||||
|         ) | ||||
|         self.backwardButton.released.connect( | ||||
|             lambda: self.on_car_controlled(CarControl.BACKWARD, False) | ||||
|         ) | ||||
|  | ||||
|         self.rightButton.pressed.connect( | ||||
|             lambda: self.on_car_controlled(CarControl.RIGHT, True) | ||||
|         ) | ||||
|         self.rightButton.released.connect( | ||||
|             lambda: self.on_car_controlled(CarControl.RIGHT, False) | ||||
|         ) | ||||
|  | ||||
|         self.leftButton.pressed.connect( | ||||
|             lambda: self.on_car_controlled(CarControl.LEFT, True) | ||||
|         ) | ||||
|         self.leftButton.released.connect( | ||||
|             lambda: self.on_car_controlled(CarControl.LEFT, False) | ||||
|         ) | ||||
|  | ||||
|         self.recordDataButton.clicked.connect(self.toggle_record) | ||||
|         self.resetButton.clicked.connect(self.rollback) | ||||
|  | ||||
|         self.autopiloting = False | ||||
|  | ||||
|         self.autopilotButton.clicked.connect(self.toggle_autopilot) | ||||
|  | ||||
|         self.saveRecordButton.clicked.connect(self.save_record) | ||||
|  | ||||
|         self.recording = False | ||||
|  | ||||
|         self.recorded_data = [] | ||||
|         self.client_thread.start() | ||||
|  | ||||
|     def on_car_controlled(self, control: CarControl, active: bool): | ||||
|         self.send_command(ControlCommand(control, active)) | ||||
|  | ||||
|     def toggle_record(self): | ||||
|         pass | ||||
|  | ||||
|     def rollback(self): | ||||
|         pass | ||||
|  | ||||
|     def toggle_autopilot(self): | ||||
|         self.autopiloting = not self.autopiloting | ||||
|         self.autopilotButton.setText( | ||||
|             "AutoPilot:\n" + ("ON" if self.autopiloting else "OFF") | ||||
|         ) | ||||
|  | ||||
|     def save_record(self): | ||||
|         pass | ||||
|  | ||||
|     @pyqtSlot(Snapshot) | ||||
|     def on_snapshot_received(self, snapshot: Snapshot): | ||||
|         self.recorded_data.append(snapshot) | ||||
|         self.nbrSnapshotSaved.setText(str(len(self.recorded_data))) | ||||
|  | ||||
|     def shutdown(self): | ||||
|         self.close_signal.emit() | ||||
|  | ||||
|     def send_command(self, command: Command): | ||||
|         self.send_signal.emit(command) | ||||
| @@ -35,6 +35,10 @@ class RemoteController: | ||||
|         self.client_thread: Optional[threading.Thread] = None | ||||
|         self.client: Optional[socket.socket] = None | ||||
|  | ||||
|     @property | ||||
|     def is_connected(self) -> bool: | ||||
|         return self.client is not None | ||||
|  | ||||
|     def wait_for_connections(self): | ||||
|         self.server.bind(("", self.port)) | ||||
|         self.server.listen(1) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user