使用游戏手柄控制机器人
abstract Welcome to my blog, enter password to read.
Read more
日志系统和log4cxx

我们都知道ROS有默认的日志工具,用rosclean check检查日志所在路径和占用的大小,一般放在~/.ros/log,可以定义环境变量ROS_LOG_DIR改变日志存放的位置。rosclean purge可以清除日志,日志记录了所有的输出信息,有时我们只需要看部分信息,看自带的日志太费劲了,所以本文讲一下怎么利用ROS实现自定义的日志。

log4cpp与log4cxx

log4..是基于log4j的一系列的c++移植版本,使用了log4j的模式结构,目前主要有:log4cxx,log4cplus,log4cpp.

一开始看到网上说ROS提供了log4cpp库的安装,而且还有对应的官方帮助页面,于是执行sudo apt-get install ros-kinetic-log4cpp顺利安装了,在CMakeList里的find package添加log4cpp,结果发现编译时报错:

执行catkin_make时,编译器会寻找find package中的package对应的-config.cmake文件,比如roscpp这个package,我们用roscd roscpp进入对应安装目录中,看到一个cmake目录,进入后发现几个文件,其中一个是roscppConfig.cmake。但是log4cxx对应的目录连cmake文件夹都没有,所以编译失败。后来看到网上一些人的讨论,说log4cpp涉及到Orocos环境,这个比较陌生,实在懒得研究了。

后来又看到log4cxx已经集成到了ROS里面,不过不是以package的形式,而是头文件的形式,在安装ROS时就安装到了/usr/local/log4cxx,这样就好办多了,不用担心编译问题了。

为什么使用log4cxx,因为它提供了强大的配置功能,好像ROS也能实现,但是我没有去研究,应该就是rosconsole这个东西,它在启动时会加载$ROS_ROOT/config/rosconsole.config作为配置文件。另外可以定义配置文件供log4cxx使用,定义环境变量ROSCONSOLE_CONFIG_FILE为配置文件的路径。

一般做成launch文件,这样使用方便,编辑灵活:

1
2
3
4
5
<launch>
<env name="ROSCONSOLE_CONFIG_FILE"
value="$(find log_test)/setting.conf"/>
<node pkg="log_test" type="log_test" name="log_test" output="screen"/>
</launch>

package和可执行文件的名称都是log_test,配置文件由环境变量指定路径。

一个典型的配置文件是这样的,看上去不复杂,但是和普通log4cxx配置不同,需要结合ROS的特性,查了好久才成功:

1
2
3
4
5
6
7
8
9
log4j.logger.ros.log_test=INFO,rosout_a    # log_test是package名
log4j.appender.rosout_a=org.apache.log4j.RollingFileAppender # 输出到文件,超过大小后自动创建下一个
log4j.appender.rosout_a.Threshold=INFO # 最低级别是INFO
log4j.appender.rosout_a.Append=true # 追加模式
log4j.appender.rosout_a.MaxFileSize=100KB # 每个日志文件的大小
log4j.appender.rosout_a.MaxBackupIndex=10 # 最多产生多少日志文件,默认2个
log4j.appender.rosout_a.File=/home/hlhp/output.txt # 输出文件,可结合环境变量${ROSOUT_LOG_PATH}
log4j.appender.rosout_a.layout=org.apache.log4j.PatternLayout # 常用PatternLayout
log4j.appender.rosout_a.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss SSS} %m%n # 输出格式

注意:

  • 第一行 log_test是package名称。
  • 如果没有设置文件最多产生个数,默认只生成2个日志文件。超过大小后,会从第一个开始覆盖。
  • 注意日志级别,否则不会记录

注意文件中就是log4j不是log4cxx,PatternLayout指可以灵活地指定布局模式,最后输出格式中的SSS指的是毫秒。log4j采用类似C语言中的printf函数的打印格式格式化日志信息:

1
2
3
4
5
6
7
%m   输出代码中指定的消息
%n 输出一个回车换行符
%p 输出优先级,即DEBUG,INFO,WARN,ERROR,FATAL
%r 输出自应用启动到输出该log信息耗费的毫秒数
%c 输出所属的类目,通常就是所在类的全名
%t 输出产生该日志事件的线程名
%l 输出日志事件的发生位置,包括类目名、发生的线程,以及在代码中的行数。举例:Testlog4.main(TestLog4.java: 10 )

日志打印宏以_ONCE结尾,表示只调用一次:ROS_DEBUG_STREAM_ONCE("This appears only once.");
日志打印宏以_THROTTLE结尾,表示按指定频率打印日志:ROS_DEBUG_STREAM_THROTTLE(0.1, "This appears every 0.1 seconds.");

日志消息有三个不同的目的地:控制台,话题/rosout,日志文件。相比于控制台输出,rosout话题输出的主要作用是它在一个流中包含了系统中所有节点的日志消息。查看话题/rosout中的消息:

1
rostopic echo /rosout

也可以使用图形界面查看:rqt_consolerqt_console订阅的是话题/rosout_agg,话题由节点rosout收集话题/rosout聚合之后发布。

我写的一个节点订阅了5个话题,这几个话题是单片机发布的,在回调函数里输出对应消息类型的变量信息,日志信息最终在output.txt中,注意输出信息是用逗号隔开,这样用EXCEL打开可以将各部分隔开,实现表格效果:


说白了,代码里没用到log4cxx的东西,当然也可以用,但是我这个例子没必要,关键用的是它的配置文件。

ROS_INFO中文显示问号

显然这是编码问题,在ROS_INFO之前加入下面代码中的一行:

1
2
3
// 设置区域为中文,或者直接取所有枚举的并集,不必仔细研究了
// setlocale(LC_CTYPE, "zh_CN.utf8");
setlocale(LC_ALL, "");

自定义ROS日志的格式

默认的日志输出是这样的:

1
2
[ INFO] [1556115082.590417546]: msg:91
[ INFO] [1556115083.591389486]: msg:12

感觉时间没用必要,希望能像log4cxx一样自定义,其实看源码rosconsole.cpp可知ROS日志就是基于log4cxx。我们可以修改环境变量ROSCONSOLE_FORMAT定制格式,格式包括以下选项:
1
2
3
4
5
6
7
8
9
severity
message
time
thread
logger
file
line
function
node

看一下rosconsole.cpp源码部分:

1
2
3
4
5
6
7
8
9
const char* g_format_string = "[${severity}] [${time}]: ${message}";
Formatter g_formatter;
format_string = getenv("ROSCONSOLE_FORMAT");
if (format_string)
{
g_format_string = format_string;
}

g_formatter.init(g_format_string);

从中可以发现源码里先给了个默认格式,我们也可以针对环境变量自己设置格式.

网上有人问能不能自定义日志信息的颜色,结果发现在函数Formatter::print里,各等级的日志信息的颜色已经写死了,比如levels::Fatal对应COLOR_RED,所以不能自定义了.

根据环境变量,我们可以这样修改:

1
export ROSCONSOLE_FORMAT='[${severity}][${node}] [${function}] line_${line}  ${message}'

只保留等级、节点名、函数、行号、信息,结果是这样的:
1
2
3
[ INFO][/Pub] [main] line_19  msg:13
[ INFO][/Pub] [main] line_19 msg:37
[ INFO][/Pub] [main] line_19 msg:73

界面日志工具

ROS提供两个带界面的日志工具:rqt_console和rqt_logger_level

命令如下:

1
2
rosrun rqt_console rqt_console
rosrun rqt_logger_level rqt_logger_level

使用很简单,就不详细解释了




参考:
rosconsole
log4j.properties配置详解
ROS 程序编写之日志
rosconsole.cpp


rosbridge实战(二) 从上位机发布各种类型的话题
abstract Welcome to my blog, enter password to read.
Read more
使用web_video_server在网页显示图像
abstract Welcome to my blog, enter password to read.
Read more
rosbridge实战(一)
abstract Welcome to my blog, enter password to read.
Read more
ROS中的坐标系

ROS中的坐标系

在ROS中坐标系总是3维的,而且遵循右手法则,Z轴用拇指表示,X轴用食指表示,Y轴用中指。X轴指向前方,Y轴指向左方,Z轴指向上方。研究坐标系绕某轴旋转时,也是用右手法则,右手握住坐标轴,大拇指 的方向朝着坐标轴朝向的正方向,四指环绕的方向定义沿着这个坐标轴旋转的正方向。


在Rviz中,默认X轴是红色、Y轴时绿色、Z轴是蓝色,也就是XYZ对应RGB。


机器人的朝哪走是向前,这是由陀螺仪和码盘决定的。

机器人中的坐标系

机器人自身坐标系一般用base_link表示,所在位置为原点,朝向的方向为X轴正方向,左方为Y轴正方向,头顶是Z轴正方向。

base_footprint坐标系,原点为base_link坐标系原点在地面的投影,只有Z值有变化

earth坐标系适用于多个机器人的环境,先不讨论了

如果想使用其他坐标系,必须使用tf包对其与map坐标系进行转换,比如下面三个坐标系都有直接或间接的转换:

如果没有经过转换,就会报错,例如:

For frame [tag_9]: No transform to fixed frame [/map]. TF error: [Could not find a connection between ‘map’ and ‘tag_9’ because they are not part of the same tree.Tf has two or more unconnected trees.]
tag_9没有向固定坐标系map的转化,不在tf树上。


基础知识和常用命令

ROS最主要的构成是节点(node),每一个节点可以看做为一个单独的处理单元.它可以接受外部的消息(这个步骤叫订阅,subscribe),也可以向外部发出自己的消息(这个步骤叫发布,publish);

节点之间通讯的管理需要一个管理员(ros中被称为roscore节点).当节点分布在不同的计算机上时,需要指明master.ROS底层的通信是通过HTTP完成的,因此ROS内核本质上是一个HTTP服务器,它的地址一般是http://localhost:11311/ ,即本机的11311端口。当需要连接到另一台计算机上运行的ROS时,只要连上该机的11311端口即可。

Binary install 是指通过sudo apt-get install ros-kinetic-pkg的方式安装的。这样的包会直接被安装进opt/ros/kinetic/share文件夹里面

ROS的程序之所以能用`rosrun`启动,只取决于`CMakeLists`和`package.xml`,如果不打算使用ROS通信的东西,C++程序里可以不添加`ros::init`和`NodeHandle`

rosbash系列

rosbash是ROS提供的一系列便利的命令,在source /opt/ros/kinetic/setup.bash后可使用。一个setup.*sh会尽可能撤销先前其他所有setup.*sh的影响,然后它再自己施加影响。

source可在最后加上选项--extend,作用是 skips the undoing of changes from a previously sourced setup file

devel/setup.bash的作用是让我们能使用roscd等ROS独有的shell命令,还有识别当前工作空间的package名称

roscd: 直接跳到某个package的地址,这个是用的最多的。不过它的本质是针对环境变量ROS_LOCATIONS中规定的地址,它的格式是这样:

1
export ROS_LOCATIONS="pkgs=~/ros/pkg:openmv=/home/user/qt-ros-ws/src/openmv_cam"

pkgs和openmv是其中规定的两个地址,二者用冒号隔开,现在就可以使用roscd openmv

rosls: 列出某个package中的所有文件

rospd: 这个命令特殊,可以记录和跳到所有package的地址,可以看做roscd的升级版。rospd package会跳到某个package,rospd会列出去过的所有package地址并且编号,以后可以直接rospd num到指定地址

rosnode

  • 查看当前运行的节点用rosnode list命令

  • 优雅关闭每个节点,使用rosnode kill命令。使用ctrl+c或者直接关闭终端也可以关闭节点,但这种方法不免过于简单粗暴,而且并未完全的关闭节点,因为节点管理器那里仍然有记录。

  • rosnode kill -a # 关闭除rosout外的所有节点

  • ping节点: rosnode ping node,检查一个节点的连通性

  • 显示ROS节点使用的IP: rosnode machine

rospack系列命令

  • 列出所有有效的package:rospack list
  • 输出指定package的路径: rospack find turtlesim,输出/opt/ros/kinetic/share/turtlesim
  • 查看package的依赖项:rospack depends turtlesim

catkin_make系列命令

使用catkin_make系列命令时,会把工作空间所有的包的CMakeLists先检查一遍,有时会很麻烦。使用catkin_make_isolated命令可以针对单独的包进行编译,但速度会慢很多。

rqt系列命令

rqt命令会运行一个对话框:

rqt_graph包用于显示各节点和主题之间的关系,命令:rosrun rqt_graph rqt_graph

rosdep

rosdep是一个安装系统依赖项的命令,用于package的依赖项安装,比如对PACKAGE:

1
rosdep install PACKAGE

也可以安装工作空间中所有package的依赖项,先cd到工作空间的根目录,执行下面命令:

1
rosdep install --from-paths src --ignore-src -r -y

其他命令

用曲线查看主题的各个消息情况rosrun rqt_plot rqt_plot,这个工具是用Qt开发的


初步控制小车
abstract Welcome to my blog, enter password to read.
Read more
ROS串口通信(二)
abstract Welcome to my blog, enter password to read.
Read more
catkin_make及cmakelists分析
  • 只用catkin_make遍历所有包时,运行的是cmake进程。实际编译时,才运行cc1plus进程,一个核一个进程。

  • catkin_make编译时,对工作空间所有packages的编译顺序是按拓扑遍历的,不是按字母也不是按创建时间。修改任意一个CMakeLists.txt或者package.xml后,执行catkin_make会将工作空间的所有package的CMakeLists重新处理一遍。catkin_make遍历所有包的过程,占CPU并不大,占CPU还是在编译阶段

  • catkin_make可能报错内存不足,尤其是move_base,因为需要占用很多资源

  • catkin_make时占CPU太大,导致几乎死机。可以从另一台电脑SSH,执行 pkill cc1plus 停止编译

编译整个工作空间是这样的:
catkin_make编译.png
先检查了slam_gmapping_nodelet的依赖项,然后是编译fake_localization和depth_image_proc,然后又开始检查map_to_image_node,并不是按包的顺序编译slam_gmapping_nodelet,也就是并不是一个一个包按顺序编译,而是万箭齐发式的。因此,要看某一个包的问题时,不要编译整个工作空间,否则很难找。

catkin_make -D是指定编译的配置, 在D后面增加命令 ,比如指定编译类型Release和核数编译: catkin_make -DCMAKE_BUILD_TYPE=Release -j8

不同package的类型

编译时,catkin_make会显示各种包的情况
plain cmake.png
plain cmake是纯cmake的包,不能用catkin_make

metapackage都是ROS官方的,自己不用写这种类型,它不安装任何文件(除了package.xml说明的), 也不包含任何代码文件、test、launch文件,你会发现navigation这个包只有四个文件:CHANGELOG.rst, CMakeLists.txt, package.xml, README.rst

它的package.xml会含有下面内容:

1
2
3
<export>
<metapackage />
</export>

CMakeLists.txt中还有一句catkin_metapackage()

这里涉及到 16和18版本的不同 ,对于16 kinetic,会有如下如下报错
使用kinetic编译会报错,但是melodic不会.png
18 melodic不会有这个报错,此外18版对C++11类型的支持更好,有时无需手动修改cmake

不同版本的package.xml

目前常用的package.xml开头是这样的:

1
2
3
4
<?xml version="1.0"?>
<package format="2">
<name>name</name>
<version>0.0.0</version>

旧版本的是这样的:

1
2
3
<package>
<name>base_local_planner</name>
<version>1.14.4</version>

旧版本的不能使用exec_depend,而是用run_depend,否则会报错

单独编译某个package

以前一直认为单独编译某个package的命令是catkin_make --pkg package1,结果这样仍然会将工作空间中所有package的CMakeLists全检查一遍,花费时间相当长,实际的命令是这个:

1
catkin_make -DCATKIN_WHITELIST_PACKAGES="package1;package2"

可以编译一个或多个package,但是执行这个命令之后再catkin_make回发现它仍然只编译上次的package。恢复成编译所有package去掉引号里面的内容就行:
1
2
3
catkin_make -DCATKIN_WHITELIST_PACKAGES=""
# 对ninja编译的包
catkin_make -DCATKIN_WHITELIST_PACKAGES="package" --use-ninja

build文件夹存放cmakemake相关的文件,devel文件夹存放编译生成的文件和目标,包括setup.sh

clean命令

make clean类似,也有个catkin_make clean命令,它会删除所有编译的可执行文件或库文件,但是不会删除删除头文件,例如msg和srv生成的头文件

INSTALL

现在执行INSTALL命令不再是sudo make install了,而是catkin_make install,它相当于:

1
2
3
4
5
cd ~/catkin_ws/build
# If cmake hasn't already been called
cmake ../src -DCMAKE_INSTALL_PREFIX=../install -DCATKIN_DEVEL_PREFIX=../devel
make
make install

这样执行完以后,工作空间会出现一个install文件夹,里面存放着编译生成的库文件,问题是它会把所有package的install都进行处理,如果想改变这个目录的位置,执行catkin_make -DCMAKE_INSTALL_PREFIX=path install。另外在CMakeLists中指定安装目录用SET(CMAKE_INSTALL_PREFIX < install_path >)

编译出的.so和可执行文件可以直接放到另一台电脑使用,但是如果是跨平台,catkin_make install恐怕不可用,需要交叉编译。

结果会在share/status_panel/cmake中生成两个cmake文件

这两个文件是做依赖包时必需的,比如roscpp就有相应的文件

链接ROS库

如果想使用ROS的头文件,必须在CMakeLists里加入下面内容,也就是链接ROS的头文件和库:

1
2
3
4
5
6
7
8
include_directories(
# include
${catkin_INCLUDE_DIRS}
)
find_package(roscpp)
target_link_libraries(foo
${catkin_LIBRARIES}
)

别忘了target_link_libraries在add_excutable之后。用message函数可以看到include文件夹和libraries文件夹如下:

在CMakeLists中加入catkin_package(), 才能在执行catkin_make后,在devel/lib中生成可执行文件