1.tf2系列教程(十六):了解ROS 2中的源码tf2和时间(C++)
2.tf2系列教程(十三):在ROS 2中编写tf2侦听者节点(C++)
3.求一个JAVA计算器源代码。不要按钮的源码那种。速度。源码。源码急用
4.ROS 源码学习 ---TF
tf2系列教程(十六):了解ROS 2中的源码tf2和时间(C++)
在ROS 2中,tf2是源码页群源码一个核心组件,用于管理坐标系变换树,源码跟踪和传播不同坐标系之间的源码变换信息。在本教程中,源码我们将探讨如何在lookupTransform()函数中使用超时设置以等待tf2坐标变换树上的源码坐标变换可用。
理解tf2的源码时间机制非常重要。每个坐标系变换都保存了一个时间快照,源码默认最多秒。源码使用lookupTransform()函数时,源码我们获取最新的源码坐标变换,但并不知道该变换的确切时间。本教程将指导你如何获取特定时间的坐标变换。
具体步骤如下:
1. 打开学习tf2的C++软件包中的src/turtle_tf2_listener.cpp源代码文件。在回调函数on_timer()中,我们关注to_frame_rel参数的定义,即在FrameListener类的漫画村 源码构造函数中。将to_frame_rel参数设置为turtle1,让第二只小乌龟跟随第一只小乌龟。
2. 移除或注释掉启动文件中设置target_frame参数的代码行,这将让turtle2跟随turtle1,而不是固定坐标系“胡萝卜(carrot1)”。
3. 更改tf2::TimePoint()为this->now(),这指定了查找当前时刻的坐标变换,并移除超时参数。这导致lookupTransform()函数失败,输出消息提示坐标变换不可用。
4. 使用tf2提供的等待工具,通过在lookupTransform()函数中添加Duration参数来解决此问题。在本例中,等待ms,或者使用以下代码。该函数有四个参数:目标坐标系、源坐标系、查找的时刻以及可选的等待超时时长。设置超时时长后,lookupTransform()将阻塞直到坐标变换可用,足彩菠菜源码或在超时时长内无法获取时引发异常。
5. 超时参数的设置至关重要。如果未设置,系统可能会报错坐标系不存在或坐标变换消息在将来。但也不能设置过长,否则会导致系统阻塞。
6. 重新编译并运行软件包,现在可以正常运行了。
通过本教程,你将了解到如何在ROS 2环境中通过设置超时等待来确保tf2坐标变换的可用性,从而在实时系统中实现稳定的坐标系跟踪。
tf2系列教程(十三):在ROS 2中编写tf2侦听者节点(C++)
. 编写tf2侦听者节点(C++)
描述:本教程将介绍如何使用C++编写一个能够通过tf2获取坐标系变换消息的tf2侦听者节点。
教程级别:入门
在前一个教程中,我们创建了tf2广播者节点来发布小乌龟的位姿到tf2。本教程将创建tf2侦听者节点以开始使用tf2坐标变换消息。
.1 如何创建tf2侦听者节点
使用前两个教程中创建的learning_tf2_cpp软件包,首先进入存放C++源代码的~/dev_ws/src/learning_tf2_cpp/src子目录,运行以下命令创建tf2侦听者节点的源代码文件turtle_tf2_listener.cpp:
在文本编辑器中,将以下代码复制到该文件中,并保存:
.1.1 代码说明
首先导入需要用到的sketchup bim源码库/模块:
tf2发布的坐标变换信息带有时间戳,因此需要包含geometry_msgs的TransformStamped消息类型头文件transform_stamped.hpp。本节点需要计算turtle1和turtle2两个坐标系的坐标差值,因此需要使用Twist消息类型,包含geometry_msgs的Twist消息头文件twist.hpp。ROS 2中,ament_cmake软件包都依赖C++客户端库rclcpp,因此需要包含该库的头文件。本节点需要侦听turtle1的坐标消息,因此需要导入tf2_ros软件包中的TransformListener类和Buffer类,包含这两个类的头文件。此外,还需要处理坐标变换异常的Exception类,包含其头文件。由于本节点需要生成新的小乌龟turtle2,需要调用turtlesim软件包的Spawn服务,导入spawn.hpp模块。上述库/模块/类的导入也代表了该节点的依赖关系,需要将这些依赖包添加到package.xml和CMakeLists.txt文件中。
接着创建了用于侦听turtle1位姿消息的FrameListener节点类,该类继承自rclcpp客户端库的今日房价源码Node类。在FrameListener类中定义了两个函数:一个是公共构造函数,指定节点名称turtle_tf2_frame_listener;申明和获取target_frame参数;创建TransformListener类对象transform_listener_;创建用于生成新小乌龟服务的客户端,并检查服务是否可用;创建turtle2的速度指令发布者对象变量publisher_;以1hz的频率调用on_timer()回调函数。
回调函数on_timer()负责执行turtle1和turtle2两个坐标系之间的坐标变换,并据此向turtle2发布速度指令以对turtle1进行跟随。在该函数中,获取要进行坐标变换的两个坐标系,调用lookupTransform()方法查找坐标变换,根据坐标变换结果计算turtle2的线速度和角速度,然后向turtle2发布速度指令消息。此回调函数的调用频率为1hz,意味着每秒进行一次坐标变换和计算、发布turtle2的速度指令。
最后是定义main()函数。初始化rclcpp客户端库,实例化FrameListener节点对象,旋转节点以调用回调函数,关闭rclcpp客户端库。
.2 构建软件包并运行tf2侦听者节点
编写好C++代码后,在构建和编译该软件包之前,需要编辑learning_tf2_cpp软件包的package.xml和CMakeLists.txt文件,填写软件包描述、许可证、作者等信息,添加相应依赖包和可执行文件等。具体步骤请参考相关教程。
如果已完成前面的教程“在ROS 2中编写tf2静态广播者节点(C++)”,则package.xml文件不用修改;在CMakeLists.txt文件中,添加本教程的可执行文件,并在install(target下面添加一行。
由于需要同时运行turtlesim软件包的turtlesim_node、learning_tf2_cpp软件包的turtle_tf2_broadcaster和turtle_tf2_listener等多个节点,需要通过启动文件组合运行这些节点。在上一教程中创建的launch子目录下为本教程创建learning_tf2_demo.launch.py启动文件,具体命令如下。
将以下代码复制到启动文件中,并保存:
完成上述工作后,构建编译软件包。进入工作空间dev_ws的根目录,并运行以下命令:
编译成功后,需要对该工作空间的安装脚本进行source,命令为:
现在可以运行刚才创建的learning_tf2_demo.launch.py启动文件了,具体命令为:
这样就会打开一个名为Turtlesim的窗口,里面有两只小乌龟。小乌龟turtle2会沿着一条弧形路径靠近小乌龟turtle1。
.3 检查运行结果
要查看本节点是否成功运行或有效,只需要在新终端中运行turtlesim软件包的turtle_teleop_key可执行文件,通过键盘上F键周围的8个字母键和箭头键控制小乌龟的旋转和移动,命令为:
确保运行turtle_teleop_key节点的终端窗口处于活动状态,并通过相应字母键和箭头键移动第一只小乌龟turtle1,这样就会看到第二只小乌龟turtle2会跟随turtle1。
现在可以使用tf2_ros软件包的tf2_echo可执行文件来检查两只小乌龟的位姿是否正在真实地被广播到tf2,命令分别为:
应该会显示第一只乌龟的位姿,如下所示:
此时继续移动turtle1,小乌龟turtle2正在跟随,turtle2的位姿信息也会一直发生变化。
还可以对turtle1和turtle2两个坐标系的坐标变换进行回显,请运行以下命令:
在驱使turtle1移动而turtle2在进行跟随的过程中,会获得如下所示的输出:
这说明已经成功地将两只小乌龟的位姿都广播到了tf2,并实现了对turtle1坐标系的侦听,使用了两只小乌龟坐标系变换信息以让turtle2对turtle1进行跟随。
求一个JAVA计算器源代码。不要按钮的那种。速度。。急用
import java.awt.*;
import java.awt.event.*;
import java.lang.*;
import javax.swing.*;
public class Counter extends Frame
{
//声明三个面板的布局
GridLayout gl1,gl2,gl3;
Panel p0,p1,p2,p3;
JTextField tf1;
TextField tf2;
Button b0,b1,b2,b3,b4,b5,b6,b7,b8,b9,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b;
StringBuffer str;//显示屏所显示的字符串
double x,y;//x和y都是运算数
int z;//Z表示单击了那一个运算符.0表示"+",1表示"-",2表示"*",3表示"/"
static double m;//记忆的数字
public Counter()
{
gl1=new GridLayout(1,4,,0);//实例化三个面板的布局
gl2=new GridLayout(4,1,0,);
gl3=new GridLayout(4,5,,);
tf1=new JTextField();//显示屏
tf1.setHorizontalAlignment(JTextField.RIGHT);
tf1.setEnabled(false);
tf1.setText("0");
tf2=new TextField();//显示记忆的索引值
tf2.setEditable(false);
//实例化所有按钮、设置其前景色并注册监听器
b0=new Button("Backspace");
b0.setForeground(Color.red);
b0.addActionListener(new Bt());
b1=new Button("CE");
b1.setForeground(Color.red);
b1.addActionListener(new Bt());
b2=new Button("C");
b2.setForeground(Color.red);
b2.addActionListener(new Bt());
b3=new Button("MC");
b3.setForeground(Color.red);
b3.addActionListener(new Bt());
b4=new Button("MR");
b4.setForeground(Color.red);
b4.addActionListener(new Bt());
b5=new Button("MS");
b5.setForeground(Color.red);
b5.addActionListener(new Bt());
b6=new Button("M+");
b6.setForeground(Color.red);
b6.addActionListener(new Bt());
b7=new Button("7");
b7.setForeground(Color.blue);
b7.addActionListener(new Bt());
b8=new Button("8");
b8.setForeground(Color.blue);
b8.addActionListener(new Bt());
b9=new Button("9");
b9.setForeground(Color.blue);
b9.addActionListener(new Bt());
b=new Button("/");
b.setForeground(Color.red);
b.addActionListener(new Bt());
b=new Button("sqrt");
b.setForeground(Color.blue);
b.addActionListener(new Bt());
b=new Button("4");
b.setForeground(Color.blue);
b.addActionListener(new Bt());
b=new Button("5");
b.setForeground(Color.blue);
b.addActionListener(new Bt());
b=new Button("6");
b.setForeground(Color.blue);
b.addActionListener(new Bt());
b=new Button("*");
b.setForeground(Color.red);
b.addActionListener(new Bt());
b=new Button("%");
b.setForeground(Color.blue);
b.addActionListener(new Bt());
b=new Button("1");
b.setForeground(Color.blue);
b.addActionListener(new Bt());
b=new Button("2");
b.setForeground(Color.blue);
b.addActionListener(new Bt());
b=new Button("3");
b.setForeground(Color.blue);
b.addActionListener(new Bt());
b=new Button("-");
b.setForeground(Color.red);
b.addActionListener(new Bt());
b=new Button("1/X");
b.setForeground(Color.blue);
b.addActionListener(new Bt());
b=new Button("0");
b.setForeground(Color.blue);
b.addActionListener(new Bt());
b=new Button("+/-");
b.setForeground(Color.blue);
b.addActionListener(new Bt());
b=new Button(".");
b.setForeground(Color.blue);
b.addActionListener(new Bt());
b=new Button("+");
b.setForeground(Color.red);
b.addActionListener(new Bt());
b=new Button("=");
b.setForeground(Color.red);
b.addActionListener(new Bt());
//实例化四个面板
p0=new Panel();
p1=new Panel();
p2=new Panel();
p3=new Panel();
//创建一个空字符串缓冲区
str=new StringBuffer();
//添加面板p0中的组件和设置其在框架中的位置和大小
p0.add(tf1);
p0.setBounds(,,,);
//添加面板p1中的组件和设置其在框架中的位置和大小
p1.setLayout(gl1);
p1.add(tf2);
p1.add(b0);
p1.add(b1);
p1.add(b2);
p1.setBounds(,,,);
//添加面板p2中的组件并设置其的框架中的位置和大小
p2.setLayout(gl2);
p2.add(b3);
p2.add(b4);
p2.add(b5);
p2.add(b6);
p2.setBounds(,,,);
//添加面板p3中的组件并设置其在框架中的位置和大小
p3.setLayout(gl3);//设置p3的布局
p3.add(b7);
p3.add(b8);
p3.add(b9);
p3.add(b);
p3.add(b);
p3.add(b);
p3.add(b);
p3.add(b);
p3.add(b);
p3.add(b);
p3.add(b);
p3.add(b);
p3.add(b);
p3.add(b);
p3.add(b);
p3.add(b);
p3.add(b);
p3.add(b);
p3.add(b);
p3.add(b);
p3.setBounds(,,,);
//设置框架中的布局为空布局并添加4个面板
setLayout(null);
add(p0);
add(p1);
add(p2);
add(p3);
setResizable(false);//禁止调整框架的大小
//匿名类关闭窗口
addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent e1)
{
System.exit(0);
}
});
setBackground(Color.lightGray);
setBounds(,,,);
setVisible(true);
}
//构造监听器
class Bt implements ActionListener
{
public void actionPerformed(ActionEvent e2)
{
try{
if(e2.getSource()==b1)//选择"CE"清零
{
tf1.setText("0");//把显示屏清零
str.setLength(0);//清空字符串缓冲区以准备接收新的输入运算数
}
else if(e2.getSource()==b2)//选择"C"清零
{
tf1.setText("0");//把显示屏清零
str.setLength(0);
}
else if(e2.getSource()==b)//单击"+/-"选择输入的运算数是正数还是负数
{
x=Double.parseDouble(tf1.getText().trim());
tf1.setText(""+(-x));
}
else if(e2.getSource()==b)//单击加号按钮获得x的值和z的值并清空y的值
{
x=Double.parseDouble(tf1.getText().trim());
str.setLength(0);//清空缓冲区以便接收新的另一个运算数
y=0d;
z=0;
}
else if(e2.getSource()==b)//单击减号按钮获得x的值和z的值并清空y的值
{
x=Double.parseDouble(tf1.getText().trim());
str.setLength(0);
y=0d;
z=1;
}
else if(e2.getSource()==b)//单击乘号按钮获得x的值和z的值并清空y的值
{
x=Double.parseDouble(tf1.getText().trim());
str.setLength(0);
y=0d;
z=2;
}
else if(e2.getSource()==b)//单击除号按钮获得x的值和z的值并空y的值
{
x=Double.parseDouble(tf1.getText().trim());
str.setLength(0);
y=0d;
z=3;
}
else if(e2.getSource()==b)//单击等号按钮输出计算结果
{
str.setLength(0);
switch(z)
{
case 0 : tf1.setText(""+(x+y));break;
case 1 : tf1.setText(""+(x-y));break;
case 2 : tf1.setText(""+(x*y));break;
case 3 : tf1.setText(""+(x/y));break;
}
}
else if(e2.getSource()==b)//单击"."按钮输入小数
{
if(tf1.getText().trim().indexOf(′.′)!=-1)//判断字符串中是否已经包含了小数点
{
}
else//如果没数点有小
{
if(tf1.getText().trim().equals("0"))//如果初时显示为0
{
str.setLength(0);
tf1.setText((str.append("0"+e2.getActionCommand())).toString());
}
else if(tf1.getText().trim().equals(""))//如果初时显示为空则不做任何操作
{
}
else
{
tf1.setText(str.append(e2.getActionCommand()).toString());
}
}
y=0d;
}
else if(e2.getSource()==b)//求平方根
{
x=Double.parseDouble(tf1.getText().trim());
tf1.setText("数字格式异常");
if(x<0)
tf1.setText("负数没有平方根");
else
tf1.setText(""+Math.sqrt(x));
str.setLength(0);
y=0d;
}
else if(e2.getSource()==b)//单击了"%"按钮
{
x=Double.parseDouble(tf1.getText().trim());
tf1.setText(""+(0.*x));
str.setLength(0);
y=0d;
}
else if(e2.getSource()==b)//单击了"1/X"按钮
{
x=Double.parseDouble(tf1.getText().trim());
if(x==0)
{
tf1.setText("除数不能为零");
}
else
{
tf1.setText(""+(1/x));
}
str.setLength(0);
y=0d;
}
else if(e2.getSource()==b3)//MC为清除内存
{
m=0d;
tf2.setText("");
str.setLength(0);
}
else if(e2.getSource()==b4)//MR为重新调用存储的数据
{
if(tf2.getText().trim()!="")//有记忆数字
{
tf1.setText(""+m);
}
}
else if(e2.getSource()==b5)//MS为存储显示的数据
{
m=Double.parseDouble(tf1.getText().trim());
tf2.setText("M");
tf1.setText("0");
str.setLength(0);
}
else if(e2.getSource()==b6)//M+为将显示的数字与已经存储的数据相加要查看新的数字单击MR
{
m=m+Double.parseDouble(tf1.getText().trim());
}
else//选择的是其他的按钮
{
if(e2.getSource()==b)//如果选择的是"0"这个数字键
{
if(tf1.getText().trim().equals("0"))//如果显示屏显示的为零不做操作
{
}
else
{
tf1.setText(str.append(e2.getActionCommand()).toString());
y=Double.parseDouble(tf1.getText().trim());
}
}
else if(e2.getSource()==b0)//选择的是“BackSpace”按钮
{
if(!tf1.getText().trim().equals("0"))//如果显示屏显示的不是零
{
if(str.length()!=1)
{
tf1.setText(str.delete(str.length()-1,str.length()).toString());//可能抛出字符串越界异常
}
else
{
tf1.setText("0");
str.setLength(0);
}
}
y=Double.parseDouble(tf1.getText().trim());
}
else//其他的数字键
{
tf1.setText(str.append(e2.getActionCommand()).toString());
y=Double.parseDouble(tf1.getText().trim());
}
}
}
catch(NumberFormatException e){
tf1.setText("数字格式异常");
}
catch(StringIndexOutOfBoundsException e){
tf1.setText("字符串索引越界");
}
}
}
public static void main(String args[])
{
new Counter();
}
}
ROS 源码学习 ---TF
TF作为ROS的核心库之一,其主要功能是维护和传播所有frame之间的变换关系。它通过两个核心函数来实现:broadcast用于发布坐标变换,listener则负责监听并构建坐标变换树,便于用户调用。
初次接触时,我曾质疑为何每个节点都需要独立的listener来维护自己的tf树,是否可以共享全局变换信息以节省资源。在研究amcl包的源码时,我发现了TF的奇特用法,使用了tf2以及额外的tf_static主题,这让我深感困惑。
为了解开这些疑问,我深入阅读了TF的源码和ROS官方的TF2 Wiki。了解到TF2被设计用来替换TF,其中引入了/tf_static来存储静态变换,以及Action Based Query,允许通过action进行查询,避免了多个listener的资源消耗。
在ROS源码中,关键的包如geometry和geometry2提供了核心数据结构,如TransformStorage和TimeCache,它们负责存储和管理变换。BufferCore是整个架构的核心,它处理了tf数据的维护和查找,包括对/tf和/tf_static的订阅与处理。
在Transformer类和TransformListener中,我们看到buffer的使用,它结合了listener的功能,并提供底层的buffer访问。这种设计使得amcl包中的listener可以暴露更底层的功能,如将数据直接注入到listener中。
TF2的改进包括了更高效的存储和查询机制,以及/tf_static提供的静态变换服务。静态变换只保存最新的一个变换,且使用latched topics确保消息不会丢失,从而提供了“总是能查到变换”的特性。
Action Based Query的引入,如buffer_server提供的action服务,使得分布式查询变得更为灵活,支持超时查找,展示了TF2在查询效率上的提升。
总结来说,TF库的设计既体现了ROS框架的简洁,也展示了TF2带来的改进。虽然TF2在某些方面有所优化,但当前TF的实用性仍然很强,没有强制性地替换。通过理解TF的核心数据结构和框架,我们可以更好地利用这个工具。