feat: implement controller interface
This commit is contained in:
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()
|
||||||
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)
|
||||||
Reference in New Issue
Block a user