Complete Restructered Robot Code
Robot into its own Class instead of lose Global Variables that cause circular imports StateClass usage instead of the old RobotState.py New Input Class for Controller and randome intputs
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
"""
|
||||
inputs/InputProvider.py - Abstraction layer for robot control inputs
|
||||
"""
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List
|
||||
|
||||
|
||||
@dataclass
|
||||
class CommandFrame:
|
||||
"""Represents a snapshot of control inputs at a single tick."""
|
||||
state: str = "idle"
|
||||
vector_dirmov: List[float] = field(default_factory=lambda: [0.0, 0.0, 0.0]) # [vx, vy, omega]
|
||||
emote: str = ""
|
||||
|
||||
|
||||
class InputProvider(ABC):
|
||||
"""Abstract Base Class for input providers (Joystick, Random/RL, Scripted, etc.)."""
|
||||
|
||||
@abstractmethod
|
||||
def get_command(self) -> CommandFrame:
|
||||
"""Polls or generates the latest input command frame."""
|
||||
pass
|
||||
|
||||
def stop(self) -> None:
|
||||
"""Optional cleanup when shutting down input loop."""
|
||||
pass
|
||||
@@ -0,0 +1,129 @@
|
||||
"""
|
||||
inputs/PygameController.py - Pygame Joystick Input Provider with Corrected Axes
|
||||
"""
|
||||
import math
|
||||
from multiprocessing import Queue
|
||||
import pygame
|
||||
|
||||
from inputs.InputProvider import InputProvider, CommandFrame
|
||||
|
||||
|
||||
class PygameController(InputProvider):
|
||||
def __init__(self, deadzone: float = 0.2):
|
||||
pygame.init()
|
||||
pygame.joystick.init()
|
||||
|
||||
self.screen = pygame.display.set_mode((400, 300))
|
||||
pygame.display.set_caption("JackBot Controller Input")
|
||||
self.font = pygame.font.Font(None, 24)
|
||||
|
||||
self.deadzone = deadzone
|
||||
self.joysticks = {}
|
||||
self.clock = pygame.time.Clock()
|
||||
self.current_command = CommandFrame(state="idle", vector_dirmov=[0.0, 0.0, 0.0])
|
||||
|
||||
def _normalize_input(self, x: float, y: float) -> tuple[float, float]:
|
||||
magnitude = math.sqrt(x**2 + y**2)
|
||||
if magnitude > 1.0:
|
||||
x /= magnitude
|
||||
y /= magnitude
|
||||
return x, y
|
||||
|
||||
def update(self) -> CommandFrame:
|
||||
vx, vy, omega = 0.0, 0.0, 0.0
|
||||
emote = ""
|
||||
|
||||
# Process Pygame events
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.QUIT:
|
||||
pass
|
||||
elif event.type == pygame.JOYDEVICEADDED:
|
||||
joy = pygame.joystick.Joystick(event.device_index)
|
||||
self.joysticks[joy.get_instance_id()] = joy
|
||||
print(f"Joystick {joy.get_instance_id()} connected: {joy.get_name()}")
|
||||
elif event.type == pygame.JOYDEVICEREMOVED:
|
||||
if event.instance_id in self.joysticks:
|
||||
print(f"Joystick {event.instance_id} disconnected")
|
||||
del self.joysticks[event.instance_id]
|
||||
elif event.type == pygame.JOYBUTTONDOWN:
|
||||
if event.button == 3:
|
||||
emote = "wave"
|
||||
|
||||
# Read active controller stick states
|
||||
if self.joysticks:
|
||||
for joystick in self.joysticks.values():
|
||||
# Axis 1 = Left Stick Up/Down (Negated so Forward = +1.0)
|
||||
# Axis 0 = Left Stick Left/Right (Right = +1.0)
|
||||
raw_vx = -joystick.get_axis(1) # Forward (+) / Backward (-)
|
||||
raw_vy = joystick.get_axis(0) # Right (+) / Left (-)
|
||||
|
||||
# Check Axis 3 first for Right Stick X, fallback to Axis 2
|
||||
num_axes = joystick.get_numaxes()
|
||||
raw_omega = joystick.get_axis(3) if num_axes > 3 else joystick.get_axis(2)
|
||||
|
||||
norm_vx, norm_vy = self._normalize_input(raw_vx, raw_vy)
|
||||
|
||||
# Apply deadzones
|
||||
if abs(raw_omega) >= self.deadzone:
|
||||
omega = raw_omega
|
||||
if abs(norm_vx) >= self.deadzone or abs(norm_vy) >= self.deadzone:
|
||||
vx, vy = norm_vx, norm_vy
|
||||
|
||||
# Axis snapping for clean cardinal walking
|
||||
if 0.8 < vx and -0.2 < vy < 0.2:
|
||||
vx, vy = 1.0, 0.0
|
||||
elif vx < -0.8 and -0.2 < vy < 0.2:
|
||||
vx, vy = -1.0, 0.0
|
||||
elif -0.2 < vx < 0.2 and 0.8 < vy:
|
||||
vx, vy = 0.0, 1.0
|
||||
elif -0.2 < vx < 0.2 and vy < -0.8:
|
||||
vx, vy = 0.0, -1.0
|
||||
|
||||
# Determine robot state
|
||||
state = "walking" if (vx != 0.0 or vy != 0.0 or omega != 0.0) else "idle"
|
||||
|
||||
self.current_command = CommandFrame(
|
||||
state=state,
|
||||
vector_dirmov=[vx, vy, omega],
|
||||
emote=emote
|
||||
)
|
||||
|
||||
# Render visual display
|
||||
self.screen.fill((30, 30, 30))
|
||||
|
||||
axis_count = list(self.joysticks.values())[0].get_numaxes() if self.joysticks else 0
|
||||
|
||||
lines = [
|
||||
f"Connected Joysticks: {len(self.joysticks)} (Axes: {axis_count})",
|
||||
f"Robot State: {self.current_command.state.upper()}",
|
||||
f"Vx (Fwd/Bwd): {vx:>6.2f}",
|
||||
f"Vy (Strafe) : {vy:>6.2f}",
|
||||
f"Omega (Yaw) : {omega:>6.2f}",
|
||||
]
|
||||
for i, line in enumerate(lines):
|
||||
txt = self.font.render(line, True, (255, 255, 255))
|
||||
self.screen.blit(txt, (20, 20 + i * 30))
|
||||
|
||||
pygame.display.flip()
|
||||
return self.current_command
|
||||
|
||||
def get_command(self) -> CommandFrame:
|
||||
return self.update()
|
||||
|
||||
def stop(self) -> None:
|
||||
pygame.quit()
|
||||
|
||||
|
||||
def controller_loop(control_queue: Queue):
|
||||
provider = PygameController()
|
||||
try:
|
||||
while True:
|
||||
cmd = provider.get_command()
|
||||
control_queue.put({
|
||||
"state": cmd.state,
|
||||
"dirmov": cmd.vector_dirmov,
|
||||
"emote": cmd.emote
|
||||
})
|
||||
provider.clock.tick(30)
|
||||
finally:
|
||||
provider.stop()
|
||||
@@ -0,0 +1,35 @@
|
||||
"""
|
||||
inputs/RandomInputProvider.py - Random Command Generator for Simulation / Training
|
||||
"""
|
||||
import random
|
||||
import time
|
||||
from inputs.InputProvider import InputProvider, CommandFrame
|
||||
|
||||
|
||||
class RandomInputProvider(InputProvider):
|
||||
def __init__(self, change_interval: float = 2.0):
|
||||
self.change_interval = change_interval
|
||||
self.last_change = time.time()
|
||||
self.current_command = CommandFrame()
|
||||
|
||||
def get_command(self) -> CommandFrame:
|
||||
now = time.time()
|
||||
if now - self.last_change > self.change_interval:
|
||||
self.last_change = now
|
||||
|
||||
# 20% chance to idle, 80% to walk randomly
|
||||
if random.random() < 0.2:
|
||||
self.current_command = CommandFrame(
|
||||
state="idle",
|
||||
vector_dirmov=[0.0, 0.0, 0.0]
|
||||
)
|
||||
else:
|
||||
vx = random.uniform(-1.0, 1.0)
|
||||
vy = random.uniform(-1.0, 1.0)
|
||||
omega = random.uniform(-1.0, 1.0)
|
||||
self.current_command = CommandFrame(
|
||||
state="walking",
|
||||
vector_dirmov=[vx, vy, omega]
|
||||
)
|
||||
|
||||
return self.current_command
|
||||
Reference in New Issue
Block a user