ros_control是对pr2_mechanism包的重写,使其适用于所有机器人的硬件封装库,负责管理硬件驱动与传感器底层细节,处理异常,分配资源,向上提供统一接口。是一个需要继承特定基类来实现接口统一的类库。内置丰富的controller,可以直接使用需要的控制算法,同时在仿真gazebo对ros_control也有很好的支持,便利了gazebo的仿真开发。
接收来自机器人执行器的编码器和输入设定点的关节状态数据作为输入。它使用一个通用的控制回路反馈机制,通常是一个PID控制器,来控制发送到执行器的输出,通常是作用力。ros_control对于没有一对一的关节位置,作用力等映射的物理机制会变得更加复杂,但是这些场景是使用transmissions来解释的。
推荐仔细研究的教程:ROS-wiki官方教程,ros_control的github教程,官方发布视频,发布视频中的PDF文档
作为应用与机器人之间的中间件,包含一系列控制器接口、传动装置接口、硬件接口、控制器工具箱等。针对不同的底盘,机械臂等,提供多种不同的controller。
提供硬件抽象层hardware_interface,进行机器人的硬件资源管理,controllers再向抽象层请求资源。所有的controllers都使用forward_command_controller向抽象层发送消息。
controller_manager提供一个实时兼容的loop来控制robot的机制,该机制由hardware_interface:robothw提供。并提供加载、卸载、启动和停止控制器的基础结构。
加载控制器时,controller_manager将使用控制器名称作为所有控制器特定参数的根,最重要的是,区分标识和要加载的插件。controller_manager提供了与控制器交互的基础结构。根据您是从启动文件、命令行还是从ros节点运行控制器,controller_manager提供了不同的工具来运行控制器。
与特定控制器进行交互
$ rosrun controller_manager controller_manager ...
load: load controllers (construct and initialize)
unload: unload controllers (destruct)
start: start controllers
stop: stop controllers
spawn: load and start controllers
kill: stop and unload controllers
查看控制器相应状态
$ rosrun controller_manager controller_manager
list: 按执行顺序列出所有控制器,并给出每个控制器的状态
list-types: 列出控制器管理器知道的所有控制器类型。如果你的控制器不在此列表中,你将无法生成它。
reload-libraries: 重新加载作为插件可用的所有控制器库。这在开发控制器时非常方便,并且您希望测试新的控制器代码,而不必每次重新启动机器人。这不会重新启动以前运行的控制器。
reload-libraries --restore: 重新加载所有可用的控制器库作为插件,并将所有控制器还原到其原始状态。
不全放上来了。。。
使用spawner工具从启动文件中自动加载、启动、停止和卸载控制器。启动spawner时,它将加载并启动控制器。当您停止spawner(启动文件被取下时),它将停止并卸载控制器。
<launch>
<node pkg="controller_manager"
type="spawner"
args="controller_name1 controller_name2" />
launch>
只加载不启动
<launch>
<node pkg="controller_manager"
type="spawner"
args="--stopped controller_name1 controller_name2" />
launch>
rqt_controller_manager是一个rqt插件,允许以图形方式加载、卸载、启动和停止控制器,以及显示有关加载的控制器的信息。
$ rosrun rqt_controller_manager rqt_controller_manager
controller类通常是以plugin形式被使用,再被controller_manager调用(加载启动卸载等),现已有controllers十分丰富,相对于自定controller要方便很多。
ros_controllers(github)在不断更新,目前包中有的controller就是当前支持的,在其相关的wiki页面会有介绍,API,以及简单示例。当然想了解深入结构还是要看功能类的源码实现,都是有注释的。在github包中还有简单的使用示例,可以在其基础上改写自己的功能实现。
hardware_interface是硬件抽象层的一部分,负责向硬件请求资源,并将资源分配给所属的controllers,每个interface接口下都有相对应的controller类型,注册绑定interface句柄,加载controllers就可以使用了。
让控制器管理器(以及控制器管理器内的控制器)访问机器人的关节状态和机器人的命令。当控制器管理器运行时,控制器将读取机器人中的pos、vel和eff变量,并且控制器将所需的命令写入cmd变量。确保pos、vel和eff变量始终具有可用的最新关节状态,还需要确保写入cmd变量的任何内容都由机器人执行。为此,可以向robot类中添加read()和write()函数。
不限于仅从一个接口继承。你的机器人可以提供尽可能多的接口。但是如果你的机器人有一些标准接口不支持的功能,自定义接口是向robot类添加任意数量的函数调用,并注册robot类本身。这些特定于机器人的功能将仅对专门为机器人设计的控制器可用,但同时机器人仍将使用您的机器人的标准接口与标准控制器一起工作。控制器管理器跟踪每个控制器正在使用的资源。资源在硬件接口中指定,当然,还可以实现自己的硬件接口,并定义自己的资源。当控制器初始化时,它们从硬件接口请求大量资源;这些请求由控制器管理器记录。所以控制器管理器确切地知道哪个控制器请求了哪些资源。
RobothW类有一个简单的默认资源管理实现:它简单地防止使用同一资源的两个控制器同时运行。注意,使用同一资源的两个控制器可以同时加载,但不能同时运行。如果这个简单的资源管理方案适合您的机器人,您不需要做任何事情,控制器管理器将自动应用这个方案。如果您的机器人需要不同的方案,可以通过实现一个单一功能轻松创建自己的方案。
checkForConflict方法的输入是控制器信息对象的列表。这些对象中的每一个都与一个控制器匹配,并包含有关该控制器的所有信息。此信息包括控制器名称、控制器类型、硬件接口类型以及控制器声明的资源列表。基于所有这些信息,可以提出自己的方案来决定是否允许给定的控制器列表同时运行。
control启动之后,会进入循环,图为循环工作内容,在数据流图中都是可以清晰看到的。
controller的使用,带来了便利,目前controller已经很好的涵盖了多数使用情况,但是hardware仍是需要继承重写,这样带来了不便,更多的是局限性,一些常用的机器人,例如UR机械臂,给出了可选择是否使用controller的两种方式,那如果放弃controller的方式,又该怎么做。
首先要明确controller具体做了什么,就是ROS上层组件的标准控制接口与感知接口,topic、service、action啊,就是接收个消息,发出个消息。
举个例子,diff_drive_controlller:The controller main input is a geometry_msgs::Twist topic in the namespace of the controller. 订阅了cmd_vel的topic,并进行了处理,那就是需要我们自己写一个cmd_vel的订阅并在callback中处理并控制。
在UR机械臂的控制驱动,可以选择不适用control的方式,它对整个控制接口进行了封装,例如moveit控制,它专门留出了控制机械臂的action,FollowJointTrajectoryAction这个action的回调函数就是第一步的处理,接收ROS消息,并进行处理,下面就是模仿负责与底层进行通信的hardware层,在回调中给调用底层驱动控制机器人,整个过程就是这样,并没有很复杂,好像ros_control的使用也变得没那么不可或缺。
那其他的必须使用controller的模块呢,在我的理解,其他的模块使用了controller,只是告诉自己使用了那个controller,于是就知道了可以从那些topic或者其他得到相关信息,只要我们以相同的方式写一个相同的消息发出就好啦~
而在gazebo中,仿真机器人的驱动接口是已经写好的,整个仿真硬件驱动层并不需要自己写,这样确实带给了我们极大的便利,其次我们去修改也需要较真机更多的成本,目前使用过的常用机器人的仿真,都是使用ros_control的模式,或许只有较为复杂的复合型机器人,才有必要去使用单独的接口。
能同过接口与电机通信,根据电机数量自动加载相应关节的controllers,暂时只在Cyton的cute包中见过,使用场景与功能区别暂不清楚。
wiki教程
配置文件,复杂的结构下使用yaml效果是极好的,不管是阅读还是编写,了解熟悉它的语法都将是很有帮助的。
environments:
dev:
url: http://dev.bar.com
name: Developer Setup
prod:
url: http://foo.bar.com
name: My Cool App
my:
servers:
- dev.bar.com
- foo.bar.com
可以直观的看到,YAML使用冒号加缩进的方式代表层级(属性)关系,使用短横杠(-)代表数组元素。
下面立刻展示YAML最基本,最常用的一些使用格式:
首先YAML中允许表示三种格式,分别是常量值,对象和数组
#即表示url属性值;
url: http://www.wolfcode.cn
#即表示server.host属性的值;
server:
host: http://www.wolfcode.cn
#数组,即表示server为[a,b,c]
server:
- 120.168.117.21
- 120.168.117.22
- 120.168.117.23
#常量
pi: 3.14 #定义一个数值3.14
hasChild: true #定义一个boolean值
name: '你好YAML' #定义一个字符串
使用#作为注释,YAML中只有行注释。
基本格式要求
1,YAML大小写敏感;
2,使用缩进代表层级关系;
3,缩进只能使用空格,不能使用TAB,不要求空格个数,只需要相同层级左对齐(一般2个或4个空格)
4, 列表中的所有成员都开始于相同的缩进级别, 并且使用一个 "- " 作为开头(一个横杠和一个空格):
使用冒号代表,格式为key: value。冒号后面要加一个空格:
key: value
可以使用缩进表示层级关系;
key:
child-key: value
child-key2: value2
YAML中还支持流式(flow)语法表示对象,比如上面例子可以写为:
key: {child-key: value, child-key2: value2}
数组
使用一个短横线加一个空格代表一个数组项:
hobby:
- Java
- LOL
可以简单理解为:[[Java,LOL]]
一个相对复杂的例子:
companies:
-
id: 1
name: company1
price: 200W
-
id: 2
name: company2
price: 500W
意思是companies属性是一个数组,每一个数组元素又是由id,name,price三个属性构成;
数组也可以使用流式(flow)的方式表示:
companies: [{id: 1,name: company1,price: 200W},{id: 2,name: company2,price: 500W}]
常量
YAML中提供了多种常量结构,包括:整数,浮点数,字符串,NULL,日期,布尔,时间。下面使用一个例子来快速了解常量的基本使用:
boolean:
- TRUE #true,True都可以
- FALSE #false,False都可以
float:
- 3.14
- 6.8523015e+5 #可以使用科学计数法
int:
- 123
- 0b1010_0111_0100_1010_1110 #二进制表示
null:
nodeName: 'node'
parent: ~ #使用~表示null
string:
- 哈哈
- 'Hello world' #可以使用双引号或者单引号包裹特殊字符
- newline
newline2 #字符串可以拆成多行,每一行会被转化成一个空格
date:
- 2018-02-17 #日期必须使用ISO 8601格式,即yyyy-MM-dd
datetime:
- 2018-02-17T15:02:31+08:00 #时间使用ISO 8601格式,时间和日期之间使用T连接,最后使用+代表时区
一些特殊符号
YAML中提供了很多特殊符号,在这里简单介绍常用的一些:
1,— YAML可以在同一个文件中,使用—表示一个文档的开始;比如Springboot中profile的定义:
server:
address: 192.168.1.100
---
spring:
profiles: development
server:
address: 127.0.0.1
---
spring:
profiles: production
server:
address: 192.168.1.120
代表定义了两个profile,一个是development,一个production;也常常使用—来分割不同的内容,比如记录日志:
---
Time: 2018-02-17T15:02:31+08:00
User: ed
Warning:
This is an error message for the log file
---
Time: 2018-02-17T15:05:21+08:00
User: ed
Warning:
A slightly different error message.
2,… 和—配合使用,在一个配置文件中代表一个文件的结束:
---
time: 20:03:20
player: Sammy Sosa
action: strike (miss)
...
---
time: 20:03:47
player: Sammy Sosa
action: grand slam
...
相当于在一个yaml文件中连续写了两个yaml配置项。
3,!! YAML中使用!!做类型强行转换:
string:
- !!str 54321
- !!str true
相当于把数字和布尔类型强转为字符串。当然允许转型的类型很多,比如:
--- !!set
- Mark McGwire: 65
- Sammy Sosa: 63
- Sammy Sosa: 63
- Ken Griffy: 58
将数组解析为set,简单理解,转化的内容就是:[{Ken Griffy=58}, {Mark McGwire=65}, {Sammy Sosa=63}],重复的Sammy Sosa去掉;
4,>在字符串中折叠换行,| 保留换行符,这两个符号是YAML中字符串经常使用的符号,比如:
accomplishment: >
Mark set a major league
home run record in 1998.
stats: |
65 Home Runs
0.278 Batting Average
那么结果是:
stats=65 Home Runs
0.278 Batting Average,
即| 符号保留了换行符,而accomplishment的结果为:
accomplishment=Mark set a major league home run record in 1998.
即将换行符转化成了空格;要注意一点的是,每行的文本前一定要有一个空格。
5,引用。重复的内容在YAML中可以使用&来完成锚点定义,使用*来完成锚点引用,例如:
hr:
- Mark McGwire
- &SS Sammy Sosa
rbi:
- *SS
- Ken Griffey
可以看到,在hr中,使用&SS为Sammy Sosa设置了一个锚点(引用),名称为SS,在rbi中,使用*SS完成了锚点使用,那么结果为:
{rbi=[Sammy Sosa, Ken Griffey], hr=[Mark McGwire, Sammy Sosa]}
注意,不能独立的定义锚点,比如不能直接这样写: &SS Sammy Sosa;另外,锚点能够定义更复杂的内容,比如:
default: &default
- Mark McGwire
- Sammy Sosa
hr: *default
那么hr相当于引用了default的数组,注意,hr: *default要写在同一行。
参考摘取感谢:
yaml语法