feat: add remote controller server
This commit is contained in:
@@ -4,10 +4,10 @@ from typing import Optional
|
|||||||
import pygame
|
import pygame
|
||||||
|
|
||||||
from src.camera import Camera
|
from src.camera import Camera
|
||||||
|
from src.remote_controller import RemoteController
|
||||||
from src.utils import get_segments_intersection, segments_intersect
|
from src.utils import get_segments_intersection, segments_intersect
|
||||||
from src.vec import Vec
|
from src.vec import Vec
|
||||||
|
|
||||||
|
|
||||||
sign = lambda x: 0 if x == 0 else (-1 if x < 0 else 1)
|
sign = lambda x: 0 if x == 0 else (-1 if x < 0 else 1)
|
||||||
|
|
||||||
|
|
||||||
@@ -38,6 +38,9 @@ class Car:
|
|||||||
self.rays: list[float] = [0] * self.N_RAYS
|
self.rays: list[float] = [0] * self.N_RAYS
|
||||||
self.rays_end: list[Vec] = [Vec() for _ in range(self.N_RAYS)]
|
self.rays_end: list[Vec] = [Vec() for _ in range(self.N_RAYS)]
|
||||||
|
|
||||||
|
self.controller: RemoteController = RemoteController(self)
|
||||||
|
self.controller.start_server()
|
||||||
|
|
||||||
def update(self, dt: float):
|
def update(self, dt: float):
|
||||||
if self.forward:
|
if self.forward:
|
||||||
self.speed += self.ACCELERATION * dt
|
self.speed += self.ACCELERATION * dt
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ class Game:
|
|||||||
while self.running:
|
while self.running:
|
||||||
dt: float = self.clock.get_time() / 1000
|
dt: float = self.clock.get_time() / 1000
|
||||||
self.process_pygame_events()
|
self.process_pygame_events()
|
||||||
|
self.car.controller.process_commands()
|
||||||
self.car.update(dt)
|
self.car.update(dt)
|
||||||
self.car.check_collisions(self.track.get_collision_polygons())
|
self.car.check_collisions(self.track.get_collision_polygons())
|
||||||
self.update_camera()
|
self.update_camera()
|
||||||
@@ -64,6 +65,7 @@ class Game:
|
|||||||
|
|
||||||
def quit(self):
|
def quit(self):
|
||||||
self.running = False
|
self.running = False
|
||||||
|
self.car.controller.close()
|
||||||
|
|
||||||
def render(self):
|
def render(self):
|
||||||
self.win.fill(self.BACKGROUND_COLOR)
|
self.win.fill(self.BACKGROUND_COLOR)
|
||||||
|
|||||||
108
src/remote_controller.py
Normal file
108
src/remote_controller.py
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import queue
|
||||||
|
import socket
|
||||||
|
import struct
|
||||||
|
import threading
|
||||||
|
from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
|
from src.command import CarControl, Command, ControlCommand
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from src.car import Car
|
||||||
|
|
||||||
|
|
||||||
|
class RemoteController:
|
||||||
|
DEFAULT_PORT = 5000
|
||||||
|
DATA_CHUNK_SIZE = 4096
|
||||||
|
|
||||||
|
CONTROL_ATTRIBUTES: dict[CarControl, str] = {
|
||||||
|
CarControl.FORWARD: "forward",
|
||||||
|
CarControl.BACKWARD: "backward",
|
||||||
|
CarControl.LEFT: "left",
|
||||||
|
CarControl.RIGHT: "right",
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, car: Car, port: int = DEFAULT_PORT) -> None:
|
||||||
|
self.car: Car = car
|
||||||
|
self.port: int = port
|
||||||
|
self.server: socket.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
self.server_thread: threading.Thread = threading.Thread(
|
||||||
|
target=self.wait_for_connections, daemon=True
|
||||||
|
)
|
||||||
|
self.running: bool = False
|
||||||
|
self.queue: queue.Queue[Command] = queue.Queue()
|
||||||
|
self.client_thread: Optional[threading.Thread] = None
|
||||||
|
self.client: Optional[socket.socket] = None
|
||||||
|
|
||||||
|
def wait_for_connections(self):
|
||||||
|
self.server.bind(("", self.port))
|
||||||
|
self.server.listen(1)
|
||||||
|
print(f"Remote control server listening on port {self.port}")
|
||||||
|
while self.running:
|
||||||
|
conn, addr = self.server.accept()
|
||||||
|
print(f"Remote connection from {addr}")
|
||||||
|
self.on_client_connected(conn)
|
||||||
|
|
||||||
|
def start_server(self):
|
||||||
|
self.running = True
|
||||||
|
self.server_thread.start()
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
if self.client:
|
||||||
|
self.client.close()
|
||||||
|
self.server.close()
|
||||||
|
self.running = False
|
||||||
|
|
||||||
|
def on_client_connected(self, conn: socket.socket):
|
||||||
|
if self.client:
|
||||||
|
print("A client is already connected")
|
||||||
|
conn.close()
|
||||||
|
return
|
||||||
|
|
||||||
|
self.client = conn
|
||||||
|
self.client_thread = threading.Thread(target=self.client_loop)
|
||||||
|
self.client_thread.start()
|
||||||
|
|
||||||
|
def client_loop(self):
|
||||||
|
buffer: bytes = b""
|
||||||
|
while self.running and self.client:
|
||||||
|
chunk: bytes = self.client.recv(self.DATA_CHUNK_SIZE)
|
||||||
|
if not chunk:
|
||||||
|
print("Client disconnected")
|
||||||
|
break
|
||||||
|
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)
|
||||||
|
|
||||||
|
if self.client:
|
||||||
|
self.client.close()
|
||||||
|
self.client = None
|
||||||
|
self.client_thread = None
|
||||||
|
|
||||||
|
def on_message(self, message: bytes):
|
||||||
|
command: Command = Command.unpack(message)
|
||||||
|
self.queue.put(command)
|
||||||
|
|
||||||
|
def process_commands(self):
|
||||||
|
while not self.queue.empty():
|
||||||
|
command: Command = self.queue.get()
|
||||||
|
self.process_command(command)
|
||||||
|
|
||||||
|
def process_command(self, command: Command):
|
||||||
|
match command:
|
||||||
|
case ControlCommand(control, active):
|
||||||
|
self.set_control(control, active)
|
||||||
|
|
||||||
|
def set_control(self, control: CarControl, active: bool):
|
||||||
|
setattr(self.car, self.CONTROL_ATTRIBUTES[control], active)
|
||||||
Reference in New Issue
Block a user