使用gazebo对scara机械臂进行仿真

news2024/10/5 20:28:24

本文主要介绍如何仿真一个scara机械臂,以及在网上看了一些项目以后,有了一些感想,不想看的可以直接跳到机械臂部分。

目录

  • 感想(自己的理解,不一定对。)
  • Scara机械臂的开发
    • 运动学计算
    • 如何使用
    • 机械臂工作图
    • 一个例子: 在start_pose抓起货物,在end_pose放置货物
  • 一些实现的细节
    • 1 通过ros控制gazebo中的机器人
      • 1.1 使用python调用gazebo的服务
      • 1.2 joint命名问题

感想(自己的理解,不一定对。)

ros控制gazebo中机器人的方式:
为了控制gazebo中的机器人运动, ros node需要调用gazebo的一些服务service,具体的方法在一些实现的细节中有写

但是人家项目里面ros_controller是怎么回事? 我的理解是:调用gazebo的服务其实非常不好用,因为你的需求可能是想让机械臂运动到某个点,但是你输入的只能是力,因此需要自己根据机械臂的位置计算出力的大小。

因此诞生了ros_controller,它通过gazebo包的形式,指定具体的关节,开发者只需要提供目标位置,可以自动帮你计算出力来。调用的方法是:把gazebo的service映射到了topic上,开发者只需要订阅topic,或者发布topic就可以间接使用gazebo的服务。

对于复杂的joint,你无法自己计算出力,必须通过ros_controller来。但是为什么本项目没有使用ros_controller呢? 因为本项目的机械臂非常简单,完全可以自己手算出来力的大小。

当然对于不使用ros_controller也有好处:

  • 开发简单,不用配置gazebo包,以及yam文件等等
  • 不用通过topic对机器人控制,直接通过变量传参,更加便捷
  • 对机器人理解更加深刻,控制起来得心应手

Scara机械臂的开发

其实是借鉴了这个大佬的项目:https://github.com/yangliu28/two_scara_collaboration

首先是封装了一下该机器人的一些接口:

#! /usr/bin/env python
# -*- coding: utf-8 -*
import rospy
import numpy as np
from joint import Joint

class ScaraInterface:

    def __init__(self, controller_name, robot_name, robot_pose, r1_pose, r2_pose):
        """
        创建scara控制器

        Input:  controller_name - 控制器节点名称
                robot_name - 控制器对应的机械臂名称
                robot_pose - scara机械臂所在位置
                r1_pose    - rotation1的初始位置
                r2_pose    - rotation2的初始位置    
        """
        rospy.init_node(controller_name)
        
        # 关节控制的参数
        self.kps = [80, 1]
        self.kvs = [16, 0.2]
        self.GRIPPER_UP_EFFORT = 0.001
        self.GRIPPER_DOWN_EFFORT = 0.0
        self.GRIPPER_GRASP_EFFORT = 0.002
        self.GRIPPER_RELEASE_EFFORT = 0.0001
        
        self.robot_name = robot_name # 机器人名字
        self.robot_pose = robot_pose # 机器人所在的位置
        
        # 初始化joint信息
        self.joints={
            # rotation joint
            "rotation1": Joint("{}::rotation1".format(self.robot_name), init_pose=r1_pose),
            "rotation2": Joint("{}::rotation2".format(self.robot_name), init_pose=r2_pose),
            # joint control gripper up
            "gripper": Joint("{}::gripper_joint".format(self.robot_name), init_effort=self.GRIPPER_UP_EFFORT, duration=0.25),
            # joint control gripper fingers
            "finger1": Joint("{}::finger1_joint".format(self.robot_name), init_effort=self.GRIPPER_RELEASE_EFFORT, duration=0.25),
            "finger2": Joint("{}::finger2_joint".format(self.robot_name), init_effort=-self.GRIPPER_RELEASE_EFFORT, duration=0.25),
            "finger3": Joint("{}::finger3_joint".format(self.robot_name), init_effort=-self.GRIPPER_RELEASE_EFFORT, duration=0.25),
            "finger4": Joint("{}::finger4_joint".format(self.robot_name), init_effort=self.GRIPPER_RELEASE_EFFORT, duration=0.25),
        }

    
    def move_to(self, pose):
        """
        移动gripper到指定的pose

        Input  pose - 移动的目标
        """
        # 计算相对坐标, 不清楚两个为什么是反着减
        x = self.robot_pose.position.x - pose.position.x
        y = self.robot_pose.position.y - pose.position.y
        dist_square = x*x + y*y # 目标到机器人中心的距离平方
        # 余弦定理计算出两个joint的转动角度, scara和中心连接的手臂长度为1, 另一个手臂长度为0.8
        angles = [
            np.arctan(np.divide(y,x)) - np.arccos((0.36 + dist_square)/(2*np.sqrt(dist_square))),
            np.pi - np.arccos((1.64 - dist_square)/1.6)
        ]
        # add robust to this inverse kinematics
        if np.isnan(angles).any():
            angles = [np.arctan(y/x), 0]
               
         # 发布joint需要旋转的角度
        for i,name in enumerate(["rotation1", "rotation2"]):
            pose_err = angles[i] - self.joints[name].cur_pose
            effort = self.kps[i] * pose_err - self.kvs[i] * self.joints[name].cur_rate
            self.joints[name].set_effort(effort)
            self.joints[name].publish()

    def move_down(self):
        """
        向下移动gripper
        """
        self.joints["gripper"].set_effort(self.GRIPPER_DOWN_EFFORT)
        pass
    
    def move_up(self):
        """
        向上移动gripper
        """
        self.joints["gripper"].set_effort(self.GRIPPER_UP_EFFORT)

    def grasp(self):
        self.joints["finger1"].set_effort(-self.GRIPPER_GRASP_EFFORT)
        self.joints["finger2"].set_effort(self.GRIPPER_GRASP_EFFORT)
        self.joints["finger3"].set_effort(self.GRIPPER_GRASP_EFFORT)
        self.joints["finger4"].set_effort(-self.GRIPPER_GRASP_EFFORT)

    def release(self):
        self.joints["finger1"].set_effort(self.GRIPPER_RELEASE_EFFORT)
        self.joints["finger2"].set_effort(-self.GRIPPER_RELEASE_EFFORT)
        self.joints["finger3"].set_effort(-self.GRIPPER_RELEASE_EFFORT)
        self.joints["finger4"].set_effort(self.GRIPPER_RELEASE_EFFORT)

    def update(self):
        """
        更新rotation_joint的位置, 发布gripper_joint和finger_joint的力
        """
        # 更新joint目前的位置
        for joint_name in ["rotation1", "rotation2"]:
            self.joints[joint_name].update_state()
        
        # 持续发布joint effort
        for joint_name in ["gripper", "finger1", "finger2", "finger3", "finger4"]:
            self.joints[joint_name].publish()

运动学计算

机械臂的参数: 大臂1m, 小臂0.8m
其中move_to(pose)表示了把机械臂的一端移动到pose这个位置,这里是直接计算了大臂到x轴的夹角,小臂到大臂的夹角。然后比较当前角度和目标角度的差,作为力的大小的kp倍。
另外力还和速度有关,当机械臂一端运动到了目标以后,需要保证速度为0,因此力其实和速度成反比,和位置偏差成正比。具体的角度计算:

在这里插入图片描述

如何使用

  • 先继承ScaraInterface,
  • 在每个主循环都调用self.update(),更新joint的位置
  • 在需要机械臂运动的时候,调用move_to,
  • 在需要机械臂上升的时候调用move_up,在下降的时候调用move_down
  • 在需要抓起的时候调用grasp,在需要松开的时候调用release

机械臂工作图

抓起货物
在这里插入图片描述
搬运货物
在这里插入图片描述

一个例子: 在start_pose抓起货物,在end_pose放置货物

#! /usr/bin/env python
# -*- coding: utf-8 -*
import rospy
import argparse
from enum import IntEnum
from scara_gazebo.msg import Poses
from geometry_msgs.msg import Pose, Point
from scara_interface import ScaraInterface

class state(IntEnum):
    waiting = 0
    move_forth = 1
    down_forth = 2
    grasp = 3
    up_forth = 4
    move_back = 5
    down_back = 6
    release = 7
    up_back = 8

class ScaraController(ScaraInterface):

    def __init__(self, controller_name, robot_name, robot_pose, \
                    start_pose, end_pose, r1_pose, r2_pose):
        """
        创建scara控制器

        Input:  controller_name - 控制器节点名称
                robot_name - 控制器对应的机械臂名称
                robot_pose - scara机械臂所在位置
                start_pose - 开始搬运的位置
                end_pose   - 结束搬运的位置
                r1_pose    - rotation1的初始位置
                r2_pose    - rotation2的初始位置    
        """
        ScaraInterface.__init__(self, controller_name, robot_name, robot_pose, \
                                    r1_pose, r2_pose)
              
        self.start_pose = start_pose # 开始搬运的位置
        self.end_pose = end_pose # 结束搬运的位置
        self.target_loop_num = 0 # 目标循环次数
        self.cur_loop_num = 0 # 计算循环次数 
        self.cur_state = state.waiting # 当前的状态
        self.cur_action = self.wait # 当前状态执行的函数
        self.time_unit = 0.01 # 每次循环的时间单位
        self.waiting = False # 是否在等待物体到达
        
        # 状态表, next_state, next_action, duration
        self.func_tbl = [
            (state.move_forth, self.move_forth, 1.50),
            (state.down_forth, self.move_down , 0.25),
            (state.grasp     , self.grasp     , 0.05),
            (state.up_forth  , self.move_up   , 0.25),
            (state.move_back , self.move_back , 1.50),
            (state.down_back , self.move_down , 0.25),
            (state.release   , self.release   , 0.05),
            (state.up_back   , self.move_up   , 0.25),
            (state.waiting   , self.wait      , 0.00),
        ]
        
        # 订阅位置信息,注册回调
        rospy.Subscriber('/rfid_tags', Poses, self.callback)

    def wait(self):
        pass

    def move_forth(self):
        self.move_to(self.end_pose)

    def move_back(self):
        self.move_to(self.start_pose)
        
    def callback(self, poses):
        for pose in poses:
            if pose.position == self.target_pose:
                self.waiting = False
                break

    def start(self):
        """
        开始主循环
        """
        while not rospy.is_shutdown():
            if self.cur_state == state.waiting and self.waiting: # 没有物体到达target_pose,等待
                pass
            else:
                if self.cur_state == state.waiting and not self.waiting:
                    self.waiting = True # 物体到达start_pose, 第一次进入循环,重新设置waiting信号

                if self.cur_loop_num == self.target_loop_num: # 到达该状态的循环次数,则更新状态
                    self.cur_state, self.cur_action, time_cost = self.func_tbl[self.cur_state]
                    self.target_loop_num = time_cost//self.time_unit
                    self.cur_loop_num = 0
                else:
                    self.cur_action()
                    self.cur_loop_num += 1
            
            self.update()

            rospy.loginfo(self.cur_state)
            rospy.sleep(self.time_unit)

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description='argparse for scara_controller')
    parser.add_argument('-cn', type=str, default="scara_controller", help="name of the controller node")
    parser.add_argument('-rn', type=str, default="scara_robot1", help="name of the robot")
    parser.add_argument('-rpx', type=float, default="0.0", help="robot_pose.position.x")
    parser.add_argument('-rpy', type=float, default="0.0", help="robot_pose.position.y")
    parser.add_argument('-rpz', type=float, default="0.0", help="robot_pose.position.z")
    parser.add_argument('-spx', type=float, default="1.5", help="start_pose.position.x")
    parser.add_argument('-spy', type=float, default="0.0", help="start_pose.position.y")
    parser.add_argument('-spz', type=float, default="0.0", help="start_pose.position.z")
    parser.add_argument('-epx', type=float, default="0.0", help="end_pose.position.x")
    parser.add_argument('-epy', type=float, default="1.5", help="robot_pose.position.y")
    parser.add_argument('-epz', type=float, default="0.0", help="robot_pose.position.z")
    parser.add_argument('-r1p', type=float, default="-0.78", help="rotation1_joint init angle")
    parser.add_argument('-r2p', type=float, default="2.1", help="rotation2_joint init angle")
    
    myargv = rospy.myargv()
    args = parser.parse_args(myargv[1:])
    
    scara_controller = \
        ScaraController(
            controller_name=args.cn, 
            robot_name=args.rn, 
            robot_pose=Pose(position=Point(args.rpx, args.rpy, args.rpz)),
            start_pose=Pose(position=Point(args.spx, args.spy, args.spz)),
            end_pose=Pose(position=Point(args.epx, args.epy, args.epz)),
            r1_pose=args.r1p,
            r2_pose=args.r2p
        )
    scara_controller.start()

完整的项目地址:https://github.com/HGGshiwo/scara_gazebo.git

接下来我打算学习一下如何使用ros_control,毕竟机器人太复杂的时候,或者说用别人的模型的时候,往往会用到ros_conrol。

一些实现的细节

1 通过ros控制gazebo中的机器人

为了控制gazebo中的机器人运动, ros node需要调用gazebo的一些服务service。具体的服务包括:

  • 设置施加在joint的力
  • 查询joint的移动速度以及位置
  • 其他等等

完整的gazebo支持的服务可以在gazebo_ros查看

  • 网址是 https://github.com/ros-simulation/gazebo_ros_pkgs, 在里面的gazebo_msgs里有完整的gazebo支持的消息。
  • 文档说明是: http://docs.ros.org/en/jade/api/gazebo_msgs/html/index-msg.html

1.1 使用python调用gazebo的服务

一个完整的获取gazebo中查询关节速度和位置的服务的例子:

from gazebo_msgs.srv import GetJointProperties, GetJointPropertiesRequest
get_property_proxy = rospy.ServiceProxy( # 获取joint状态的代理
            name="/gazebo/get_joint_properties", 
            service_class=GetJointProperties
        )
rospy.wait_for_service("/gazebo/get_joint_properties")
property_msg = GetJointPropertiesRequest() # 配置一个joint信息的msg
property_msg.joint_name = joint_name 
response = get_property_proxy.call(property_msg) # 生成一个获取joint状态的请求
cur_pose = response.position[0] # 猜测是一个角度
cur_rate = response.rate[0] # 猜测是一个角速度

至于如何设置joint的力,和这个非常类似。

1.2 joint命名问题

如何确定joint_name呢? 经过检测,如果使用spawn_model脚本添加的模型,gazebo中的joint以及link的名字格式是:model_name::link_name,其中model_name是spawn_model脚本-model 之后填写的参数。而和在urdf里面填写的robot_name以及spawn_model的node name没有关系。

比如我的机器人name是sara_robot,urdf文件是:

<?xml version="1.0"?>
<robot name="scara_robot">
	<link name="base_link">
	....
	</link>
	<joint name="rotation1">
	 ....
	</joint>
</robot>	

而当我在launch中添加以后:

<launch>
  <node name="scara_robot1" pkg="gazebo_ros" type="spawn_model" args="-file $(find scara_controller)/urdf/scara_robot.urdf -urdf -model scara_robot2" />
</launch>

那么实际的机器人jointt中的rotate1名称为:scara_robot2::rotate1,下图为证:
在这里插入图片描述
而使用命名空间是不管用的,因为命名空间只是分割了消息,而joint的名称在加载机器人以后是固定的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/179930.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Hadoop】MapReduce分布式计算实践(统计文本单词数量)

文章目录1. 前言2. Mapper代码3. Reducer代码4. Main代码5. 项目打包6. Hadoop运行7. 运行结果查看7.1 输出文件查看7.2 日志查看1. 前言 在博客【Hadoop】MapReduce原理剖析&#xff08;Map&#xff0c;Shuffle&#xff0c;Reduce三阶段&#xff09;中已经分析了MapReduce的运…

ASP.NET Core+Element+SQL Server开发校园图书管理系统(二)

随着技术的进步&#xff0c;跨平台开发已经成为了标配&#xff0c;在此大背景下&#xff0c;ASP.NET Core也应运而生。本文主要基于ASP.NET CoreElementSql Server开发一个校园图书管理系统为例&#xff0c;简述基于MVC三层架构开发的常见知识点&#xff0c;前一篇文章&#xf…

Linux C编程一站式学习笔记6

Linux C编程一站式学习笔记 chap6 循环结构 文章目录Linux C编程一站式学习笔记 chap6 循环结构一.while语句递归 VS 循环函数式编程&#xff08;Functional Programming&#xff09; & 命令式编程&#xff08;Imperative Programming&#xff09;无限递归 & 无限循环习…

光流估计(二) FlowNet 系列文章解读

在上篇文章中&#xff0c;我们学习并解了光流&#xff08;Optical Flow&#xff09;的一些基本概念和基本操作&#xff0c;但是传统的光流估计方法计算比较复杂、成本较高。近些年来随着CNN卷积神经网络的不断发展和成熟&#xff0c;其在各种计算机视觉任务中取得了巨大成功&am…

docker-基础实战第六课镜像挂载

镜像挂载: docker run --namemynginx -d --restartalways -p 8088:80 -v /usr/local/docker/data/html:/usr/share/nginx/html:ro nginx访问403 原因: /usr/local/docker/data/html 没有创建index.html 需要创建目录并且创建index.html docker命令补充&#xff1a; 如果有一…

向QAbstractItemView子类如:QTreeView、QTableView等子项单元格插入窗体小部件的功能实现(第1种方法)

1.前言 工作中经常会遇到这样的需求&#xff1a;向QAbstractItemView子类如QTreeView、QTableView单元格插入窗体小部件&#xff0c;如&#xff1a;进度条、按钮、单行编辑框等。下面链接的系列博文就是讲解如何实现该功能的。《向QAbstractItemView子类如:QTreeView、QTableVi…

Android音频播放有杂音?原来是这个JAVA API接口惹的祸

最近在调试一个基于十年前Android版本的多媒体应用软件时&#xff0c;遇到了音频播放的问题&#xff0c;这里记录问题的发现、分析和处理过程。 有人可能会好奇&#xff0c;十年前的Android版本是什么版本&#xff1f;大家可以去Google网站上查查&#xff0c;就是目前Android网…

Android深入系统完全讲解(40)

调试 C 代码 15.1 改成 C 写法 这个没啥必要&#xff0c;但是对 C 比 C 情谊深的我&#xff0c;把它修改了。下面是修改的一部分代码&#xff0c; 把 C 的写法&#xff0c;改成 C 的&#xff0c;同时修改引入头文件。 jstring Java_hellojni_codegg_com_hellojni_MainActivity_…

Java基础41 面向对象(高级)

面向对象&#xff08;高级&#xff09;一、类变量和类方法1.1、static &#xff08;类变量&#xff09;1.1.1 关于static的存放位置1.1.2 类变量使用细节及注意事项1.2、类方法1.2.1、类方法使用细节及注意事项二、main方法2.1、深入理解main方法三、代码块3.1、代码块的基本介…

19.6、Javaweb_案例旅游路线收藏功能

旅游线路收藏功能 分析 判断当前登录用户是否收藏过该线路 当页面加载完成后&#xff0c;发送ajax请求&#xff0c;获取用户是否收藏的标记 根据标记&#xff0c;展示不同的按钮样式 编写代码 后台代码 RouteServlet&#xff1a; package cn.itcast.travel.web.servlet;…

【Typescript学习】使用 React 和 TypeScript 构建web应用(四)useReducer、扑街了的分区功能【完结了】

教程来自freecodeCamp&#xff1a;【英字】使用 React 和 TypeScript 构建应用程序 跟做&#xff0c;仅记录用 其他资料&#xff1a;https://www.freecodecamp.org/chinese/news/learn-typescript-beginners-guide/ 作者提供的源码https://github.com/piyush-eon/react-typescr…

机器学习【西瓜书/南瓜书】--- 第四章决策树

一、决策树理论分析 1.1 通俗理解 决策树是一种非常经典的机器学习算法&#xff0c;通俗理解的话我们可以举一个例子&#xff0c;比如现在别人要找你借钱&#xff0c;那么按照首先是不是要判断你和他的关系如何?如果关系不好&#xff0c;我就直接拒绝他。如果关系很好&#…

Python机器学习:一元回归

→\rightarrow→回归效果评价 &#x1f315; 一元回归 一元回归主要研究一个自变量和一个因变量之间的关系&#xff0c;而这个自变量和因变量之间的关系又可分为线性回归和非线性回归。 ⭐️ 一元线性回归分析两个变量之间的线性关系&#xff0c;如ykxbykxbykxb中xxx和yyy就是…

深度学习笔记:神经网络的学习(1)

机器学习的核心在于从数据中提取规律和特征&#xff0c;并用于分类或预测。对于识别手写数字&#xff0c;如果人工设计一个识别算法逻辑是十分困难的。一种方法是任务在数据中提取更重要的特征量&#xff0c;然后利用机器学习算法如SVM或KNN。而神经网络的方法则是完全由机器自…

ISIS的3级别(level-1、level-2、level-1-2)4大类(IIH、LSP、CSNP、PSNP)9小类与邻接关系建立LSP交互过程介绍

2.2.0 ISIS 4种报文类型IIH、LSP、CSNP、PSNP、邻居建立过程、交互LSP过程 ISIS的3级别4大类9小类 ISIS拥有3种级别的路由器&#xff0c;分别是level-1、level-2、level-1-2。 不同级别之间进行交互的报文也是有所区别的&#xff0c;常规的ISIS报文分有4大类&#xff1a;IIH、…

cubeIDE开发, stm32人工智能开发应用实践(Cube.AI).篇一

一、cube.AI简介及cubeIDE集成 1.1 cube.AI介绍 cube.AI准确来说是STM32Cube.AI&#xff0c;它是ST公司的打造的STM32Cube生态体系的扩展包X-CUBE-AI&#xff0c;专用于帮助开发者实现人工智能开发。确切地说&#xff0c;是将基于各种人工智能开发框架训练出来的算法模型&#…

Vue3商店后台管理系统设计文稿篇(六)

记录使用vscode构建Vue3商店后台管理系统&#xff0c;这是第六篇&#xff0c;从这一篇章开始&#xff0c;所有的预备工作结束&#xff0c;正式进入商店后台管理系统的开发 文章目录一、创建后台管理系统的标题栏二、安装Icon 图标三、创建Menu菜单正文内容&#xff1a; 一、创…

PowerShell 学习笔记:操作JSON文件

JSON文件&#xff08;字符串&#xff09;是有一定格式要求的文本文件。百度百科JSON&#xff08;JavaScriptObject Notation, JS对象简谱&#xff09;是一种轻量级的数据交换格式。它基于 ECMAScript&#xff08;European Computer Manufacturers Association, 欧洲计算机协会制…

初识Linux常见指令汇总

文章目录前言1.对文件或目录的常用指令1.查看当前路径下的文件或目录相关信息2.进入指定路径3.创建删除文件或者目录4.使用nano简单编辑文件查看文件属性5.复制移动重命名文件或目录6.输入输出重定(查看文件内容)向和搜索查找1.输入输出重定向2.搜索查找7.打包压缩文件2.时间相…

如何使用Maven构建Java项目?Maven的使用详细解读

文章目录1. 前言2. Maven 快速入门2.1 Maven 项目模型2.2 Maven 仓库3. Maven的安装配置3.1 安装3.2 配置环境变量3.4 Maven 配置4. Maven 的常用命令4.1 编译4.2 清理4.3 打包4.4 测试4.5 安装5. Maven生命周期6. 总结Java编程基础教程系列&#xff1a;1. 前言 在 Java 开发中…