tmp

角度->力矩

def sim_loop(self):
    # 当前时间
    sim_time = self.data.time
    
    # 目标角度
    target_shoulder = 0.0
    if sim_time > 1.0:
        target_shoulder = -1.57
    
    # 获取当前角度和速度
    q_current = self.data.qpos[self.shoulder_id]
    qdot_current = self.data.qvel[self.shoulder_id]
    
    # PD 控制计算力矩
    Kp = 50.0
    Kd = 5.0
    tau = Kp * (target_shoulder - q_current) - Kd * qdot_current
    
    # 应用力矩
    self.data.ctrl[self.shoulder_id] = tau

xml

<mujoco model="openarm_simple">
  <option timestep="0.005" gravity="0 0 -9.81"/>
  
  <visual>
    <global offwidth="1920" offheight="1080"/>
  </visual>

  <worldbody>
    <geom type="plane" size="2 2 0.1" rgba=".8 .8 .8 1"/>
    <light pos="0 0 3" dir="0 0 -1"/>

    <body pos="0 0 0.1" name="base">
      <geom type="cylinder" size="0.05 0.1" rgba=".2 .2 .2 1"/>
      
      <body pos="0 0 0.1">
        <joint name="shoulder_joint" type="hinge" axis="0 1 0" range="-1.57 1.57" damping="1.0"/>
        <geom type="capsule" fromto="0 0 0 0 0 0.3" size="0.03" rgba="0.8 0.1 0.1 1"/>
        
        <body pos="0 0 0.3">
          <joint name="elbow_joint" type="hinge" axis="0 1 0" range="-2.0 2.0" damping="1.0"/>
          <geom type="capsule" fromto="0 0 0 0 0 0.25" size="0.025" rgba="0.1 0.8 0.1 1"/>
          
          <body pos="0 0 0.25">
             <geom type="box" size="0.04 0.01 0.05" rgba="0 0 1 1"/>
          </body>
        </body>
      </body>
    </body>
  </worldbody>

  <actuator>
    <position joint="shoulder_joint" name="servo_shoulder" kp="50"/>
    <position joint="elbow_joint" name="servo_elbow" kp="50"/>
  </actuator>
</mujoco>

arm.py

import rclpy
from rclpy.node import Node
import mujoco
import mujoco.viewer
import os
import numpy as np
from ament_index_python.packages import get_package_share_directory

class OpenArmSimNode(Node):
    def __init__(self):
        super().__init__('openarm_sim_node')
        
        # --- 1. 加载模型 ---
        pkg_path = get_package_share_directory('openarm_mujoco')
        xml_path = os.path.join(pkg_path, 'meshes', 'openarm_simple.xml')
        self.get_logger().info(f"Loading MuJoCo model: {xml_path}")

        self.model = mujoco.MjModel.from_xml_path(xml_path)
        self.data = mujoco.MjData(self.model)

        # 获取执行器ID,方便后续控制
        self.shoulder_id = mujoco.mj_name2id(self.model, mujoco.mjtObj.mjOBJ_ACTUATOR, "servo_shoulder")
        self.elbow_id = mujoco.mj_name2id(self.model, mujoco.mjtObj.mjOBJ_ACTUATOR, "servo_elbow")

        # --- 2. 启动 Viewer (被动模式) ---
        self.viewer = mujoco.viewer.launch_passive(self.model, self.data)

        # --- 3. 设置仿真循环 (100Hz) ---
        self.timer = self.create_timer(0.01, self.sim_loop)
        
        self.get_logger().info("Simulation Started. Arm will lift in 2 seconds...")

    def sim_loop(self):
        # --- 控制逻辑区域 ---
        # 获取当前仿真时间
        sim_time = self.data.time

        # 简单的状态机:2秒后抬手
        target_shoulder = 0.0
        target_elbow = 0.0

        if sim_time > 2.0:
            # 抬手动作:肩部转动 -0.8弧度,肘部转动 -0.5弧度
            target_shoulder = -0.8
            target_elbow = -0.5
        
        # 将目标角度写入控制输入 (data.ctrl)
        self.data.ctrl[self.shoulder_id] = target_shoulder
        self.data.ctrl[self.elbow_id] = target_elbow

        # --- 物理步进 ---
        # 执行一步物理计算
        mujoco.mj_step(self.model, self.data)

        # --- 同步显示 ---
        if self.viewer.is_running():
            self.viewer.sync()
        else:
            self.destroy_node()
            rclpy.shutdown()

def main(args=None):
    rclpy.init(args=args)
    node = OpenArmSimNode()
    try:
        rclpy.spin(node)
    except KeyboardInterrupt:
        pass
    finally:
        node.viewer.close()
        node.destroy_node()
        rclpy.shutdown()

if __name__ == '__main__':
    main()

setup.py

from setuptools import setup
import os
from glob import glob

package_name = 'openarm_mujoco'

setup(
    name=package_name,
    version='0.0.0',
    packages=[package_name],
    data_files=[
        ('share/ament_index/resource_index/packages',
            ['resource/' + package_name]),
        ('share/' + package_name, ['package.xml']),
        # --- 关键:添加这行以安装 meshes 文件夹下的所有 xml ---
        (os.path.join('share', package_name, 'meshes'), glob('meshes/*.xml')),
    ],
    install_requires=['setuptools'],
    zip_safe=True,
    maintainer='User',
    maintainer_email='user@todo.todo',
    description='OpenArm MuJoCo Example',
    license='TODO',
    tests_require=['pytest'],
    entry_points={
        'console_scripts': [
            # 注册节点入口
            'arm_control = openarm_mujoco.arm:main'
        ],
    },
)

package.xml

<package format="3">
  <name>openarm_mujoco</name>
  <version>0.0.0</version>
  <description>Demo</description>
  <maintainer email="user@todo.todo">User</maintainer>
  <license>TODO</license>

  <depend>rclpy</depend>
  <depend>python3-numpy</depend> 

  <test_depend>ament_copyright</test_depend>
  <test_depend>ament_flake8</test_depend>
  <test_depend>ament_pep257</test_depend>
  <test_depend>python3-pytest</test_depend>

  <export>
    <build_type>ament_python</build_type>
  </export>
</package>

————————
命令行发布话题
ros2 topic pub /joint_cmd std_msgs/msg/Float64MultiArray "{data: [0,0,0,0.785,0,0]}"

# bridge.py
import rclpy
from rclpy.node import Node
from sensor_msgs.msg import JointState
from std_msgs.msg import Float64MultiArray

import mujoco
import numpy as np

class MuJoCoRosBridge(Node):
    def __init__(self):
        super().__init__('mujoco_ros_bridge')

        # Load MuJoCo model
        xml_path = str(self.get_package_share_directory()) + "/models/robot.xml"
        self.model = mujoco.MjModel.from_xml_path(xml_path)
        self.data = mujoco.MjData(self.model)

        # Control buffer
        self.cmd = np.zeros(self.model.nu)

        # ROS I/O
        self.pub = self.create_publisher(JointState, 'joint_state', 10)
        self.sub = self.create_subscription(
            Float64MultiArray, 'joint_cmd', self.cmd_callback, 10)

        # Timer 1 kHz
        self.timer = self.create_timer(0.001, self.sim_step)

    def get_package_share_directory(self):
        from ament_index_python.packages import get_package_share_directory
        return get_package_share_directory('mujoco_ros')

    def cmd_callback(self, msg):
        n = min(len(msg.data), self.model.nu)
        self.cmd[:n] = msg.data[:n]

    def sim_step(self):
        self.data.ctrl[:] = self.cmd
        mujoco.mj_step(self.model, self.data)

        js = JointState()
        js.header.stamp = self.get_clock().now().to_msg()
        js.position = self.data.qpos[:self.model.nq].tolist()
        js.velocity = self.data.qvel[:self.model.nv].tolist()
        js.effort   = self.data.actuator_force.tolist()
        self.pub.publish(js)


def main():
    rclpy.init()
    node = MuJoCoRosBridge()
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()

# cmd_test.py
import rclpy
from rclpy.node import Node
from std_msgs.msg import Float64MultiArray

class CmdTest(Node):
    def __init__(self):
        super().__init__('cmd_test')
        self.pub = self.create_publisher(Float64MultiArray, 'joint_cmd', 10)
        self.timer = self.create_timer(0.02, self.send_cmd)

    def send_cmd(self):
        msg = Float64MultiArray()
        msg.data = [0.1, -0.2]   # 例如两个关节的 torque
        self.pub.publish(msg)

def main():
    rclpy.init()
    node = CmdTest()
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()

# mujoco.launch.py
from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
    return LaunchDescription([
        Node(
            package='mujoco_ros',
            executable='bridge',
            output='screen'
        ),
        Node(
            package='mujoco_ros',
            executable='cmd_test',
            output='screen'
        )
    ])

在package.xml添加

<exec_depend>rclpy</exec_depend>
<exec_depend>std_msgs</exec_depend>
<exec_depend>sensor_msgs</exec_depend>
# setup.py
entry_points={
    'console_scripts': [
        'bridge = mujoco_ros.bridge:main',
        'cmd_test = mujoco_ros.cmd_test:main',
    ],
},
posted @ 2025-12-12 18:31  TimeLimit  阅读(4)  评论(1)    收藏  举报