Motivation¶
Following are some problems outlined with ROS and rospy
that motivates the
use of rosonic
. However, no rosonic
code will be found here.
ROS Nodes¶
When creating a node in ROS, it will typically look something like any of the examples below.
#! /usr/bin/env python
import rospy
## Publisher pattern ##
def main():
# initialization
pub = rospy.Publisher(...)
rate = rospy.Rate(...)
# main loop
while not rospy.is_shutdown():
pub.publish(...)
rate.sleep()
if __name__ == '__main__':
rospy.init_node('my_node')
try:
main()
except rospy.ROSInterruptException:
pass
## Subscriber pattern ##
def callback(msg):
...
def main():
# initialization
Subscriber(..., callback)
# main loop
rospy.spin()
if __name__ == '__main__':
rospy.init_node('my_node')
main()
## Pub-Sub pattern ##
def callback(msg, args):
(pub1, pub2) = args
pub1.publish(...)
pub2.publish(...)
def main():
# initialization
pub1 = Publisher(...)
pub2 = Publisher(...)
Subscriber(..., callback, (pub1, pub2))
# main loop
rospy.spin()
if __name__ == '__main__':
rospy.init_node('my_node')
main()
Neither of these are very complicated. However, this is mainly since they follow a clear design pattern. When developing, especially in research- oriented projects,following design patterns are often second priority and complexity tend to get out of hand. This is especially true when you start using other ROS concepts more and more.
For new users there is also the overhead of learning what a ROS node is, how it
behaves and how they relate to other ROS concepts. It may be easy to explain
that ROS starts a separate process that runs your python script as a standalone
program, the script declares itself to be ROS node with rospy.init_node
,
opens communication channels for any of your topics, and then it executes your
logic. However, that is probably unclear for any developers/researcher that
gets thrown into ROS for the first time.
As a minor example, how should you design a subscribe-publish node? To clarify,
you node should subscribe to some topic, augment the incoming data then, on a
new topic, publish it. Should you initialize in main
? How does callback
then have your pub
? Maybe you declare pub
globally, but that’s a Python
anti-pattern! Of course you can solve this neatly and in many ways, but there
is nothing in rospy
that motivates you to follow best-practice and/or a good
design patter. Below is one solution where pub
is sent as an argument to
callback
but you can imagine how that can get out of hand with more publisher
and subscribers. The initialization is moved to a dedicated function and, like
previously, the node ends on rospy.spin()
.
#! /usr/bin/env python
import rospy
def callback(msg, pub):
pub.publish(...)
def init():
pub = rospy.Publisher(...)
rospy.Subscriber(..., callback, pub)
if __name__ == '__main__':
rospy.init_node('my_node')
init()
rospy.spin()
ROS Parameters¶
Enough about design patterns! I don’t care about writing nice code, why should I care about
rosonic
?
Now, let me tell you! Surely the most important problems to solve are those
that cause mild inconvenience. rosonic
will help you once again before you
can say “ROS parameters”! Let’s look at a typical scenario…
#! /usr/bin/env python
import rospy
use_filter = True
def main():
global use_filter
# initialize
has_image2 = rospy.has_param('~image2')
assert has_image2, 'Parameter "image2" is missing!'
image1 = rospy.get_param('~image1', 'image1')
image2 = rospy.get_param('~image2')
use_filter = rospy.get_param('~use_filter', True)
pub = rospy.Publisher(image1, ...)
Subscriber(image2, ...)
# main loop
rospy.spin()
def callback(msg):
if use_filter:
...
...
if __name__ == '__main__':
main()
There are multiple problems with passing topic names through parameters, a very
typical use case. Firstly, the distinction between '~image1'
and 'image1'
is not very clear (a problem not solved by rosonic
). Many times '~image1'
may have the default value 'image1'
as well! Secondly, asserting that
required parameters, such as '~image2'
, exists is clunky. Thirdly, parameter
values must be passed to different functions/scopes or declared global (same
problem as pub
before).