本帖最后由 lb8820265 于 2022-11-1 14:52 编辑
前面介绍了运行“turtle_teleop_key”节点,然后通过键盘来控制小乌龟。要自己编写代码来控制小乌龟,可以先学习“turtle_teleop_key”节点是怎么编写的。
官方源码
ROS安装后“turtle_teleop_key”节点就已经是二进制文件了,无法看到源码,如下图。
不过在官方github仓库中可以看到源码,该源码中有很多C++和Python的例子供参考,下载下来,解压缩,在如下位置中就可以看到cpp源码。
“turtle_teleop_key.cpp”全部源码如下。
#include <ros/ros.h>
#include <geometry_msgs/Twist.h>
#include <signal.h>
#include <stdio.h>
#ifndef _WIN32
# include <termios.h>
# include <unistd.h>
#else
# include <windows.h>
#endif
#define KEYCODE_RIGHT 0x43
#define KEYCODE_LEFT 0x44
#define KEYCODE_UP 0x41
#define KEYCODE_DOWN 0x42
#define KEYCODE_B 0x62
#define KEYCODE_C 0x63
#define KEYCODE_D 0x64
#define KEYCODE_E 0x65
#define KEYCODE_F 0x66
#define KEYCODE_G 0x67
#define KEYCODE_Q 0x71
#define KEYCODE_R 0x72
#define KEYCODE_T 0x74
#define KEYCODE_V 0x76
class KeyboardReader
{
public:
KeyboardReader()
#ifndef _WIN32
: kfd(0)
#endif
{
#ifndef _WIN32
// get the console in raw mode
tcgetattr(kfd, &cooked);
struct termios raw;
memcpy(&raw, &cooked, sizeof(struct termios));
raw.c_lflag &=~ (ICANON | ECHO);
// Setting a new line, then end of file
raw.c_cc[VEOL] = 1;
raw.c_cc[VEOF] = 2;
tcsetattr(kfd, TCSANOW, &raw);
#endif
}
void readOne(char * c)
{
#ifndef _WIN32
int rc = read(kfd, c, 1);
if (rc < 0)
{
throw std::runtime_error("read failed");
}
#else
for(;;)
{
HANDLE handle = GetStdHandle(STD_INPUT_HANDLE);
INPUT_RECORD buffer;
DWORD events;
PeekConsoleInput(handle, &buffer, 1, &events);
if(events > 0)
{
ReadConsoleInput(handle, &buffer, 1, &events);
if (buffer.Event.KeyEvent.wVirtualKeyCode == VK_LEFT)
{
*c = KEYCODE_LEFT;
return;
}
else if (buffer.Event.KeyEvent.wVirtualKeyCode == VK_UP)
{
*c = KEYCODE_UP;
return;
}
else if (buffer.Event.KeyEvent.wVirtualKeyCode == VK_RIGHT)
{
*c = KEYCODE_RIGHT;
return;
}
else if (buffer.Event.KeyEvent.wVirtualKeyCode == VK_DOWN)
{
*c = KEYCODE_DOWN;
return;
}
else if (buffer.Event.KeyEvent.wVirtualKeyCode == 0x42)
{
*c = KEYCODE_B;
return;
}
else if (buffer.Event.KeyEvent.wVirtualKeyCode == 0x43)
{
*c = KEYCODE_C;
return;
}
else if (buffer.Event.KeyEvent.wVirtualKeyCode == 0x44)
{
*c = KEYCODE_D;
return;
}
else if (buffer.Event.KeyEvent.wVirtualKeyCode == 0x45)
{
*c = KEYCODE_E;
return;
}
else if (buffer.Event.KeyEvent.wVirtualKeyCode == 0x46)
{
*c = KEYCODE_F;
return;
}
else if (buffer.Event.KeyEvent.wVirtualKeyCode == 0x47)
{
*c = KEYCODE_G;
return;
}
else if (buffer.Event.KeyEvent.wVirtualKeyCode == 0x51)
{
*c = KEYCODE_Q;
return;
}
else if (buffer.Event.KeyEvent.wVirtualKeyCode == 0x52)
{
*c = KEYCODE_R;
return;
}
else if (buffer.Event.KeyEvent.wVirtualKeyCode == 0x54)
{
*c = KEYCODE_T;
return;
}
else if (buffer.Event.KeyEvent.wVirtualKeyCode == 0x56)
{
*c = KEYCODE_V;
return;
}
}
}
#endif
}
void shutdown()
{
#ifndef _WIN32
tcsetattr(kfd, TCSANOW, &cooked);
#endif
}
private:
#ifndef _WIN32
int kfd;
struct termios cooked;
#endif
};
KeyboardReader input;
class TeleopTurtle
{
public:
TeleopTurtle();
void keyLoop();
private:
ros::NodeHandle nh_;
double linear_, angular_, l_scale_, a_scale_;
ros::Publisher twist_pub_;
};
TeleopTurtle::TeleopTurtle():
linear_(0),
angular_(0),
l_scale_(2.0),
a_scale_(2.0)
{
nh_.param("scale_angular", a_scale_, a_scale_);
nh_.param("scale_linear", l_scale_, l_scale_);
twist_pub_ = nh_.advertise<geometry_msgs::Twist>("turtle1/cmd_vel", 1);
}
void quit(int sig)
{
(void)sig;
input.shutdown();
ros::shutdown();
exit(0);
}
int main(int argc, char** argv)
{
ros::init(argc, argv, "teleop_turtle");
TeleopTurtle teleop_turtle;
signal(SIGINT,quit);
teleop_turtle.keyLoop();
quit(0);
return(0);
}
void TeleopTurtle::keyLoop()
{
char c;
bool dirty=false;
puts("Reading from keyboard");
puts("---------------------------");
puts("Use arrow keys to move the turtle. 'q' to quit.");
for(;;)
{
// get the next event from the keyboard
try
{
input.readOne(&c);
}
catch (const std::runtime_error &)
{
perror("read():");
return;
}
linear_=angular_=0;
ROS_DEBUG("value: 0x%02X\n", c);
switch(c)
{
case KEYCODE_LEFT:
ROS_DEBUG("LEFT");
angular_ = 1.0;
dirty = true;
break;
case KEYCODE_RIGHT:
ROS_DEBUG("RIGHT");
angular_ = -1.0;
dirty = true;
break;
case KEYCODE_UP:
ROS_DEBUG("UP");
linear_ = 1.0;
dirty = true;
break;
case KEYCODE_DOWN:
ROS_DEBUG("DOWN");
linear_ = -1.0;
dirty = true;
break;
case KEYCODE_Q:
ROS_DEBUG("quit");
return;
}
geometry_msgs::Twist twist;
twist.angular.z = a_scale_*angular_;
twist.linear.x = l_scale_*linear_;
if(dirty ==true)
{
twist_pub_.publish(twist);
dirty=false;
}
}
return;
}
代码写的很复杂,主要的思路是获取键盘按下键位,然后发送一次数据给小乌龟节点。
代码编写
下面参考上述代码,写一个简化版的控制小乌龟节点代码。工程创建可以参考,ROS学习(VSCode实现Hello world),主要步骤如下:
1. 建立工作空间
将工程建立在桌面“Example”文件夹中,进入在该文件,然后新建“ROS”文件夹和“src”子文件夹,并编译,最后输入“code .”用VSCode打开。
2. 添加Package包与依赖
设置包名“turtle_teleop”,添加依赖“roscpp rospy std_msgs geometry_msgs turtlesim”
3. 编写代码
新建文件“turtle_teleop.cpp”,代码如下
#include <ros/ros.h>
#include <geometry_msgs/Twist.h>
int main(int argc, char **argv)
{
ros::init(argc, argv, "velocity_publisher");
ros::NodeHandle n;
ros::Publisher turtle_vel_pub = n.advertise<geometry_msgs::Twist>("/turtle1/cmd_vel", 10);
ros::Rate loop_rate(10);
int count = 0;
while (ros::ok())
{
geometry_msgs::Twist vel_msg;
vel_msg.linear.x = 0.5;
vel_msg.angular.z = 0.2;
turtle_vel_pub.publish(vel_msg);
ROS_INFO("Publsh turtle velocity command[%0.2f m/s, %0.2f rad/s]",
vel_msg.linear.x, vel_msg.angular.z);
loop_rate.sleep();
}
return 0;
}
上述代码的核心就是信息的发布,可以参考官网的教程“编写简单的发布者和订阅者(C++)”
之后是配置文件和编译,这里省略。
效果演示
1. 启动内核
在VSCode界面按下“Ctrl+Shift+P”,输入
ROS:Start
或者在终端中输入“roscore”也可以启动ros内核。
2. 运行“turtlesim_node”节点
打开一个新终端,输入,打开小乌龟界面,
rosrun turtlesim turtlesim_node
3. 运行
在VSCode界面按下“Ctrl+Shift+P”打开命令窗口,输入“ROS:Run a ROS executable”,回车后输入包名“turtle_teleop”回车后输入执行文件名“turtle_teleop_node”,就可以看到代码在控制小乌龟旋转运动。
参考
ROS知识:用你自己的程序控制小乌龟_无水先生的博客-CSDN博客_ros小乌龟
问题
如何远程控制小乌龟呢?
源码
GitHub:https://github.com/wanli-car/ROS_Examples
Gitee:https://gitee.com/wanli-car/ROS_Examples