基于ROS通过外设控制仿真机械臂运动

前言

最近要实现手柄控制机械臂的运动,苦于经费不足,手柄目前还用不上,不得已采用鼠标代替,不过原理是一样的,其实笔者早些时间学过一点ROS,不过已经忘的差不多了,趁此机会好好恶补一下,学习资料参考《ROS机器人开发实践》

鼠标部分

安装功能包

为了能够让系统检测到外设的接入同时正常使用,我们需要下载一些功能包

sudo apt-get install ros-noetic-joystick-drivers

有时候会出现安装版本不同导致异常的情况,这个时候用aptitude辅助安装,他会智能检索要安装的包

sudo apt-get install aptitude
sudo aptitude install <package name>

检测外设

我们需要查看Ubuntu中的硬件驱动层目录dev/input,在终端中输入

ls /dev/input/

我们会看到如下输出

ls /dev/input/
by-id  by-path  event0  event1  event2  event3  event4  event5  event6  js0  js1  mice  mouse0  mouse1  mouse2

创建的端口是js0,我们通过jstest来测试其是否工作

jstest /dev/input/js0

输出的结果应该为

输出的信息很清晰地告诉我们鼠标X&Y轴的状态,到时候换成手柄也就是多几个键的事情,原理是一样的,至此外设测试完成,现在我们来梳理一下控制逻辑

控制逻辑

用口头讲可能有点晦涩,直接上思维导图来解释,我们先启动joy_node手柄节点来发布外设的X&Y轴数据,然后通过简单的转换成机械臂能接收到的数据,再发布到joint_state上,move_group订阅到这些数据后对机械臂进行运动学规划,这个下次我打算从原理开始好好总结一下

 

 因此我们首先要做的第一步是启动joy_node节点,在这里我把它集成到了launch文件中,在第二个node标签中,当节点死掉了就重新生成,包名为joy,节点类型&名称:“joy_node”

<?xml version="1.0"?>
<launch>
    <!--create a node named mouse_joy to sub&pub datas,and set axis parameters about device-->
    <node pkg="joystick" type="mouse_joy" name="mouse_joy"/>
    <param name="axis_linear" value="1" type="int"/>
    <param name="axis_angular" value="0" type="int"/>
    <!--create a joy node and set some parameters about device-->
    <node respawn="true" pkg="joy" type="joy_node" name="joy_node">
        <param name="dev" value="/dev/input/js0" type="string"/>
        <param name="deadzone" value="0.12"/>
    </node>
</launch>

接下来第二步就是创建订阅者发布者来处理数据

类的创建

class TeleopJoy{
    public:
    TeleopJoy();    //define a constructor
    private:
    void callBack(const sensor_msgs::Joy::ConstPtr& joy);   //define a callback function,param is the data adress which sub recieve
    ros::NodeHandle n;
    ros::Publisher pub;
    ros::Subscriber sub;
    int i_velLinear,i_velAngular;   //device's axis input value
};

构造函数

TeleopJoy::TeleopJoy()
{
    i_velLinear = 1;
    i_velAngular = 0;
    n.param("axis_linear",i_velLinear,i_velLinear); //set vaule from param
    n.param("axis_angular",i_velAngular,i_velAngular);
    /*
        pub advertise meassage to topic named /joint_states,maximum quene:100
        meassage type is ensor_msgs::JointState    
    */
    pub = n.advertise<sensor_msgs::JointState>("/joint_states",100); 
    /*
        sub subscribe from topic named 'joy' and after receive using the callback function
        meassage type is sensor_msgs::Joy
    */
    sub = n.subscribe<sensor_msgs::Joy>("joy",100,
    &TeleopJoy::callBack,this);
}

回调函数

/*
    every time sub node receive messages will recall callback function
    it will create a new value named vel and publish it 
*/
void TeleopJoy::callBack(const sensor_msgs::Joy::ConstPtr& joy)
{
    sensor_msgs::JointState joint_state;
    joint_state.header.stamp = ros::Time::now();

    joint_state.name.resize(6); //指定name数组长度
    joint_state.position.resize(6);//指定position数组长度
    joint_state.velocity.resize(1);//指定velocity数组长度
    joint_state.effort.resize(1);//指定effort数组长度
    joint_state.name ={"joint1","joint2","joint3","joint4","joint5","joint6"};
    joint_state.position = {20.0*joy->axes[0],10.0*joy->axes[0],25.0*joy->axes[0],15.0*joy->axes[0],20.0*joy->axes[0],20.0*joy->axes[0]};//position数组的元素赋值
    joint_state.velocity[0] = 0.1*joy->axes[1];
    joint_state.effort[0] = 20.0*joy->axes[1];

    pub.publish(joint_state);   //publish the meassage
}

主函数入口

int main(int argc,char** argv)
{
    ros::init(argc,argv,"mouse_joy");
    TeleopJoy teleop_joy;   //create an object 
    ros::spin();
}

逻辑梳理

订阅者和发布者直接的逻辑都比较清晰,个人认为实现程序的关键是数据类型是否能对的上,因此在通讯之前了解话题消息类型很重要,我们要用到rostopic echo <topic name>

这是前面看到过的鼠标信息

---
header: 
  seq: 1582
  stamp: 
    secs: 1669302437
    nsecs: 962512510
  frame_id: "/dev/input/js0"
axes: [-0.31968000531196594, -0.8116175532341003]
buttons: [0]

这是joint_state消息信息

---
header: 
  seq: 4126
  stamp: 
    secs: 1669302509
    nsecs: 743610368
  frame_id: ''
name: 
  - joint1
  - joint2
  - joint3
  - joint4
  - joint5
  - joint6
position: [-6.393600106239319, -3.1968000531196594, -7.992000132799149, -4.795200079679489, -6.393600106239319, -6.393600106239319]
velocity: [-0.03451005220413208]
effort: [-6.902010440826416]

因此我们要要将接收到的两个鼠标的数据映射到至少八个(position velocity effort)数据再发布到joint_states话题上,这个时候就需要指定每个数组的长度以发送对应长度的数据,否则会报错,ps:这里的映射关系非常地不严谨,只是为了验证消息能够成功传递而设置的

sensor_msgs::JointState joint_state;
    joint_state.header.stamp = ros::Time::now();

    joint_state.name.resize(6); //指定name数组长度
    joint_state.position.resize(6);//指定position数组长度
    joint_state.velocity.resize(1);//指定velocity数组长度
    joint_state.effort.resize(1);//指定effort数组长度
    joint_state.name ={"joint1","joint2","joint3","joint4","joint5","joint6"};
    joint_state.position = {20.0*joy->axes[0],10.0*joy->axes[0],25.0*joy->axes[0],15.0*joy->axes[0],20.0*joy->axes[0],20.0*joy->axes[0]};//position数组的元素赋值
    joint_state.velocity[0] = 0.1*joy->axes[1];
    joint_state.effort[0] = 20.0*joy->axes[1];

机械臂部分

算法实现方面不是本篇的重点,这里就通过moveit_setup_assistant简单配置一下

URDF部分

首先我们要有一个描述机械臂的urdf文件,可以通过solidwroks插件导出,这里为了图方便我就用古月的了,学习资料可以在GitHub上找到

https://github.com/huchunxu/ros_exploring

配置部分

如果没有Moveit setup assistant的话要下载一个

sudo apt install ros-noetic-moveit
roslaunch moveit_setup_assistant setup_assistant.launch

接下来开始配置

 

 

 

 

 

 

 

 

 

 

 

至此机械臂的功能包就创建好了

机械臂测试

在终端输入

roslaunch arm_joystick demo.launch 

 不出意外就能在rviz里面看到机械臂模型了

鼠标控制

此时我们再启动先前写好的launch文件就可以实现鼠标控制机械臂了

roslaunch joystick mouse_joy.launch 

 效果演示

鼠标移动一下机械臂也跟着移动了

 源码部分

其实前面已经差不多贴完了,不过再贴一次吧

CPP部分

/*
    joy node publish joystick meassage,than sub acall the callback function 
    to publish vel which caculate from parameters named i_velLinear,i_velAngular
    to the topic named /turtle1/cmd_vel,turtle node receieve the meassage and 
    control turtle to move
*/

#include <ros/ros.h>
#include <iostream>
#include <geometry_msgs/Twist.h>
#include <sensor_msgs/Joy.h>
#include <sensor_msgs/JointState.h>

using namespace std;

class TeleopJoy{
    public:
    TeleopJoy();    //define a constructor
    private:
    void callBack(const sensor_msgs::Joy::ConstPtr& joy);   //define a callback function,param is the data adress which sub recieve
    ros::NodeHandle n;
    ros::Publisher pub;
    ros::Subscriber sub;
    int i_velLinear,i_velAngular;   //device's axis input value
};

TeleopJoy::TeleopJoy()
{
    i_velLinear = 1;
    i_velAngular = 0;
    n.param("axis_linear",i_velLinear,i_velLinear); //set vaule from param
    n.param("axis_angular",i_velAngular,i_velAngular);
    /*
        pub advertise meassage to topic named /joint_states,maximum quene:100
        meassage type is ensor_msgs::JointState    
    */
    pub = n.advertise<sensor_msgs::JointState>("/joint_states",100); 
    /*
        sub subscribe from topic named 'joy' and after receive using the callback function
        meassage type is sensor_msgs::Joy
    */
    sub = n.subscribe<sensor_msgs::Joy>("joy",100,
    &TeleopJoy::callBack,this);
}
/*
    every time sub node receive messages will recall callback function
    it will create a new value named vel and publish it 
*/
void TeleopJoy::callBack(const sensor_msgs::Joy::ConstPtr& joy)
{
    sensor_msgs::JointState joint_state;
    joint_state.header.stamp = ros::Time::now();

    joint_state.name.resize(6); //指定name数组长度
    joint_state.position.resize(6);//指定position数组长度
    joint_state.velocity.resize(1);//指定velocity数组长度
    joint_state.effort.resize(1);//指定effort数组长度
    joint_state.name ={"joint1","joint2","joint3","joint4","joint5","joint6"};
    joint_state.position = {20.0*joy->axes[0],10.0*joy->axes[0],25.0*joy->axes[0],15.0*joy->axes[0],20.0*joy->axes[0],20.0*joy->axes[0]};//position数组的元素赋值
    joint_state.velocity[0] = 0.1*joy->axes[1];
    joint_state.effort[0] = 20.0*joy->axes[1];

    pub.publish(joint_state);   //publish the meassage
}
int main(int argc,char** argv)
{
    ros::init(argc,argv,"mouse_joy");
    TeleopJoy teleop_joy;   //create an object 
    ros::spin();
}

Launch部分

<?xml version="1.0"?>
<launch>
    <!--create a node named mouse_joy to sub&pub datas,and set axis parameters about device-->
    <node pkg="joystick" type="mouse_joy" name="mouse_joy"/>
    <param name="axis_linear" value="1" type="int"/>
    <param name="axis_angular" value="0" type="int"/>
    <!--create a joy node and set some parameters about device-->
    <node respawn="true" pkg="joy" type="joy_node" name="joy_node">
        <param name="dev" value="/dev/input/js0" type="string"/>
        <param name="deadzone" value="0.12"/>
    </node>
</launch>

### ROS 使用手柄控制机械的实现 在 ROS 中,可以通过 Joystick 或 Gamepad 手柄来发送指令并控制机械的动作。这通常涉及订阅 `joy` 主题中的数据,并将其映射到机械的具体动作上。 #### 实现流程概述 为了完成这一目标,可以按照以下方式设计节点结构: 1. **Joy 节点配置**: 配置一个 Joy 节点用于读取手柄输入的数据流[^3]。 2. **自定义消息转换逻辑**: 创建一个中间节点,该节点接收来自 Joy 的主题 `/joy` 数据,并将其解析为适合机械控制器的消息类型(如 Twist、JointTrajectory 等)[^4]。 3. **发布至机械接口**: 将处理后的运动命令传递给机械的实际驱动程序或仿真环境。 以下是基于上述描述的一个具体代码示例: ```python #!/usr/bin/env python3 import rospy from sensor_msgs.msg import Joy from geometry_msgs.msg import Twist class JoystickToTwist: def __init__(self): self.pub_twist = rospy.Publisher('/cmd_vel', Twist, queue_size=10) rospy.Subscriber('joy', Joy, self.joy_callback) def joy_callback(self, data): twist_msg = Twist() # 假设左摇杆上下轴对应线速度,左右轴对应角速度 linear_speed = data.axes[1] * 0.5 # 缩放因子调整范围 [-0.5, 0.5] angular_speed = data.axes[0] * 1.0 # 缩放因子调整范围 [-1.0, 1.0] twist_msg.linear.x = linear_speed twist_msg.angular.z = angular_speed self.pub_twist.publish(twist_msg) if __name__ == '__main__': try: rospy.init_node('joystick_to_twist') controller = JoystickToTwist() rospy.spin() except rospy.ROSInterruptException: pass ``` 此脚本实现了从手柄输入到机器人移动基础的速度控制转化过程。对于更复杂的机械控制系统,则可能需要进一步扩展功能模块以支持关节空间轨迹规划或其他高级特性[^5]。 #### 注意事项 - 不同品牌的手柄按键布局有所差异,在开发前需确认所用手柄的确切按钮编号及其含义。 - 如果针对特定硬件比如 Franka Emika 机械进行适配时,请参照官方文档中有关 API 接口说明部分的内容[^6]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值