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