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_delta: Vec = Vec(dx, dy) * self.zoom * self.UNIT_RATIO
|
||||||
screen_pos: Vec = self.car_screen_pos + screen_delta
|
screen_pos: Vec = self.car_screen_pos + screen_delta
|
||||||
return screen_pos
|
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
|
MAX_BACK_SPEED = -3
|
||||||
ROTATE_SPEED = 1
|
ROTATE_SPEED = 1
|
||||||
COLOR = (230, 150, 80)
|
COLOR = (230, 150, 80)
|
||||||
|
CTRL_COLOR = (80, 230, 150)
|
||||||
WIDTH = 0.4
|
WIDTH = 0.4
|
||||||
LENGTH = 0.6
|
LENGTH = 0.6
|
||||||
COLLISION_MARGIN = 0.4
|
COLLISION_MARGIN = 0.4
|
||||||
@@ -82,6 +83,14 @@ class Car:
|
|||||||
pts = [camera.world2screen(p) for p in pts]
|
pts = [camera.world2screen(p) for p in pts]
|
||||||
pygame.draw.polygon(surf, self.COLOR, 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]:
|
def get_corners(self) -> list[Vec]:
|
||||||
u: Vec = self.direction * self.LENGTH / 2
|
u: Vec = self.direction * self.LENGTH / 2
|
||||||
v: Vec = self.direction.perp * self.WIDTH / 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_thread: Optional[threading.Thread] = None
|
||||||
self.client: Optional[socket.socket] = None
|
self.client: Optional[socket.socket] = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_connected(self) -> bool:
|
||||||
|
return self.client is not None
|
||||||
|
|
||||||
def wait_for_connections(self):
|
def wait_for_connections(self):
|
||||||
self.server.bind(("", self.port))
|
self.server.bind(("", self.port))
|
||||||
self.server.listen(1)
|
self.server.listen(1)
|
||||||
|
|||||||
Reference in New Issue
Block a user