ROS1云课→09功能包小定制(CLI命令行接口)
使用console
运行ROS1各类应用后,消息会分类显示:
通过这类信息可以查看机器人系统过程中的各种警报信息等。
日志信息
通过信息记录显示程序的运行状态是好的习惯,但需要确定这样做不会影响软件的运行效率和输出的清晰度。在ROS1中有满足以上要求并且内置于log4cxx(众所周知的log4j记录库的一个端口)之上的API。简单地说,有不同层级的调试信息输出,每条信息都有自己的名称,并根据相应条件输出消息。如果它们被当前冗长级别掩盖(甚至在编译时),它们对性能没有影响。它们与ROS1其他工具完全集成来可视化或过滤来自所有运行节点的消息。
输出日志信息
ROS1自带了大量的能够输出日志信息的函数和宏。它提供了如信息(或日志)级别、条件触发消息和STL的流接口等诸多方式。从简单的开始,用C 代码输出一个消息信息:
代码语言:javascript复制ROS_INFO("My INFO message.");
为了获取日志记录的函数和宏,这个头文件足够了:
代码语言:javascript复制#include <ros/ros.h>
这包括了以下头文件:
代码语言:javascript复制#include <ros/console.h>
前面的消息处理程序运行的结果如下所示:
INFO: My INFO message.
所有输出的信息都附带其级别和当前时间戳(因为这个原因输出可能有所不同),这两个值放在实际信息之前的方括号中。时间戳以公历时间计时,代表着自1970年1月1日以来的秒和纳秒计数。于是在新一行输出了信息。此函数允许以和C语言中的printf函数相同的方式增加参数。例如,可以按照下面代码输出变量val对应的浮点数值:
代码语言:javascript复制floatval = 1.23;
ROS_INFO("My INFO message with argument: %f", val);
此外,C STL流被*_STREAM函数支持。因此,前面的指令相当于使用流:
代码语言:javascript复制ROS_INFO_STREAM("My INFO message with argument: " <<val);
请注意,没有指定任何流,因为API负责这些,通过重定向到cout/cerr、一个文件或两者。
设置调试信息级别
ROS有五个日志记录标准级别,按照顺序排列分别是:
- DEBUG调试
- INFO信息
- WARN警告
- ERROR错误
- FATAL致命
这些名称是输出信息的函数的一部分,它们遵循以下语法:
ROS_<LEVEL>_<OTHER>
每一种信息都会以特定的颜色在屏幕上输出。这些颜色分别是:
DEBUG in green 绿色
INFO in white 白色
WARN in yellow 黄色
ERROR in red 红色
FATAL in purple 紫色
乱码怎么办?怎么办?
每个消息级别用于不同的目的。在这里,建议:
- DEBUG:只在调试时用,此信息不应出现在部署的应用中,仅用于测试目的。
- INFO:应有的标准信息,说明重要步骤或节点所正在执行的操作。
- WARN:提醒一些错误、缺失或者不正常,但程序仍能运行。
- ERROR:提示错误,尽管节点仍然可以在这里恢复,但他们对节点的行为设置了一定的期望。
- FATAL:这些消息通常表示阻止节点继续运行的错误。
为特定节点配置调试信息级别
默认情况下,系统会显示INFO及更高级别的调试信息,并使用ROS默认级别来过滤特定节点输出的信息。要实现这一功能有很多方法。其中有些是在编译时设定,而其他的可以在执行前使用配置文件进行更改。另外,也可以动态地改变级别。下面将介绍使用rqt_console和rqt_logger_level来实现这一功能。
然而,在一些时候,需要删除低于设定级别的日志。这时,希望看到那些消息后,将它们删除而不是禁用。为此需要将ROSCONSOLE_MIN_SEVERITY设置为期望的最低严重级别或者避免任何消息(甚至是FATAL)。宏如下:
ROSCONSOLE_SEVERITY_DEBUG
ROSCONSOLE_SEVERITY_INFO
ROSCONSOLE_SEVERITY_WARN
ROSCONSOLE_SEVERITY_ERROR
ROSCONSOLE_SEVERITY_FATAL
ROSCONSOLE_SEVERITY_NONE
ROSCONSOLE_MIN_SEVERITY宏在<ros/console.h>中默认定义为DEBUG级别。于是可将它作为一个编译参数(使用-D)传递或把它放在头文件前。例如,若想仅显示ERROR或更高级别的调试信息,在源代码中加入下面代码:
代码语言:javascript复制#define ROSCONSOLE_MIN_SEVERITY ROSCONSOLE_SEVERITY_ERROR
或者,在CMakeLists.txt中使用下面代码设置包中所有节点的宏:
add_definitions(-DROSCONSOLE_MIN_SEVERITY =ROSCONSOLE_SEVERITY_ERROR)
除此之外,还有一个更灵活的方法就是在配置文件中设置最低日志级别。用文件创建一个名为的config文件夹和名为chapter3_tutorials.config的文件,文件内容如下(从它设置为DEBUG级别开始编辑给定文件):
log4j.logger.ros.demo_tutorials=ERROR
然后,设置ROSCONSOLE_CONFIG_FILE环境变量指向我们的文件。可以使用一个启动(launch)文件来替代配置环境变量,但这样做会直接运行节点。因此,可以通过env(环境变量)字段扩展launch文件,如下所示:
<launch>
<!-- Logger config -->
<env name="ROSCONSOLE_CONFIG_FILE"
value="$(find demo_tutorials)/config/demo_tutorials.config"/>
<!-- Example 1 -->
<node pkg="demo_tutorials" type="example1" name="example1" output="screen"/>
</launch>
如上所述,环境变量会找到之前显示的配置文件,其中包含每个已命名日志的日志级别说明。在这个例子中是功能包名称。
信息命名
默认情况,ROS1分配一些名字给节点记录器。目前讨论过的消息在节点名字后命名。对于复杂的节点,可以为一个给定的模块或功能的消息提供一个名字。ROS_<LEVEL>_STREAM_NAMED函数,如下面代码所示(以example2节点为例):
代码语言:javascript复制ROS_INFO_STREAM_NAMED(
"named_msg",
"My named INFO stream message; val = " <<val
);
通过命名的消息,可以使用配置文件为每个命名的消息设置不同的初始日志级别,之后可以单独修改它们。在命名规范上必须使用消息的名称作为功能包的子包。例如可以设定named_msg的信息,如下列代码所示:
log4j.logger.ros.demo_tutorials.named_msg=ERROR
按条件显示信息与过滤信息
按条件显示(conditional)信息是指仅当满足给定的条件时才输出的信息。需要使用ROS_<LEVEL>_STREAM_COND_NAMED函数来调用它们,请注意它们也可以是命名的信息。下面是以example2节点为例的代码:
代码语言:javascript复制ROS_INFO_STREAM_COND( val< 0.,
"My conditional INFO stream message; val (" <<val<< ") < 0"
);
过滤(Filtered)信息在本质上与按条件显示信息类似,但它允许指定一个用户自定义的过滤器。这个自定义过滤器继承自ros::console::FilterBase结构体。必须将过滤器作为指针传递给以ROS_<LEVEL>_STREAM_FILTER _NAMED为格式的宏的第一个参数。下例来自于example2节点:
代码语言:javascript复制structMyLowerFilter : public ros::console::FilterBase
{
MyLowerFilter(const double&val ) :
value( val ) {} inline virtual boolisEnabled()
{ return value < 0.; }
double value;
};
MyLowerFilterfilter_lower(val );
ROS_INFO_STREAM_FILTER(&filter_lower,
"My filter INFO stream message; val (" <<val<< ") < 0"
);
显示信息的方式——单次、可调、以及其他组合
控制信息的显示次数。通过使用ROS_<LEVEL>_STREAM_ONCE_NAMED可以让信息只输出一次。
代码语言:javascript复制for(int i = 0; i< 10; i ) {
ROS_INFO_STREAM_ONCE("My once INFO stream message; i = " <<i);
}
这段代码也是来自example2节点,只会显示信息1次。
然而,有时候在迭代中以一定频率显示信息更好。这就需要可调信息。它们和前面单次显示的消息格式是一样的,但是需要将函数名中的ONCE替换成THROTTLE,函数会将period作为第一个参数,也就是说,它将会间隔每个period秒后输出:
代码语言:javascript复制for(int i = 0; i< 10; i ) {
ROS_INFO_STREAM_THROTTLE(2,
"My throttle INFO stream message; i = " <<i);
ros::Duration( 1 ).sleep();
}
最后要说明的是,无论对于哪个级别的信息,命名信息、条件显示信息、单次/可调信息等都能够组合起来使用。
动态加载节点(nodelet)对于日志信息也提供了一定的支持。因为它们有自己的命名空间,而且有各自唯一的名称,这样才能够让一个动态加载节点的提示信息与其他节点的提示信息区别开。简单地说,前面提到的所有宏对于动态加载节点都是可用的,只是宏的名称需要将ROS_*开头替换成NODELET_*。这些宏将只能够在动态加载节点内部编译。同时,它们会使用动态加载节点运行时的名称设置一个命名的日志记录器。这样你就能够区分同一个动态加载节点管理器下运行的两个相同类型动态加载节点的输出。动态加载节点的另外一个优势是它们能够帮助你将某个动态加载节点转换到调试级别,而不是把整个特定类型的动态加载节点都转换过去。
使用rqt_console和rqt_logger_level在运行时修改调试级别
ROS中提供了一系列工具去管理日志信息。在ROS Kinetic中,有两个独立的GUI:rqt_logger_level设置节点或者指定日志记录器的日志记录级别;rqt_console对日志信息进行可视化、过滤和分析。
使用代码示例example3测试这个功能。运行roscore和rqt_console来看日志信息:
$ rosrun rqt_console rqt_console
将看到如下窗口:
运行下面节点:
$ rosrun demo_tutorials example3
一旦example3节点开始运行,就会看到如下图所示的信息。注意roscore必须已经启动,也必须点击在rqt_console窗口上的记录(recording)按钮。
在默认情况下每个节点都有多个内部日志记录器,与ROS通信API相关,一般不要降低它们的严重级别。
代码语言:javascript复制int main( int argc, char **argv )
{
ros::init( argc, argv, "example1" );
#if OVERRIDE_NODE_VERBOSITY_LEVEL
// Se the logging level manually to DEBUG
ros::console::set_logger_level(ROSCONSOLE_DEFAULT_NAME, ros::console::levels::Debug);
#endif
ros::NodeHandle n;
const double val = 666.666;
char buf[] = "u84DDu6865u4E91u8BFEu80FDu5426u8BA9u5B66u4E60ROSu673Au5668u4EBAu7684u8FC7u7A0Bu66F4u52A0u591Au5FEBu597Du7701u5145u6EE1u6B22u4E50u5462";
printf("%srn", buf);
setlocale(LC_CTYPE, "zh_CN.utf8");
ROS_DEBUG( "Happy day! %s", buf );
ROS_DEBUG( "This is a DEBUG message with an argument: %f", val );
ROS_DEBUG_STREAM(
"This is DEBUG stream message with an argument: " << val
);
ros::spinOnce();
return EXIT_SUCCESS;
}