Compare commits

..

3 Commits

Author SHA1 Message Date
JackM323 a649865643 Hardware Connection fix 2026-09-20 13:01:36 +02:00
JackM323 b6cb5bb6a1 log upload 2026-09-15 18:31:41 +02:00
JackM323 5a1ac694e0 env output missing variable 2026-09-13 13:32:23 +02:00
8 changed files with 56 additions and 26 deletions
+1 -1
View File
@@ -8,7 +8,7 @@ ml/checkpoints/*
!ml/checkpoints/jackbot_ppo6_149994_steps.zip !ml/checkpoints/jackbot_ppo6_149994_steps.zip
!ml/checkpoints/jackbot_ppo6_199992_steps.zip !ml/checkpoints/jackbot_ppo6_199992_steps.zip
!ml/checkpoints/jackbot_ppo6_499980_steps.zip !ml/checkpoints/jackbot_ppo6_499980_steps.zip
ml/logs/ #ml/logs/
# Ignore environment files with private passwords/keys # Ignore environment files with private passwords/keys
.env .env
+13 -3
View File
@@ -11,7 +11,7 @@ class ArduinoCommunication(Thread):
def __init__(self, port=cfg.port, baudrate=cfg.baudrate, timeout=cfg.comm_timeout): def __init__(self, port=cfg.port, baudrate=cfg.baudrate, timeout=cfg.comm_timeout):
super().__init__() super().__init__()
self.daemon = True # Thread schlie�t sich beim Programmende self.daemon = True # Thread schlie�t sich beim Programmende
self.serial_conn = serial.Serial(port, baudrate, timeout=timeout) self.serial_conn = serial.Serial(port, baudrate, timeout=timeout)
time.sleep(2) # Warten bis Arduino ready time.sleep(2) # Warten bis Arduino ready
self.command_queue = Queue() self.command_queue = Queue()
@@ -19,6 +19,9 @@ class ArduinoCommunication(Thread):
self.running = Event() self.running = Event()
self.running.set() self.running.set()
def send_motion(self, radial_array):
self.write(radial_array)
def run(self): def run(self):
while self.running.is_set(): while self.running.is_set():
# 1. Befehle senden # 1. Befehle senden
@@ -70,5 +73,12 @@ class ArduinoCommunication(Thread):
def stop(self): def stop(self):
self.running.clear() self.running.clear()
self.join() try:
self.serial_conn.close() self.join(timeout=0.5)
except RuntimeError:
pass
if self.serial_conn and self.serial_conn.is_open:
self.serial_conn.close()
def close(self):
self.stop()
+13 -3
View File
@@ -25,10 +25,14 @@ HEADER_FMT = "<B B H I" # ID, Ver, Len, Timestamp
TLV_FMT = "<B H" TLV_FMT = "<B H"
class ESP32Communication(Thread): class ESP32Communication(Thread):
def __init__(self, host=cfg.esp32_ip, port=cfg.esp32_port): def __init__(self, host=None, port=None, ip=None):
super().__init__(daemon=True) super().__init__(daemon=True)
if ip is not None:
host = ip
host = cfg.esp32_ip if host is None else host
port = cfg.esp32_port if port is None else port
self.addr = (host, port) self.addr = (host, port)
# UDP Socket - Zero Lag # UDP Socket - Zero Lag
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
@@ -126,4 +130,10 @@ class ESP32Communication(Thread):
def stop(self): def stop(self):
self.running.clear() self.running.clear()
self.sock.close() try:
self.sock.close()
except Exception:
pass
def close(self):
self.stop()
+20 -19
View File
@@ -59,8 +59,11 @@ class HardwareBackend:
return [0.0, 0.0, 0.0], [0.0, 0.0, 0.0] return [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]
def cleanup(self) -> None: def cleanup(self) -> None:
if self.comm_channel and hasattr(self.comm_channel, 'close'): if self.comm_channel:
self.comm_channel.close() if hasattr(self.comm_channel, 'stop'):
self.comm_channel.stop()
elif hasattr(self.comm_channel, 'close'):
self.comm_channel.close()
class PyBulletBackend: class PyBulletBackend:
@@ -123,9 +126,11 @@ class Robot:
self.backend: RobotBackend = PyBulletBackend(sim_instance) self.backend: RobotBackend = PyBulletBackend(sim_instance)
elif backend_type == BackendType.ESP32: elif backend_type == BackendType.ESP32:
comm = ESP32Communication(ip=cfg.esp32_ip, port=cfg.esp32_port) comm = ESP32Communication(ip=cfg.esp32_ip, port=cfg.esp32_port)
comm.start()
self.backend = HardwareBackend(comm) self.backend = HardwareBackend(comm)
elif backend_type == BackendType.ARDUINO: elif backend_type == BackendType.ARDUINO:
comm = ArduinoCommunication(port=cfg.port, baudrate=cfg.baudrate) comm = ArduinoCommunication(port=cfg.port, baudrate=cfg.baudrate)
comm.start()
self.backend = HardwareBackend(comm) self.backend = HardwareBackend(comm)
else: else:
self.backend = backend_type self.backend = backend_type
@@ -188,7 +193,11 @@ class Robot:
self.current_state = STATE_REGISTRY[next_state_key] self.current_state = STATE_REGISTRY[next_state_key]
self.current_state.enter(self) self.current_state.enter(self)
def tick(self, action: Optional[np.ndarray] = None, physics_substeps: int = 4) -> None: def tick(self, action: Optional[np.ndarray] = None) -> None:
"""
Unified control loop tick.
Processes commands through direct RL, residual RL, or State Machine kinematics.
"""
vx, vy, omega = self.vector_dirmov vx, vy, omega = self.vector_dirmov
if self.mode == "direct": if self.mode == "direct":
@@ -200,12 +209,12 @@ class Robot:
if action is not None: if action is not None:
self.apply_rl_action_delta(action) self.apply_rl_action_delta(action)
else: # kinematics mode else: # "kinematics" / standard State Machine execution
self.step_kinematic_gait(vx, vy, omega) next_state_key = self.current_state.execute(self)
if next_state_key:
self.transition_to(next_state_key)
# Step PyBullet engine sub-steps to allow physics actuation self.step_sim()
for _ in range(physics_substeps):
self.step_sim()
def step_kinematic_gait(self, vx: float, vy: float, omega: float) -> None: def step_kinematic_gait(self, vx: float, vy: float, omega: float) -> None:
"""Procedural Tripod Gait solver.""" """Procedural Tripod Gait solver."""
@@ -261,17 +270,9 @@ class Robot:
self.set_joint_angles(target_rad) self.set_joint_angles(target_rad)
def apply_rl_action(self, action: np.ndarray) -> None: def apply_rl_action(self, action: np.ndarray) -> None:
""" action = np.asarray(action, dtype=np.float32)
Applies direct RL joint action deltas to the current joint positions. new_rad = dt.RadArray(data=action.reshape(self.current_rad.data.shape))
""" self.set_joint_angles(new_rad)
action_flat = np.asarray(action, dtype=np.float32).flatten()
# Keep internal memory updated in (6, 3) format for Kinematic/IK math
self.current_rad = dt.RadArray(data=action_flat.reshape(6, 3))
# Send target positions to the PyBullet backend
if self.backend:
self.backend.send_angles(self.current_rad)
def apply_rl_action_delta(self, action: np.ndarray) -> None: def apply_rl_action_delta(self, action: np.ndarray) -> None:
"""Applies action deltas on top of joint state for Residual RL.""" """Applies action deltas on top of joint state for Residual RL."""
+8
View File
@@ -38,6 +38,14 @@ class RobotConfig:
tick_rate_hz: float = 25.0 # Motion loop execution rate [ticks/sec] tick_rate_hz: float = 25.0 # Motion loop execution rate [ticks/sec]
step_duration: float = 0.8 # Time to complete full gait stride [s] step_duration: float = 0.8 # Time to complete full gait stride [s]
@property
def standard_tickpersec(self) -> float:
return self.tick_rate_hz
@standard_tickpersec.setter
def standard_tickpersec(self, value: float) -> None:
self.tick_rate_hz = value
@property @property
def tick_duration(self) -> float: def tick_duration(self) -> float:
return 1.0 / self.tick_rate_hz return 1.0 / self.tick_rate_hz
+1
View File
@@ -400,6 +400,7 @@ class JackBotEnv(gym.Env):
"ang_vel": float(r_ang_vel), "ang_vel": float(r_ang_vel),
"jitter_penalty": float(jitter_penalty), "jitter_penalty": float(jitter_penalty),
"stand_penalty": float(stand_penalty), "stand_penalty": float(stand_penalty),
"stillness_penalty": float(stillness_penalty),
"total": final_reward, "total": final_reward,
} }
for k, v in self.last_reward_components.items(): for k, v in self.last_reward_components.items():
Binary file not shown.