目录
我们不是来蹭“时间管理大师”热度的,而是实实在在讨论ROS2获取时间戳的技术。
既然要讨论“时间管理”,一向想好学的我们,肯定得去网上查查大家和专家是怎么理解,怎么定义的。反正,不管生活、工作还是学习,都用得上。
时间管理是指通过事先规划和运用一定的技巧、方法与工具实现对时间的灵活以及有效运用,从而实现个人或组织的既定目标的过程。
说得有点复杂,我们通俗地讲,时间管理是基本技能,干啥都需要。
对于ROS2环境里的软件编程而言,时间管理无外乎两类,定频与计时。
定频,以固定周期持续运转;计时,更加灵活,与回调函数配合使用。
(扩展知识:回调函数与spin_once或spin配合使用,参见下文)
一文搞懂ROS2的spin_some, spin和ROS的spinOnce
https://blog.csdn.net/slampai/article/details/127992755
C++ 标准库中有三种时钟,且都在chrono包中。他们分别是system_clock、steady_clock和high_resolution_clock。
三者用法相似,我们已system_clock为例说明,其他两种在system_clock对应位置替换即可。
#include <chrono>
#include <iostream>
#include <time>
std::chrono::steady_clock::time_point start= std::chrono::system_clock::now();
// do something...
std::chrono::steady_clock::time_point end = std::chrono::system_clock::now();
std::chrono::duration<double> diff = end - start;
std::cout << "Time span(s): " << diff.count() << " s\n";
std::cout << "Time span(ns): "
<< std::chrono::duration_cast<std::chrono::microseconds>(end - start).count()
<< "us.\n";
再来说说三者的区别。
system_clock是系统时钟。可修改的,也可同步网络时间。可以认为有很大的相对性和主观性。steady_clock是单调时钟。其时间点单向递增,类似秒表,通常精度能达到纳秒级别,适合计时。
对于high_resotion_clock,简单的说,不推荐使用,因为不适合移植和稳定编译。
high_resolution_clock在不同标准库之间有不同的实现。通常它只是system_clock和steay_clock的别名,取决于库或配置。例如对于gcc的libstdc++它是system_clock,对于MSVC,它是steady_clock,而对于clang的libc++,它取决于配置。所以推荐直接使用对应的时钟,而不是high_resolution_clock。
简单地说,不推荐使用high_resotion_clock。
与C++类似,ROS2中定义了三种时钟。
默认是使用RCL_SYSTEM_TIME,即系统时间,对应C++的system_clock。
typedef enum rcl_clock_type_t
{
/// Clock uninitialized
RCL_CLOCK_UNINITIALIZED = 0,
/// Use ROS time
RCL_ROS_TIME,
/// Use system time
RCL_SYSTEM_TIME,
/// Use a steady clock time
RCL_STEADY_TIME
} rcl_clock_type_t;
ROS2中存在两种时间戳,一种是实际的物理系统时间,另一种是仿真时间,对应着在线与仿真。两种时间通过参数可以use_sim_time设置,可以写在launch文件,或执行ros2 launch时手动添加。
仿真时间通常是Gazebo发出的/clock话题。当需要在仿真环境中测试程序时,要将use_sim_time置为True,否则会报错,表示时间不匹配。tf关系不能正常找到。
在说ROS2之前,先用一句话总结:ROS1直接用Time类型。
ros::Time now = ros::Time::now();
然而,ROS2有了变化,使用builtin_interfaces/msg/Time类型。
总的来说,ROS2有三种形式可以获得当前时间。
#include <rclcpp/rclcpp.hpp>
# 为方便说明获取时间的方法,先创建一个node
auto node = rclpy.node.Node("exampleNode");
# 第一种方法不受use_sim_time的影响,总是输出物理系统的真实时间。
auto t1 = rclcpp::Clock().now();
# 后面两种方法,要区分物理系统时间和仿真时间。
auto t2 = node->get_clock()->now();
auto t3 = node->now();
我们稍微归纳总结以下:
1)关闭仿真模式时,ROS2的三个方法rclcpp::Clock().now(),this->get_clock()->now()和this->now()获取的时间与C++里的std::chrono::system_clock::now()是一致的,都是系统时间。
2)打开仿真模式后,rclcpp::Clock().now()依然等于std::chrono::system_clock::now(),无法获取仿真时间。
3)this->get_clock()->now()和this->now()是等效的,可以返回真实时间,也可返回仿真时间。
更进一步,我们提炼一个更简单更适用的结论:
ROS2代码中要获取时间戳时,直接使用
this->get_clock()->now()或this->now()接口,这样可以保证在标志位use_sim_time变化时,代码各处使用的时间戳是一致的。
这里值得多说几句的是,ROS2的三个方法rclcpp::Clock().now(),this->get_clock()->now()和this->now()获取的时间戳均包含seconds()和nanoseconds()两个等价方法,两者仅单位不同,前者是秒,后者是纳秒。
rclcpp::Node::SharedPtr node_handle = nullptr;
node_handle = rclcpp::Node::make_shared("newNode");
//获取时间戳
builtin_interfaces::msg::Time now = node->get_clock()->now();
//分别获取秒和纳秒
double time_s = node->get_clock()->now().seconds();
double time_ns = node_handle->get_clock()->now().nanoseconds();
同理,ROS2中的时间类型builtin_interfaces::msg::Time是需要把秒和纳秒组合起来才能表示当前时间的。
// 如果获取的是msg时间戳,恢复成完整表达如下
auto now_sec = msg->header.stamp.sec + msg->header.stamp.nanosec * 1e-9;
如果要计算程序运行时间,依然与C++类似,选用RCL_STEADY_TIME时钟比较准确的。
rclcpp::Clock steady_clock_{RCL_STEADY_TIME};
auto start_time = steady_clock_.now();
//do something ...
auto cycle_duration = steady_clock_.now() - start_time;
RCLCPP_INFO(get_logger(), "Cost %.4f s", cycle_duration.seconds());
用法很直观,回调函数可以直接按常规方法定义,也可以用lambda表示达。
#include "rclcpp/rclcpp.hpp"
#include <iostream>
#include <time.h>
#include <chrono>
// using namespace std;
using namespace std::chrono_literals;
// 第一个回调函数
auto timer1_callback =
[]() -> void
{
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "-------timer callback!-----------");
// do something else;
};
// 第二个回调函数
void timer2_callback()
{
// 获取时间
time_t tt;
time( &tt );
// 调整时区
tt = tt + 8*3600;
tm* t= gmtime( &tt );
// 打印到终端上
std::cout<<"current time - "<<t->tm_hour<<":"<<t->tm_min<<":"<<t->tm_sec<<std::endl;
}
int main(int argc, char** argv){
rclcpp::init(argc, argv);
rclcpp::Node::SharedPtr node_handle = nullptr;
node_handle = rclcpp::Node::make_shared("newNode");
//创建两个定时器,并定义回调函数
rclcpp::TimerBase::SharedPtr timer1,timer2;
timer1 = node_handle->create_wall_timer(500ms, timer1_callback);
timer2 = node_handle->create_wall_timer(1s, timer2_callback);
rclcpp::spin(node_handle);
rclcpp::shutdown();
return 0;
}
上面程序主要看这一段
//创建两个定时器,并定义回调函数
rclcpp::TimerBase::SharedPtr timer1,timer2;
timer1 = node_handle->create_wall_timer(500ms, timer1_callback);
timer2 = node_handle->create_wall_timer(1s, timer2_callback);
另外,提醒以下,为了用好create_wall_timer,记得声明。
using namespace std::chrono_literals;
没错,有两个让人困惑的rate。那就在源码里看看定义吧。
// Rate,使用系统时钟
using rclcpp::rate::Rate = typedef GenericRate<std::chrono::system_clock>
// WallRate,使用稳定时钟
using rclcpp::rate::WallRate = typedef GenericRate<std::chrono::steady_clock>
嗯,也就这点区别,很容易理解。Rate与system_clock对应,WallRate则与steady_clock对应。
使用起来有类似单片机或嵌入式系统的中断的味道。
以WallRate为例,我们看看示例用法。
#include "rclcpp/rclcpp.hpp"
#include <time.h>
using namespace std;
int main(int argc, char** argv){
rclcpp::init(argc, argv);
rclcpp::Node::SharedPtr node_handle = nullptr;
node_handle = rclcpp::Node::make_shared("newNode");
// 定频10Hz
rclcpp::WallRate loop_rate(10.0);
while (rclcpp::ok())
{
RCLCPP_INFO(rclcpp::get_logger("newNode"), "-------timer callback!-----------");
/* 可以再干点别的事 */
loop_rate.sleep();
}
return 0;
}
关于rate使用的注意事项,放到python版本一起讲。
使用thread
#include "rclcpp/rclcpp.hpp"
#include <thread>
class CustomizedNode: public rclcpp::Node
{
public:
CustomizedNode(): Node("rate_node"), loop_rate_(1.0)
{
RCLCPP_INFO(this->get_logger(), "--CustomizedNode Inititalization!--");
std::thread task01(std::bind(&CustomizedNode::_function_A, this));
task01.join();
}
void _function_A()
{
while (rclcpp::ok())
{
RCLCPP_INFO(this->get_logger(), "-------Rate callback!-----------");
/* 可以再干点别的事 */
loop_rate_.sleep();
}
}
private:
rclcpp::WallRate loop_rate_;
};
int main(int argc, char** argv)
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<CustomizedNode>());
rclcpp::shutdown();
return 0;
}
在 Python 中,通常有三种方式来表示时间:时间戳、格式化的时间字符串、元组struct_time。
先说后面两种在ROS2中不常用的方式。
一个是格式化的时间字符串,通用用于打印,常用于人机交互和显示;另外一个是元组(struct_time),以元组形式(或理解成结构体形式)来表达时间,可以单独获得 9 个元素,比如年月日、时分秒、一周的第几日、一年的第几天以及是否夏令时。
再说说我们在ROS2中常用于时间记录的时间戳。
时间戳,即timestamp。通常来说,时间戳表示的是从1970年1月1日00:00:00开始按秒计算的偏移量。常用命令为time.time() ,返回的是float类型。
import time
# 返回时间戳,一个浮点数
time.time()
# 返回时间元组
# time.struct_time(tm_year=2022, tm_mon=12, tm_mday=4, tm_hour=12, tm_min=34, tm_sec=56, tm_wday=6, tm_yday=338, tm_isdst=0)
time.localtime()
# 返回时间字符串,比如:'Sun Dec 4 12:34:56 2022'
time.asctime()
另外,介绍两个有用的时间格式转换方法,用于打印时间信息。
# 时间戳转成字符串形式,默认转化为time.asctime()的形式。
# 如果参数未给或者为 None 的时候,将会默认time.time()为参数。
time.ctime()
# time.strftime(format,[t])
# 把时间元组(struct_time)转化为格式化的时间字符串。
# 如果参数t未指定,将传入time.localtime()。
time.strftime("%Y年-%m月-%d日 %H:%M:%S", time.localtime()))
我个人认为,只要你理解了timer,那用起来真的是简单粗暴。
import time
import rclpy
from rclpy.node import Node
import threading
# 定时器设定的周期,单位s
TIMER_INTERVAL = 1
# 功能函数执行的时间(用time.sleep仿真)
SLEEP_INTERVAL = 0.1
class CustomizedNode(Node):
def __init__(self, name):
super().__init__(name)
# 创建计时器,并绑定回调函数
self._timer = self.create_timer(TIMER_INTERVAL, self._function_A)
# 需要定时执行的函数
def _function_A(self):
self.get_logger().info(f"[_function_A]: Lalala, I'm a callback now!, sleep for {SLEEP_INTERVAL}s")
time.sleep(SLEEP_INTERVAL) #休眠,仿真出函数执行的耗时
def main():
rclpy.init()
node = CustomizedNode("RobotNode")
rclpy.spin(node)
rclpy.shutdown()
if __name__=="__main__":
main()
被誉为定频神器的rate,其实有很大的局限性。rate设置的频率必须得到保证,如果循环执行的任务用时超出rate频率,那么rate.sleep()是没有作用的。
我们先来看看下面的例子。
import time
import rclpy
from rclpy.node import Node
# 设置循环调用的频率
RATE_INTERVAL = 5
# 功能函数执行的时间(用time.sleep替代)
SLEEP_TIME = 0.1
# 循环的功能函数
def main_work(node, count):
node.get_logger().info(f"sleep for {SLEEP_TIME}s - {count} times")
time.sleep(SLEEP_TIME) #休眠,仿真出函数执行的耗时
if __name__=="__main__":
rclpy.init()
node = Node("exampleNode")
rate = node.create_rate(RATE_INTERVAL)
start = time.time()
for i in range(RATE_INTERVAL):
# 循环执行的功能
main_work(node, i)
rclpy.spin_once(node)
rate.sleep() #调用一次rate.sleep()
end = time.time()
print(f'循环{RATE_INTERVAL}次,耗时:{end-start}')
rclpy.shutdown()
在下面的例子里,rate频率RATE_INTERVAL取5,对应的执行周期是0.2,那么循环的功能主体的时间应小于0.2,此时,rate.sleep()是能实现定频的。

但是,如果此后将SLEEP_TIME改成1,你会发现,rate.sleep()不按照设定工作了。

对于定制节点,为了能一直循环使用rate,我们单独开一个线程。
方法也很简单,示例代码如下:
import rclpy
from rclpy.node import Node
import threading
class CustomizedNode(Node):
def __init__(self, name):
super().__init__(name)
# 创建Rate,数量随意,可1或多
self._rate = self.create_rate(1)
# 创建线程,数量随意,可1或多
self._thread_1 = threading.Thread(target=self._function_A)
self._thread_1.start()
self._thread_2 = threading.Thread(target=self._function_B)
self._thread_2.start()
# 需要在线程里调用的函数
def _function_A(self):
while rclpy.ok():
print("[_function_A]: Yes, commander!")
self._rate.sleep()
# 需要在线程里调用的函数
def _function_B(self):
while rclpy.ok():
print("[_function_B]: Show me the money!")
self._rate.sleep()
if __name__=="__main__":
rclpy.init()
node = CustomizedNode("RobotNode")
rclpy.spin(node)
rclpy.shutdown()
ROS2中的时间戳和程序执行时间的计算,有很多种选择,都很简洁易用,不要有心理负担。
注意区分system_clock与steady_clock,也要注意use_sim_time分辨物理系统时间与仿真时间。
也许文章有点长,各所所需,遇到问题时,能有个参照也好。
希望对你有所帮助。
只是想确保我理解了事情。据我目前收集到的信息,Cucumber只是一个“包装器”,或者是一种通过将事物分类为功能和步骤来组织测试的好方法,其中实际的单元测试处于步骤阶段。它允许您根据事物的工作方式组织您的测试。对吗? 最佳答案 有点。它是一种组织测试的方式,但不仅如此。它的行为就像最初的Rails集成测试一样,但更易于使用。这里最大的好处是您的session在整个Scenario中保持透明。关于Cucumber的另一件事是您(应该)从使用您的代码的浏览器或客户端的角度进行测试。如果您愿意,您可以使用步骤来构建对象和设置状态,但通常您
我正在寻找一个用ruby演示计时器的在线示例,并发现了下面的代码。它按预期工作,但这个简单的程序使用30Mo内存(如Windows任务管理器中所示)和太多CPU有意义吗?非常感谢deftime_blockstart_time=Time.nowThread.new{yield}Time.now-start_timeenddefrepeat_every(seconds)whiletruedotime_spent=time_block{yield}#Tohandle-vesleepinteravalsleep(seconds-time_spent)iftime_spent
在Rails自动生成的功能测试(test/functional/products_controller_test.rb)中,我看到以下代码:classProductsControllerTest我的问题是:方法调用products()在哪里/如何定义?products(:one)到底是什么意思?看代码,大概意思是“创建一个产品”,但是它是如何工作的呢?注意我是Ruby/Rails的新手,如果这些是微不足道的问题,我深表歉意。 最佳答案 如果您查看test/fixtures文件夹,您会看到一个products.yml文件。这是在您创建
在我的一些Controller中,我有一个before_filter检查用户是否登录?用于CRUD操作。application.rbdeflogged_in?unlesscurrent_userredirect_toroot_pathendendprivatedefcurrent_user_sessionreturn@current_user_sessionifdefined?(@current_user_session)@current_user_session=UserSession.findenddefcurrent_userreturn@current_userifdefine
require'pp'p*1..10这会打印出1-10。为什么这么简洁?您还可以用它做什么? 最佳答案 它是“splat”运算符。它可用于分解数组和范围并在赋值期间收集值。这里收集赋值中的值:a,*b=1,2,3,4=>a=1b=[2,3,4]在此示例中,内部数组([3,4])中的值被分解并收集到包含数组中:a=[1,2,*[3,4]]=>a=[1,2,3,4]您可以定义将参数收集到数组中的函数:deffoo(*args)pargsendfoo(1,2,"three",4)=>[1,2,"three",4]
我读过的关于Ruby符号的每一篇文章都在谈论符号相对于字符串的效率。但是,这不是1970年代。我的电脑可以处理一些额外的垃圾收集。我错了吗?我拥有最新最好的奔腾双核处理器和4GBRAM。我认为这应该足以处理一些字符串。 最佳答案 您的计算机可能能够处理“一点点额外的垃圾收集”,但是当“一点点”发生在运行数百万次的内部循环中时呢?如果它在内存有限的嵌入式系统上运行呢?有很多地方你可以随意使用字符串,但在某些地方你不能。这完全取决于上下文。 关于ruby-现代计算机的功能是否不足以处理字符串
我正在使用Windows并尝试运行一个现有的功能包,该功能包最初是在MacOS上构建的,这允许他们通过使用带空格的"\"来解决问题。我正在使用Ruby2.2.3和Cucumber。功能名称包含空格,我无法更改它。我尝试使用""和''来绕过空白,但每次都有同样的问题。这是问题的一个例子。如果我运行:cucumberfeatures/'Namecontainingwhitespaces.feature'它工作正常。但是当我运行时:cucumber-pmy_profile和cucumber.yml包含:my_profile:features/'Namecontainingwhitespace
单元测试的好方法是测试脚本在执行之间保持正确数据的能力——在使用Ctrl-C终止脚本然后重新运行之后?是否有针对执行类似操作的现有模块或脚本的任何测试可以针对最佳实践进行审查? 最佳答案 像http://avdi.org/devblog/2010/07/19/greenletters-painless-automation-and-testing-for-command-line-applications/一样使用库或者期望、运行、终止并重新运行您的程序,并检查它是否运行正确。好的做法是将程序设计为独立的模块,每个模块都经过良好测试
一文解决关于VLAN所有的疑惑VLAN基本概念为什么需要VLAN?怎么在交换机上划分VLAN,VLAN的工作原理有了子网,已经隔离了广播,还需要VLAN干啥?只进行子网划分,不进行VLAN划分VLAN划分与子网划分附加VLAN信息的方法VLAN划分交换机的端口类型(Access和Trunk)一、访问链接二、汇聚链接汇聚链接VLAN间通信为什么要进行VLAN间通信?路由器实现VLAN间通信路由器和交换机的连接方式通信细节三层交换机实现VLAN间通信加速VLAN间通信三层交换机与路由器三层交换机路由器路由器和交换机配合构建LAN的实例使用VLAN设计局域网的特点VLAN增加网络的灵活性不使用VLA
平时开发中我们经常会遇到这样的需求,在一个不限高度的盒子中会有很多内容,如果全部显示用户体验会非常不好,所以可以先折叠起来,当内容达到一定高度时,显示展开更多按钮,点击即可显示全部内容,先来看看效果图: 这样做用户体验瞬间得到提升,接下来看看具体细节。0">主要操作在内容这里{{item.username}},……展开更多样式大家可依据自己项目需求进行设计,这里就不贴了,主要说几个关键的。1、在data中定义三个属性isShowMore:false, //控制展开更多的显示与隐藏textHeight:null, //框中内容的高度status:false, //内容状态是否打开2.计算内容是否