Files
2026-03-30 14:22:53 +08:00

257 lines
9.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Joystick Control Module
Python equivalent of the C++ Joystick functionality for ROS Joy messages
"""
import os
import time
import yaml
import threading
from dataclasses import dataclass
from typing import Optional
import rclpy
from rclpy.node import Node
from sensor_msgs.msg import Joy
import numpy as np
@dataclass
class ControlFlag:
"""手柄控制标志"""
fsm_state_command: str = "gotoZERO"
# 禁用、启用手柄控制标志
enable: bool = True
@dataclass
class YUNZHUOMap:
"""云卓T12手柄按键映射 (对应ROS Joy消息)"""
a: float = 0.0 # axes[8] #a,b,c,d手柄轴初始值为-1
b: float = 0.0 # axes[9]
c: float = 0.0 # axes[10]
d: float = 0.0 # axes[11]
e: float = 0.0 # axes[4] e,f,g,h手柄轴初始值为0.0
f: float = 0.0 # axes[7]
g: float = 0.0 # axes[5]
h: float = 0.0 # axes[6]
x1: float = 0.0 # axes[3]
x2: float = 0.0 # axes[0]
y1: float = 0.0 # axes[2]
y2: float = 0.0 # axes[1]
class YUNZHUOFlag(ControlFlag): # 继承ControlFlag
def __init__(self):
super().__init__() # 调用父类初始化
# walk command
self.x_speed_command: float = 0.0
self.y_speed_command: float = 0.0
self.yaw_speed_command: float = 0.0
self.walk_height_command: float = 0.0
# floating base command
self.waist_roll_command: float = 0.0
self.waist_pitch_command: float = 0.0
self.waist_yaw_command: float = 0.0
self.waist_height_command: float = 0.0
class JoystickHumanoid:
"""人形机器人手柄控制器 (ROS Joy版本)"""
def __init__(self):
print("Joystick Start")
# 初始化成员变量
self.joy_map = YUNZHUOMap()
self.joy_flag = YUNZHUOFlag()
self.data_mutex = threading.Lock()
# 配置参数
self.initial_height = 0.0
self.current_height = 0.0
self.max_height = 0.0
self.min_height = 0.0
self.x_command_offset = 0.0
self.y_command_offset = 0.0
self.yaw_command_offset = 0.0
self.max_x_plus_speed = 0.0
self.max_x_minus_speed = 0.0
self.max_y_speed = 0.0
self.max_yaw_speed = 0.0
# 高度平滑控制
self.target_height = 0.0
self.last_input_time = 0.0
self.last_fsm_command_time = 0.0
# 加载配置文件
self._load_config()
def _load_config(self):
"""加载YAML配置文件"""
config_path = os.path.join('.', "config", "dex_config.yaml")
with open(config_path, 'r') as file:
config = yaml.safe_load(file)
if not config:
print("[Joystick_humanoid] Failed to load config file")
return
joystick_cfg = config.get("joystick", {})
# 加载配置参数
self.initial_height = joystick_cfg.get("initial_height")
self.x_command_offset = joystick_cfg.get("x_command_offset")
self.y_command_offset = joystick_cfg.get("y_command_offset")
self.yaw_command_offset = joystick_cfg.get("yaw_command_offset")
self.max_x_plus_speed = joystick_cfg.get("max_x_plus_speed")
self.max_x_minus_speed = joystick_cfg.get("max_x_minus_speed")
self.max_y_speed = joystick_cfg.get("max_y_speed")
self.max_yaw_speed = joystick_cfg.get("max_yaw_speed")
self.max_height = joystick_cfg.get("max_height")
self.min_height = joystick_cfg.get("min_height")
print(f"Loaded initial_height: {self.initial_height}, "
f"x_command_offset: {self.x_command_offset}, "
f"y_command_offset: {self.y_command_offset}, "
f"yaw_command_offset: {self.yaw_command_offset},"
f"max_x_plus_speed: {self.max_x_plus_speed}, "
f"max_x_minus_speed: {self.max_x_minus_speed}, "
f"max_y_speed: {self.max_y_speed}, "
f"max_yaw_speed: {self.max_yaw_speed}, "
f"max_height: {self.max_height},"
f"min_height: {self.min_height}")
self.current_height = self.initial_height
self.target_height = self.initial_height
self.joy_flag.waist_height_command = self.current_height
self.joy_flag.walk_height_command = self.current_height
def joy_map_read(self, msg: Joy):
"""处理ROS Joy消息更新手柄映射"""
with self.data_mutex:
if len(msg.axes) >= 12: # 确保有足够的轴数据
yunzhuo_map = YUNZHUOMap(
a=msg.axes[8] if len(msg.axes) > 8 else 0.0,
b=msg.axes[9] if len(msg.axes) > 9 else 0.0,
c=msg.axes[10] if len(msg.axes) > 10 else 0.0,
d=msg.axes[11] if len(msg.axes) > 11 else 0.0,
e=msg.axes[4] if len(msg.axes) > 4 else 0.0,
f=msg.axes[7] if len(msg.axes) > 7 else 0.0,
g=msg.axes[5] if len(msg.axes) > 5 else 0.0,
h=msg.axes[6] if len(msg.axes) > 6 else 0.0,
x1=msg.axes[3] if len(msg.axes) > 3 else 0.0,
x2=msg.axes[0] if len(msg.axes) > 1 else 0.0,
y1=msg.axes[2] if len(msg.axes) > 2 else 0.0,
y2=msg.axes[1] if len(msg.axes) > 0 else 0.0)
if yunzhuo_map != self.joy_map:
self.last_input_time = time.time()
self.joy_map = yunzhuo_map
def joy_flag_update(self):
"""根据手柄输入更新控制标志"""
with self.data_mutex:
fsm_command_updated = False
# 更新手柄启动标志
if self.joy_map.f == -1.0:
self.joy_flag.enable = False
else:
self.joy_flag.enable = True
# FSM状态切换命令
if self.joy_map.c == 1.0:
self.joy_flag.fsm_state_command = "gotoSTOP"
fsm_command_updated = True
else:
button_pressed_nums = self.check_button_pressed_nums(
self.joy_map)
if button_pressed_nums == 0:
if self.joy_map.d == 1.0:
self.joy_flag.fsm_state_command = "gotoZERO"
fsm_command_updated = True
elif self.joy_map.a == 1.0:
self.joy_flag.fsm_state_command = "gotoWALKAMP"
fsm_command_updated = True
# 获取walk速度命令
self.get_x_y_yaw_speed_command()
# 获取高度命令
self.get_walk_height_command()
if button_pressed_nums == 1:
if self.joy_map.e == -1.0:
#e上拨
if self.joy_map.a == 1.0:
self.joy_flag.fsm_state_command = "gotoBEYONDMIMIC"
fsm_command_updated = True
elif self.joy_map.d == 1.0:
self.joy_flag.fsm_state_command = "gotoBEYONDZERO"
fsm_command_updated = True
if fsm_command_updated:
self.last_fsm_command_time = time.time()
def get_joy_flag(self) -> ControlFlag:
"""获取当前手柄标志"""
with self.data_mutex:
return self.joy_flag
def get_last_input_time(self) -> float:
return self.last_input_time
def get_last_fsm_command_time(self) -> float:
return self.last_fsm_command_time
def init(self) -> int:
"""初始化手柄控制器"""
print("Joystick controller initialized")
return 0
def check_button_pressed_nums(self, joy_map: YUNZHUOMap) -> int:
"""检查按下的按钮数量"""
count = 0
if joy_map.e != 0.0:
count += 1
if joy_map.f != 0.0:
count += 1
if joy_map.g != 0.0:
count += 1
if joy_map.h != 0.0:
count += 1
return count
def get_x_y_yaw_speed_command(self):
"""获取当前速度命令"""
# 速度命令计算
self.joy_flag.y_speed_command = (self.joy_map.x1 * -self.max_y_speed +
self.y_command_offset)
# X速度 (前进/后退)
if self.joy_map.y1 >= 0:
self.joy_flag.x_speed_command = (
self.joy_map.y1 * self.max_x_plus_speed + self.x_command_offset
) # 前进快一点
else:
self.joy_flag.x_speed_command = self.joy_map.y1 * self.max_x_minus_speed # 后退慢一点
# 偏航速度
self.joy_flag.yaw_speed_command = (
self.joy_map.x2 * -self.max_yaw_speed + self.yaw_command_offset)
def get_walk_height_command(self):
"""获取当前高度命令"""
current_height_command = self.joy_flag.walk_height_command
deadzone_height = 0.5
# 高度命令计算
if self.joy_map.x2 >= deadzone_height:
# x2 下拨
self.joy_flag.walk_height_command += -self.joy_map.x2 * (
self.joy_flag.walk_height_command - self.min_height)
if self.joy_map.x2 <= -deadzone_height:
# x2 上拨
self.joy_flag.walk_height_command += -self.joy_map.x2 * (
self.max_height - self.joy_flag.walk_height_command)
# 1s中高度变化3cm, step= 0.03 / 100 hz = 0.0003
step = 0.03 / 100
self.joy_flag.walk_height_command = np.clip(
self.joy_flag.walk_height_command, current_height_command - step,
current_height_command + step)