10

I am currently trying to make a ROS node in Python which has both a subscriber and a publisher.

I've seen examples where a message is published within the callback, but I want it to "constantly" publish messages, and perform callbacks when it is the case.

Here is how I do it now:

#!/usr/bin/env python

import rospy
from std_msgs.msg import Empty
from std_msgs.msg import String
import numpy as np

pub = rospy.Publisher('/status', String, queue_size=1000)

def callback(data):
    print "Message received"

def listener():

    rospy.init_node('control', anonymous=True)

    rospy.Subscriber('control_c', Empty, callback)
    rospy.spin()

if __name__ == '__main__':
    print "Running"
    listener()

So where should I publish?

luator
  • 4,769
  • 3
  • 30
  • 51
Carlton Banks
  • 395
  • 1
  • 6
  • 25
  • 1
    I shortened the text of your question to only the really relevant parts (publishing and subscribing in one node is very common, no need to justify why you want to do this). Feel free to revert my edit, if you don't agree. – luator Nov 20 '16 at 15:44

2 Answers2

6

Well, I think there's a lot of solutions here, you could even make use of a python process, but what I'm proposing is a ROS approach using a ros Timer.

I am not really that efficient in python but this code may gave you a heads up.

#!/usr/bin/env python

import rospy
from std_msgs.msg import Empty
from std_msgs.msg import String
import numpy as np

last_data = ""
started = False
pub = rospy.Publisher('/status', String, queue_size=1000) 

def callback(data):
    print "New message received"
    global started, last_data
    last_data = data
    if (not started):
        started = True

def timer_callback(event):
    global started, pub, last_data
    if (started):
        pub.publish(last_data)
        print "Last message published"


def listener():

    rospy.init_node('control', anonymous=True)

    rospy.Subscriber('control_c', String, callback)
    timer = rospy.Timer(rospy.Duration(0.5), timer_callback)

    rospy.spin()    
    timer.shutdown()

if __name__ == '__main__':
    print "Running"
    listener()

Here, your callback will update the message and your timer will fire up every 0.5sec and publishes the last data received.

you can test this code by publishing data on "/contriol_c" every 3 seconds and configuring you timer to 0.5 sec. start an echo on /status

$ rostopic echo /status

and you'll got your message published on a 2 Hz rate.

Hope that helps !

Vtik
  • 3,073
  • 2
  • 23
  • 38
  • I'm very new to ROS and am also trying to build a ROS node as both a subscriber and publisher (regardless of the topic's data type). Is it recognized as good practice to achieve this by having global `pub` and `sub` variables? Or would it be better to pass `pub` as an argument to the `callback`? – Matteo Jun 25 '18 at 21:26
4

Simply replace rospy.spin() with the following loop:

while not rospy.is_shutdown():
    # do whatever you want here
    pub.publish(foo)
    rospy.sleep(1)  # sleep for one second

Of course you can adjust the sleep duration to whatever value you want (or even remove it entirely).

According to this reference subscribers in rospy are running in a separate thread, so you don't need to call spin actively.

Note that in roscpp (i.e. when using C++) this is handled differently. There you have to call ros::spinOnce() in the while loop.

luator
  • 4,769
  • 3
  • 30
  • 51
  • I'm very new to ROS and am also trying to build a ROS node as both a subscriber and publisher. Is it recognized as good practice to achieve this by having global `pub` and `sub` variables? Or would it be better to pass `pub` as an argument to the `callback`? – Matteo Jun 25 '18 at 21:25
  • 1
    I would in general avoid global variables. Instead I usually implement the node as a class with `pub` as member and `callback` as a class method. This way you can access the publisher inside the callback via `self.pub`. – luator Jun 26 '18 at 06:55