Get started¶
Requirements¶
rosonic has been developed for ROS Melodic and above, i.e Python 2.7,
Python 3.6 and above. However, at writing moment only Python 3.6 has been
tested. As soon as I’m transitioning my other projects to Noetic then I will
drop any support for Python 2.7.
Installation¶
This package is available on PyPI and can be installed as a normal pip package.
pip install rosonic
Usage¶
There will be two nodes for counting numbers in this example. The first simply
publishes a uint32 that increases each iteration until it wraps around
and starts over. The other node subscribes to the uint32 and publishes a
string with binary, and possibly hexadecimal, representation. Let’s start
by breaking down the code for the first node.
Example 1¶
#! /usr/bin/env python
from rosonic import Node, Parameter
from rospy import Publisher
from std_msgs.msg import UInt32
class uint_counter(Node):
START = Parameter('~start', 0)
rate = 4
def __init__(self):
self.value = self.START
self.pub_counter = Publisher('counter', UInt32, queue_size=1)
def spin(self):
self.pub_counter.publish(self.value)
self.value += 1
self.value %= 32
if __name__ == '__main__':
uint_counter(anonymous=True)
Example 1 – Explained¶
We import Node and Parameter from rosonic. You should still import other
rospy tools as you need them. There are neither a special Publisher or
Subscriber in rosonic yet so we need to use rospy‘s for now.
from rosonic import Node, Parameter
from rospy import Publisher
from std_msgs.msg import UInt32
We setup a class to declare the node. The name of the class will be the default
name of the node (just as when you call rospy.init_node('<name>'). The
special field rate will be turned into a rospy.Rate used to control how
often spin will be called.
class uint_counter(Node):
START = Parameter('~start', 0)
rate = 4
One of the benefits of viewing nodes as classes is the clear separation of
initialization and the rest of its lifetime. During initialization we want
to create resources like publishers, subscribers, services, action-servers
and more. It is here we use parameters and setup the runtime behvaiour. In
rosonic we naturally utilize the __init__ method with no arguments but
self.
class uint_counter(Node):
...
def __init__(self):
self.value = self.START
self.pub_counter = Publisher('counter', UInt32, queue_size=1)
The innovation of rosonic nodes is very simple, but constitutes of three
different methods. __init__ that was previously mentioned, main and spin.
In most cases you will not want to change main whose default behaviour
will loop over a call spin. spin, on the other hand, is meant to be
overloaded. In this example we publish and update the uint32 value.
class uint_counter(Node):
...
def spin(self):
self.pub_counter.publish(self.value)
self.value += 1
self.value %= 32
Finally we are ready to start our node! As if it were a normal function we can
call uint_counter (with key-word arguments passed to rospy.init_node) and
run the code. This code snippet is required for every rosonic-style node.
if __name__ == '__main__':
uint_counter(anonymous=True)
Example 2¶
#! /usr/bin/env python
from rosonic import Node, Parameter
from rospy import Publisher, Subscriber
from std_msgs.msg import UInt32, String
class str_counter(Node):
HEX = Parameter('~hex', False)
def __init__(self):
self.pub_bin_counter = Publisher(
'bin',
String,
queue_size=1,
)
self.pub_hex_counter = Publisher(
'hex',
String,
queue_size=1,
)
self.sub_uint_counter = Subscriber(
'counter',
UInt32,
self.uint_counter_cb,
)
def uint_counter_cb(self, msg):
data = msg.data
hex_msg = String('hex=' + hex(data))
bin_msg = String('bin=' + bin(data))
if self.HEX:
self.pub_hex_counter.publish(hex_msg)
self.pub_bin_counter.publish(bin_msg)
if __name__ == '__main__':
str_counter(anonymous=True)
Example 2 – Explained¶
Just like in Example 1 we start by declaring a Node class, any parameters
and its initialization. Notice that we have a subscriber this time!
class str_counter(Node):
HEX = Parameter('~hex', False)
def __init__(self):
self.pub_bin_counter = Publisher(
'bin',
String,
queue_size=1,
)
self.pub_hex_counter = Publisher(
'hex',
String,
queue_size=1,
)
self.sub_uint_counter = Subscriber(
'counter',
UInt32,
self.uint_counter_cb,
)
There is no problem using a method as the callback. Having self in the
callback allow us to use resources belonging to the class. It is therefore no
need to have any global variables and we can rest assured that data,
hex_str and bin_str are all contained in this scope.
class str_counter(Node):
...
def uint_counter_cb(self, msg):
data = msg.data
hex_msg = String('hex=' + hex(data))
bin_msg = String('bin=' + bin(data))
if self.HEX:
self.pub_hex_counter.publish(hex_msg)
self.pub_bin_counter.publish(bin_msg)
Then we start the node, just as we did last time! This time we did not overload
spin which is also fine. Let’s look at Example 3 to investigate this.
if __name__ == '__main__':
str_counter(anonymous=True)
Example 3¶
#! /usr/bin/env python
from rosonic import Node
class dummy(Node):
pass
if __name__ == '__main__':
dummy(anonymous=True)
Example 3 – Explained¶
The purpose of str_counter was to react to uint_counter and only use its
one callback. Normally we would call rospy.spin at the end in such nodes.
However, this happens automatically in rosonic. Remember __init__, main
and spin? Well, by default spin calls rospy.spin! Lets look at the
default behaviour of our dummy node.
When dummy is called it will (as with any Python class) run __init__ for
initialization. This is up to you to decide if it’s necessary or not.
if __name__ == '__main__':
dummy(anonymous=True)
The next step, called internally by rosonic, is main. It will look
something like a while-loop that keeps calling spin. The loop is conditioned
by keep_alive (that you can also overload if necessary) that by default
returns not rospy.is_shutdown().
class Node(...):
...
def main(self):
while self.keep_alive():
self.spin()
self.rate.sleep()
self.shutdown()
Then there is spin that calls rospy.spin. This essentially means that
after __init__ is done, if neither main or spin is overloaded, then the
node will fall into rospy.spin until the node is shutdown.
class Node(...):
...
def spin(self):
rospy.spin()
What’s next?¶
If you want to learn more about rosonic‘s features I recommend you to read
the source code. You are also free to contribute with ideas through issue
tickets or PR’s.