时间对于机器人操作系统非常重要。
所有机器人类的编程中所涉及的变量如果需要在网络中传输都需要这个数据结构的时间戳。
宏观上,ROS1、ROS2各版本都有官方支持的时间节点。
ROS时钟--支持时间倒计时小工具
效果如下:
如果要部署机器人操作系统,ROS1最好选择noetic,ROS2最好选择humble。
学习核心要点:
ros1:indigo、kinetic、melodic、noetic均可。
ros2:foxy、humble均可。
时钟和时间
官网介绍了支持编程的 ROS 原理和应用,这些编程既可以实时运行,也可以模拟时间运行,后者可能更快或更慢。
背景知识
许多机器人算法本质上依赖于定时和同步。 为此,要求在ROS网络中运行的节点具有同步的系统时钟,以便它们可以准确地报告事件的时间戳。
与此同时,在很多实际案例中,能够控制系统的进度很重要。
实时计算需要严格的时间控制。还有后续处理如下:
在回放记录的数据时,支持对时间进度的加速、减慢或阶梯控制通常非常有价值。 此控件允许到达特定时间并暂停系统,以便可以对其进行深入调试。 可以使用传感器数据的日志来执行此操作,但是如果传感器数据与系统的其余部分不同步,则会破坏许多算法。
使用抽象时间源的另一个重要用例是,当针对模拟机器人而不是真实机器人运行记录的数据时。 根据仿真特性,模拟器可能能够比实时运行得快得多,或者可能需要运行得更慢。 比实时运行得更快的速度对于高级测试以及允许重复系统测试很有价值。 对于精度比速度更重要的复杂系统,比实时仿真慢是必要的。 通常,仿真是系统的限制因素,因此模拟器可以成为更快或更慢播放的时间源。 此外,如果模拟暂停,系统也可以使用相同的机制暂停。
为了提供简化的时间接口,将提供 ROS 时间和持续时间数据类型。 要查询最新时间,将提供 ROS 时钟接口。 时间源可以管理一个或多个时钟实例。
使用抽象时间的挑战
有许多同步算法,它们通常可以实现比网络上设备之间网络通信延迟更好的精度。 但是,这些算法利用了有关时间恒定和连续性质的假设。
使用抽象时间的一个重要方面是能够操纵时间。 在某些情况下,加速、减慢或完全暂停时间对于调试非常重要。
支持暂停时间的能力要求不假设时间值总是在增加。
当通信时间传播的变化时,通信网络中的延迟成为一个挑战。 时间抽象的任何更改都必须传达给图中的其他节点,但会受到正常的网络通信延迟的影响。 这种不准确性与通信延迟成正比,也与模拟时间与实时相比前进的速率增加成正比(“实时因素”)。 如果在使用时间抽象时需要非常准确的时间戳,可以通过减慢实时因素来实现,从而使通信延迟相对较小。
最后一个挑战是时间抽象必须能够向后跳转,此功能对于日志文件播放非常有用。 此行为类似于负日期更改后的系统时钟,并且要求开发人员使用时间抽象来确保其算法可以处理不连续性。 必须为开发人员 API 提供适当的 API,以启用向前和向后的时间跳转通知。
使用介绍
通常,ROS 客户端库将使用计算机的系统时钟作为时间源,也称为“clock”或“挂钟”(如实验室墙上的时钟)。但是,当运行模拟或回放记录的数据时,通常需要让系统使用模拟时钟,以便可以加速、减慢或逐步控制系统的感知时间。例如,如果要将传感器数据回放到系统中,则可能希望时间与传感器数据的时间戳相对应。
为了支持这一点,ROS 客户端库可以监听用于发布“模拟时间”的 /clock 主题。
为了使代码利用 ROS 模拟时间,所有代码都必须使用适当的 ROS 客户端库时间 API 来访问时间和睡眠,而不是使用语言原生例程。这将使您的系统无论使用挂钟还是模拟时钟,都能进行一致的时间测量。下面简要介绍了这些 API,但您应该熟悉所选的客户端库以获取更多详细信息。
在多台计算机上使用挂钟时间时,在它们之间同步时间非常重要。ROS 不提供此功能,因为已经有成熟的方法(例如 ntp,我们推荐的同步工具是 chrony)来执行此操作。如果不同步多台机器的时钟,它们将不会就 tf 中使用的时间计算达成一致。
注意:这不是实时系统的 API!
使用 /clock 主题中的模拟时间
为了让ROS节点根据/clock主题使用模拟时间,在初始化节点之前,必须将/use_sim_time参数设置为true。这可以在启动文件中或从命令行完成。
如果设置了 /use_sim_time 参数,ROS 时间 API 将返回 time=0,直到收到来自 /clock 主题的值。然后,时间将仅在收到来自 /clock 主题的消息时更新,并且在更新之间保持不变。
对于使用模拟时间时持续时间的计算,客户端应始终等到收到第一个非零时间值后再开始,因为 /clock 主题中的第一个模拟时间值可能很高。
注意:在 ROS C Turtle 之前,节点会自动订阅 /clock 主题,如果有任何内容发布到 /clock 主题,节点将使用模拟时间。
运行时钟服务器
时钟服务器是发布到 /clock 主题的任何节点,单个 ROS 网络中不应运行多个节点。在大多数情况下,时钟服务器是模拟器或日志回放工具。
为了解决启动顺序的任何问题,在使用时钟服务器的任何启动文件中将 /use_sim_time 参数设置为 true 非常重要。如果您正在播放带有 rosbag 播放的包文件,则使用 --clock 选项将在播放包文件时运行时钟服务器。
最简单的案例演示:
ROS1
roscpp
//Get the time and store it in the time variable.
ros::Time time = ros::Time::now();
//Wait a duration of one second.
ros::Duration d = ros::Duration(1, 0);
d.sleep();
rospy
rospy.get_rostime() #get time as rospy.Time instance
rospy.get_time() #get time as float secs
rospy.sleep(duration)
具体cpp案例:
获取当前时间
ros::Time begin = ros::Time::now();
创建时间和持续时间实例
浮点数
ros::Time a_little_after_the_beginning(0.001);
ros::Duration five_seconds(5.0);
整型数
ros::Time a_little_after_the_beginning(0, 1000000);
ros::Duration five_seconds(5, 0);
转换时间和持续时间实例
double secs =ros::Time::now().toSec();
ros::Duration d(0.5);
secs = d.toSec();
时间和持续时间算术
ros::Duration two_hours = ros::Duration(60*60) + ros::Duration(60*60);
ros::Duration one_hour = ros::Duration(2*60*60) - ros::Duration(60*60);
ros::Time tomorrow = ros::Time::now() + ros::Duration(24*60*60);
ros::Duration negative_one_day = ros::Time::now() - tomorrow;
云端实践-蓝桥ROS云课
使用turtlesim看一下时间戳的重要性。
在三个终端分别输入:
roscore
rosrun turtlesim turtlesim_node [13:46:56]
[ INFO] [1677476826.846406306]: Starting turtlesim with node name /turtlesim
[ INFO] [1677476826.858068449]: Spawning turtle [turtle1] at x=[5.544445], y=[5.544445], theta=[0.000000]
rosrun turtlesim draw_square [13:46:58]
[ INFO] [1677476941.925480026]: New goal [7.544445 5.544445, 0.000000]
[ INFO] [1677476943.845465404]: Reached goal
[ INFO] [1677476943.845537205]: New goal [7.448444 5.544445, 1.570796]
[ INFO] [1677476947.797971492]: Reached goal
[ INFO] [1677476947.798056675]: New goal [7.454037 7.544437, 1.568000]
[ INFO] [1677476949.733859442]: Reached goal
[ INFO] [1677476949.733924604]: New goal [7.453769 7.448437, 3.138796]
[ INFO] [1677476953.669993390]: Reached goal
[ INFO] [1677476953.670053662]: New goal [5.453913 7.472422, 3.129600]
[ INFO] [1677476955.605801148]: Reached goal
[ INFO] [1677476955.605865104]: New goal [5.549906 7.471271, 4.700396]
[ INFO] [1677476959.541884586]: Reached goal
[ INFO] [1677476959.541945308]: New goal [5.507532 5.471720, 4.691200]
[ INFO] [1677476961.478066122]: Reached goal
[ INFO] [1677476961.478127597]: New goal [5.509565 5.567698, 6.261996]
[ INFO] [1677476965.413272345]: Reached goal
[ INFO] [1677476965.413332146]: New goal [7.508642 5.506937, 6.252800]
[ INFO] [1677476967.349857822]: Reached goal
[ INFO] [1677476967.349916915]: New goal [7.412686 5.509853, 1.540412]
[ INFO] [1677476971.285716384]: Reached goal
这里面时间不是常见格式的,那么参考如下:
rqt→console
其中关于时间:
ros::Timer timer = nh.createTimer(ros::Duration(0.016), boost::bind(timerCallback, _1, twist_pub));
Node: /draw_square
Time: 13:55:19.311055762 (2023-02-27)
Severity: Info
Published Topics: /rosout, /turtle1/cmd_vel
New goal [5.306689 5.861878, 5.998100]
Location:
/tmp/binarydeb/ros-kinetic-turtlesim-0.7.1/tutorials/draw_square.cpp:printGoal:41
#include <boost/bind.hpp>
#include <ros/ros.h>
#include <turtlesim/Pose.h>
#include <geometry_msgs/Twist.h>
#include <std_srvs/Empty.h>
turtlesim::PoseConstPtr g_pose;
turtlesim::Pose g_goal;
enum State
{
FORWARD,
STOP_FORWARD,
TURN,
STOP_TURN,
};
State g_state = FORWARD;
State g_last_state = FORWARD;
bool g_first_goal_set = false;
#define PI 3.141592
void poseCallback(const turtlesim::PoseConstPtr& pose)
{
g_pose = pose;
}
bool hasReachedGoal()
{
return fabsf(g_pose->x - g_goal.x) < 0.1 && fabsf(g_pose->y - g_goal.y) < 0.1 && fabsf(g_pose->theta - g_goal.theta) < 0.01;
}
bool hasStopped()
{
return g_pose->angular_velocity < 0.0001 && g_pose->linear_velocity < 0.0001;
}
void printGoal()
{
ROS_INFO("New goal [%f %f, %f]", g_goal.x, g_goal.y, g_goal.theta);
}
void commandTurtle(ros::Publisher twist_pub, float linear, float angular)
{
geometry_msgs::Twist twist;
twist.linear.x = linear;
twist.angular.z = angular;
twist_pub.publish(twist);
}
void stopForward(ros::Publisher twist_pub)
{
if (hasStopped())
{
ROS_INFO("Reached goal");
g_state = TURN;
g_goal.x = g_pose->x;
g_goal.y = g_pose->y;
g_goal.theta = fmod(g_pose->theta + PI/2.0, 2*PI);
printGoal();
}
else
{
commandTurtle(twist_pub, 0, 0);
}
}
void stopTurn(ros::Publisher twist_pub)
{
if (hasStopped())
{
ROS_INFO("Reached goal");
g_state = FORWARD;
g_goal.x = cos(g_pose->theta) * 2 + g_pose->x;
g_goal.y = sin(g_pose->theta) * 2 + g_pose->y;
g_goal.theta = g_pose->theta;
printGoal();
}
else
{
commandTurtle(twist_pub, 0, 0);
}
}
void forward(ros::Publisher twist_pub)
{
if (hasReachedGoal())
{
g_state = STOP_FORWARD;
commandTurtle(twist_pub, 0, 0);
}
else
{
commandTurtle(twist_pub, 1.0, 0.0);
}
}
void turn(ros::Publisher twist_pub)
{
if (hasReachedGoal())
{
g_state = STOP_TURN;
commandTurtle(twist_pub, 0, 0);
}
else
{
commandTurtle(twist_pub, 0.0, 0.4);
}
}
void timerCallback(const ros::TimerEvent&, ros::Publisher twist_pub)
{
if (!g_pose)
{
return;
}
if (!g_first_goal_set)
{
g_first_goal_set = true;
g_state = FORWARD;
g_goal.x = cos(g_pose->theta) * 2 + g_pose->x;
g_goal.y = sin(g_pose->theta) * 2 + g_pose->y;
g_goal.theta = g_pose->theta;
printGoal();
}
if (g_state == FORWARD)
{
forward(twist_pub);
}
else if (g_state == STOP_FORWARD)
{
stopForward(twist_pub);
}
else if (g_state == TURN)
{
turn(twist_pub);
}
else if (g_state == STOP_TURN)
{
stopTurn(twist_pub);
}
}
int main(int argc, char** argv)
{
ros::init(argc, argv, "draw_square");
ros::NodeHandle nh;
ros::Subscriber pose_sub = nh.subscribe("turtle1/pose", 1, poseCallback);
ros::Publisher twist_pub = nh.advertise<geometry_msgs::Twist>("turtle1/cmd_vel", 1);
ros::ServiceClient reset = nh.serviceClient<std_srvs::Empty>("reset");
ros::Timer timer = nh.createTimer(ros::Duration(0.016), boost::bind(timerCallback, _1, twist_pub));
std_srvs::Empty empty;
reset.call(empty);
ros::spin();
}