diff --git a/ArduinoCommunication.py b/ArduinoCommunication.py index ecc8609..4ba0bb8 100644 --- a/ArduinoCommunication.py +++ b/ArduinoCommunication.py @@ -11,7 +11,7 @@ class ArduinoCommunication(Thread): def __init__(self, port=cfg.port, baudrate=cfg.baudrate, timeout=cfg.comm_timeout): super().__init__() 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 self.command_queue = Queue() @@ -19,6 +19,9 @@ class ArduinoCommunication(Thread): self.running = Event() self.running.set() + def send_motion(self, radial_array): + self.write(radial_array) + def run(self): while self.running.is_set(): # 1. Befehle senden @@ -70,5 +73,12 @@ class ArduinoCommunication(Thread): def stop(self): self.running.clear() - self.join() - self.serial_conn.close() \ No newline at end of file + try: + 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() \ No newline at end of file diff --git a/EspCommunication.py b/EspCommunication.py index 60cbafd..c037ad7 100644 --- a/EspCommunication.py +++ b/EspCommunication.py @@ -25,10 +25,14 @@ HEADER_FMT = " None: - if self.comm_channel and hasattr(self.comm_channel, 'close'): - self.comm_channel.close() + if self.comm_channel: + if hasattr(self.comm_channel, 'stop'): + self.comm_channel.stop() + elif hasattr(self.comm_channel, 'close'): + self.comm_channel.close() class PyBulletBackend: @@ -123,9 +126,11 @@ class Robot: self.backend: RobotBackend = PyBulletBackend(sim_instance) elif backend_type == BackendType.ESP32: comm = ESP32Communication(ip=cfg.esp32_ip, port=cfg.esp32_port) + comm.start() self.backend = HardwareBackend(comm) elif backend_type == BackendType.ARDUINO: comm = ArduinoCommunication(port=cfg.port, baudrate=cfg.baudrate) + comm.start() self.backend = HardwareBackend(comm) else: self.backend = backend_type @@ -188,7 +193,11 @@ class Robot: self.current_state = STATE_REGISTRY[next_state_key] 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 if self.mode == "direct": @@ -200,12 +209,12 @@ class Robot: if action is not None: self.apply_rl_action_delta(action) - else: # kinematics mode - self.step_kinematic_gait(vx, vy, omega) + else: # "kinematics" / standard State Machine execution + 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 - for _ in range(physics_substeps): - self.step_sim() + self.step_sim() def step_kinematic_gait(self, vx: float, vy: float, omega: float) -> None: """Procedural Tripod Gait solver.""" @@ -261,17 +270,9 @@ class Robot: self.set_joint_angles(target_rad) def apply_rl_action(self, action: np.ndarray) -> None: - """ - Applies direct RL joint action deltas to the current joint positions. - """ - 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) + action = np.asarray(action, dtype=np.float32) + new_rad = dt.RadArray(data=action.reshape(self.current_rad.data.shape)) + self.set_joint_angles(new_rad) def apply_rl_action_delta(self, action: np.ndarray) -> None: """Applies action deltas on top of joint state for Residual RL.""" diff --git a/config.py b/config.py index 1476505..a0b2b45 100644 --- a/config.py +++ b/config.py @@ -38,6 +38,14 @@ class RobotConfig: tick_rate_hz: float = 25.0 # Motion loop execution rate [ticks/sec] 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 def tick_duration(self) -> float: return 1.0 / self.tick_rate_hz