IT技术之家

首页 > 人工智能

人工智能

【ROS】源码分析-roscore与Master启动_ShadowWalker_roscore

发布时间:2022-10-24 17:12:42 人工智能 0次 标签:自动驾驶 人工智能 机器学习 ROS
本文从源码层面分析ros Master是如何启动的,以及Master(服务注册和发布、参数服务)到底是什么高大尚的东东。...

说明

本文从源码层面分析ros Master是如何启动的,以及Master(服务注册和发布、参数服务)到底是什么高大尚的东东。

Node启动过程分析以小海龟为例,操作步骤如下

S1: 启动 roscoreS2: 启动 turtlesim_nodeS3: 启动 turtlesim_teleop_key

这几个操作过程中master、publisher、subscriber到底发生了什么?

源码目录

ros_comm/tools : ROS工具roslaunchrosmaster等ros_tutorials/turtlsim:小海龟的PubNode和SubNode。

源码分析

S1: 启动roscore (MasterNode)

此命令对应的可执行文件是/opt/ros/noetic/bin/roscore, 这是一个python脚本,主要做了2个事情:

解析roscore入参调用roslaunch.main(['roscore', '--core'] + sys.argv[1:])
import roslaunch
roslaunch.main(['roscore', '--core'] + sys.argv[1:])

进入启动入口 roslanch.main() 方法: 创建ROSLaunchParent并start

(源码文件:ros_comm/tools/roslaunch/src/roslaunch/_init_.py)
p = roslaunch_parent.ROSLaunchParent(uuid, args, roslaunch_strs=roslaunch_strs,
	is_core=options.core, port=options.port, local_only=options.local_only,
	verbose=options.verbose, force_screen=options.force_screen,
	force_log=options.force_log,
	num_workers=options.num_workers, timeout=options.timeout,
	master_logger_level=options.master_logger_level,
	show_summary=not options.no_summary,
	force_required=options.force_required,
	sigint_timeout=options.sigint_timeout,
	sigterm_timeout=options.sigterm_timeout)
p.start()
p.spin()

进入ROSLaunchParentstart方法实现: 启动launchServer(pm是ProcessMonitor进程管理组件)和ROSLaunchRunner

(源码文件:ros_comm/tools/roslaunch/src/roslaunch/parent.py)
self.server = roslaunch.server.ROSLaunchParentNode(self.config, self.pm)  
self.server.start()
self.runner = roslaunch.launch.ROSLaunchRunner(self.run_id, self.config, server_uri=self.server.uri, pmon=self.pm, is_core=self.is_core,
   remote_runner=self.remote_runner, is_rostest=self.is_rostest, num_workers=self.num_workers, timeout=self.timeout, master_logger_level=self.master_logger_level,
   sigint_timeout=self.sigint_timeout, sigterm_timeout=self.sigterm_timeout)
self.runner.launch()

进入ROSLaunchRunnerlaunch方法实现: 这里才真正开始创建Master进程

(源码文件:ros_comm/tools/roslaunch/src/roslaunch/launch.py)
def launch(self):
	launched = self._launch_master()
	if launched:
		self._launch_core_nodes()
	self._launch_nodes()


def _launch_master(self):
	p = create_master_process(
		    self.run_id, m.type, get_ros_root(), m.get_port(), self.num_workers,
		    self.timeout, master_logger_level=self.master_logger_level,
		    sigint_timeout=self.sigint_timeout, sigterm_timeout=self.sigterm_timeout)
	self.pm.register_core_proc(p) # 这里会提交进程给pm,然后进行启动
	success = p.start() # 这里实际是`LocalProcess.start()`

看一下create_master_process是如何创建master进程的: 创建了一个LocalProcess实例,然后启动LocalProcess.start()

(源码文件:ros_comm/tools/roslaunch/src/roslaunch/nodeprocessor.py)
def create_master_process():
   if type_ in [Master.ROSMASTER, Master.ZENMASTER]:        
        package = 'rosmaster'        
        args = [master, '--core', '-p', str(port), '-w', str(num_workers)]
        if timeout is not None:
            args += ['-t', str(timeout)]
        if master_logger_level:
            args += ['--master-logger-level', str(master_logger_level)]
    else:
        raise RLException("unknown master typ_: %s"%type_)

    _logger.info("process[master]: launching with args [%s]"%args)
    log_output = False
    return LocalProcess(run_id, package, 'master', args, os.environ, log_output, None, required=True,
                        sigint_timeout=sigint_timeout, sigterm_timeout=sigterm_timeout)

上面代码中的一些关键变量值:

package=“rosmaster”args[0](master)=“rosmaster” # 即 Master.ROSMASTERtype_=Master.ROSMASTER

然后看一下真是的进程是如何启动的class LocalProcess(Process).start

(源码文件:ros_comm/tools/roslaunch/src/roslaunch/nodeprocessor.py)
class LocalProcess(Process):
    super(LocalProcess, self).start()
    self.popen = subprocess.Popen(self.args, cwd=cwd, stdout=logfileout, stderr=logfileerr, env=full_env, close_fds=close_file_descriptor, preexec_fn=preexec_function)

subprocess.Popen 真实的启动了一个子进程,其中args变量从上面create_master_process得知进程可执行的命令(文件)是rosmaster

self.args=["rosmaster",  "--core", "-p", str(port), '-w', str(num_workers)]

所以这个子进程的命令文件是rosmaster,即源码中的ros_comm/tools/rosmaster/script/rosmaster,这是一个python脚本文件, 非常简洁的调用 rosmaster.rosmaster_main()

import rosmaster
rosmaster.rosmaster_main()
(源码文件:ros_comm/tools/rosmaster/src/rosmaster/main.py)
def rosmaster_main(argv=sys.argv, stdout=sys.stdout, env=os.environ):
	master = rosmaster.master.Master(port, options.num_workers)
    master.start()


(源码文件:ros_comm/tools/rosmaster/src/rosmaster/master.py)
class Master(object):
	def start(self):
		handler = rosmaster.master_api.ROSMasterHandler(self.num_workers)
		master_node = rosgraph.xmlrpc.XmlRpcNode(self.port, handler)
		master_node.start()

(源码文件:ros_comm/tools/rosgraph/src/rosgraph/xmlrpc.py)
class XmlRpcNode(object):
    def start(self):
    	#这里启动了一个线程
        _thread.start_new_thread(self.run, ())

	def run(self):
		self.server = ThreadingXMLRPCServer((bind_address, port), log_requests)
		self.server.serve_forever()

class ThreadingXMLRPCServer(socketserver.ThreadingMixIn, SimpleXMLRPCServer):

所以master进程使用python的SimpleXMLRPCServer启动了http服务器,对应的服务处理是rosmaster.master_api.ROSMasterHandler,这里就是大家开发中经常调用的几个方法(这几个方法如何对应到常用的advertisesubscribe等方法在后文中会详细讲解)

class ROSMasterHandler(object):
	def registerService(self, caller_id, service, service_api, caller_api):
	def registerSubscriber(self, caller_id, topic, topic_type, caller_api):
	def searchParam(self, caller_id, key):
	def getParam(self, caller_id, key):

补充一点:master服务是如何被发现的?
答:是master约定了一个默认的端口

(源码文件:ros_comm/tools/rosmaster/src/rosmaster/master.py)
DEFAULT_MASTER_PORT=11311 #default port for master's to bind to

到此master已启动,然后大家在控制台就看到了数学的页面

S2: 启动 turtlesim_node/S3: 启动 turtlesim_teleop_key

因为这2个Node启动过程是一样的,所以放到一起分析,先看turtlesim_teleop_key

源码:ros_tutorials/turlesim/tutorials/teleop_turtle_key.cpp,这里定义了一个Node,即ros::Publisher twist_pub_;然后发布Topic声明turtle1/cmd_vel,数据类型是geometry_msgs::Twist,队列大小是1

然后在keyloop方法中不断读取键盘方向键的输入,并转换为线速度或角速度

LEFT: 角速度=1.0 (弧度)RIGHT: 角速度=-1.0 (弧度)UP: 线速度=1.0DOWN: 线速度=-1.0

最后将线速度(x)和角速度(z)信息发布(publish)给订阅者(即turlesim_node)。

turtlesim_node 订阅topic,并更新角速度和线速度

为什么subscribe的topic并没有写turtle1/cmd_vel? 这与ROS的命名空间、Node命名空间、NodeName相关,具体参考这里,然后查看代码turtle_frame.cpp中的方法spawnTurtle应该就能命名了。

Master启动汇总

roscore脚本调用launch主方法,launch是主进程,然后启动子进程master,master启动HTTP服务。

launch:是使用python编写实现的一个工具,是roscore启动的主进程;master: 是使用python编写实现的一个HTTP服务,属于launch的一个子进程; 参数服务:使用字典类型的内存对象来保存;Topic发布订阅信息:使用字典类型的内存对象来保存;服务通讯:使用python的SimpleXMLRPCServer启动了http服务器(默认端口号11311 ),以接受PubNode&SubNode的服务注册和参数服务处理,具体的master所有提供的功能实现在rosmaster.master_api.ROSMasterHandler

Master中的参数服务、Topic订阅和注册信息都是使用最基本的字典内存对象维护
self.parameters = {}
.## { key: [(caller_id, caller_api)] }
self.map = {}
self.service_api_map = None