ROS Resources: Documentation | Support | Discussion Forum | Index | Service Status | ros @ Robotics Stack Exchange
Ask Your Question
0

How to create a ROS node that can start and stop counting based on what message the other node its subscribed to sends

asked 2021-06-25 14:16:43 -0500

ROSNewbie gravatar image

updated 2021-06-26 12:45:36 -0500

Hello, I am basically trying to get a node that both publishes and subscribes. Imagine I have two nodes, node1 publishes and count_node subscribes and publishes as well. When node1 publishes "start count" I want count_node to start counting and publish the count value. If node1 publishes to "stop count" while count_node is counting, I want count_node to stop the active count. My attempt is below in code. I use ROS Melodic on Ubuntu 18.04

My attempt thus far fails because when I receive the message to start the count the callback function calls a function (startCount) that uses a while loop to increment. Thus until the count it finished, count_node cannot process the message to stop the count and the stopCount function is called AFTER the count is finished, not while its counting.

Is there a way to do what I want?

Here is my attempt at count_node:

import rospy
from std_msgs.msg import String
from std_msgs.msg import Int32

def callback(data):
    rospy.loginfo(rospy.get_caller_id() + ' I heard: %s', data.data)
    if (data.data=="start count"):
        startSanitization()        
    elif (data.data=="stop count"):
        stopSanitization()

def startCount():
    percent = 0

    while percent < 101 :
        rospy.loginfo(percent)
        pub.publish(percent)
        percent = percent + 1
        rate.sleep()     

def stopCount():
    percent = 0
    rospy.loginfo(percent)
    pub.publish(percent)

def listener():

    # In ROS, nodes are uniquely named. If two nodes with the same
    # name are launched, the previous one is kicked off. The
    # anonymous=True flag means that rospy will choose a unique
    # name for our 'listener' node so that multiple listeners can
    # run simultaneously.

    #check for message on Topic
    rospy.Subscriber('brwsrButtons', String, callback)

    rospy.loginfo("hello")
    # spin() simply keeps python from exiting until this node is stopped
    rospy.spin()

if __name__ == '__main__':
    #create a unique node
    rospy.init_node('count_node', anonymous=True)
    #create a publisher object
    pub = rospy.Publisher('progress', Int32, queue_size=10)
    rate = rospy.Rate(10) #10Hz
    #start the subscribing and publishing process
    listener()

*New Updated code:

import rospy
from std_msgs.msg import String
from std_msgs.msg import Int32
from multiprocessing import Process, Pipe
import thread

keepCounting = 0
percent = 0

def callback(data,keepCounting):
#    rospy.loginfo(rospy.get_caller_id() + ' I heard: %s', data.data)

    if (data.data=="Started Sanitizing the Room"):
        rospy.loginfo(rospy.get_caller_id() + ' I heard: %s', data.data)
        keepCounting = 1
    elif (data.data=="Stopped Sanitizing the Room"):
        keepCounting = 0


def countingProc(percent, keepCounting):

    while percent < 101 and keepCounting==0 :
        rospy.loginfo(percent)
        pub.publish(percent)
        percent += 1
        rate.sleep()
#    elif keepCounting==0 :




def listener(percent, keepCounting):

    # In ROS, nodes are uniquely named. If two nodes with the same
    # name are launched, the previous one is kicked off. The
    # anonymous=True flag means that rospy will choose a unique
    # name for our 'listener' node so that multiple listeners can
    # run simultaneously.
    #check for message on Topic
    rospy.Subscriber('brwsrButtons', String, callback,(keepCounting))
    rospy.loginfo("hello")
    thread.start_new_thread (countingProc, (percent, keepCounting,))
    # spin() simply keeps python from exiting until this node is stopped
    rospy.spin()

if __name__ == '__main__':
    #create a unique node
    rospy ...
(more)
edit retag flag offensive close merge delete

2 Answers

Sort by ยป oldest newest most voted
2

answered 2021-06-27 03:10:21 -0500

ParkerRobert gravatar image

updated 2021-06-27 03:13:38 -0500

As mentioned by @janindu, you can use threads to accomplish this task. At the same time, it is possible for you to restructure your code to accomplish the same task as well.

Let's try to understand your task:

When node1 publishes "Start Sanitizing the Room", count_node should start counting from 0 to 100 at a predefined rate. count_node should therefore start counting when it receives the std_msgs String data "Start Sanitizing the Room", and should stop counting when it receives the std_msgs String data "Stop Sanitizing the Room". The count can be seen from a rospy.loginfo statement or from the topic 'progress' that it is publishing to.

#!/usr/bin/env python
import rospy
from std_msgs.msg import String, Int32

keepCounting = True
count = 0

def callback(data):
    global keepCounting, count # global keyword allows us to access global variables
    if (data.data == "Start Sanitizing the Room"): 
        keepCounting = True
    elif (data.data == "Stop Sanitizing the Room"): 
        keepCounting = False

        # Restart the count
        count = 0
    else: 
        # There are 5 logger levels, debug, info, warn, error, and fatal
        rospy.logwarn("Received the wrong data, please check")

def listener(): 
    global keepCounting, count # global keyword allows us to access global variables

    # Start a subscriber to subscribe to String type data and pass the data to the callback function
     rospy.Subscriber('brwsrButtons', String, callback)

    # There are 5 logger levels, debug, info, warn, error, and fatal
     rospy.loginfo("Started listener method")

    # Run this node at 10hz 
     r = rospy.Rate(10)

    # As long as the node is not shutdown keep running this while loop
    while not rospy.is_shutdown():
        if (count < 101 and keepCounting): 
            rospy.loginfo("The count is: " + str(count))
            pub.publish(count)
            count += 1
         r.sleep()

if__name__ == '__main__': 
    # Take note that if anonymous is set to True, the name of the node will be randomised 
    # Something like /countageHandler_13506_1624779538099
    rospy.init_node('countageHandler', anonymous=True)

    # Start a publisher to publish Int32 type data which buffers up to 10 pieces of the data
     pub = rospy.Publisher('progress', Int32, queue_size=10)

    # Call the listener function defined above
    listener()

The reason this works is because you have 2 processes that are running: 1. The while loop in listener 2. The callback function based on the subscriber

As you initially intended in your original code, the count is incremented at a rate of 10hz, once the cleaning stops, the keepCount flag is set to False, and the count is reset to 0. When cleaning starts again, the keepCount flag is set to True and both the cleaning and the count resumes. keepCount flag is a global variable because it is defined outside any of the functions at the top of the code. It can be accessed within each of the functions because of the "global" keyword. Hope this helps!

edit flag offensive delete link more

Comments

Thanks for the help, I have used this solution and it worked wonderfully, I'm glad to see other ways to utilize ROS functionality, all the best!

ROSNewbie gravatar image ROSNewbie  ( 2021-06-27 14:59:08 -0500 )edit

Hi @ROSNewbie great to hear! You can go ahead and accept the answer you think is best by clicking on the green arrow in the circle below each answer, that way other users will be able to follow the answer as well. Cheers!

ParkerRobert gravatar image ParkerRobert  ( 2021-06-29 06:38:05 -0500 )edit
1

answered 2021-06-26 00:17:21 -0500

janindu gravatar image

updated 2021-06-26 00:18:10 -0500

My attempt thus far fails because when I receive the message to start the count the callback function calls a function (startCount) that uses a while loop to increment. Thus until the count it finished, count_node cannot process the message to stop the count and the stopCount function is called AFTER the count is finished, not while its counting.

Your diagnosis is 100% accurate. You need parallel execution of the subscriber callback and the counting.

What you need to do :

  1. Inside the startCount method, set a flag keepCounting to true, and do nothing else
  2. Inside the stopCount method, set the same flag keepCounting to false and do nothing else
  3. Before rospy.spin(), start a thread that will watch for the keepCounting flag and depending on the status of the flag, will publish the incremented count from 0, or reset the count to 0 and not increment

    If you want an intro to python threads : https://www.tutorialspoint.com/python...

edit flag offensive delete link more

Comments

Hello, I tried to implement this but it didn't work as intended, I'm not sure if my callback function is using changing the same keepCounting variable, although that might sound weird. I'm not sure my thread is working either. Can you check the bottom of my original question for the updated code? Thanks.

ROSNewbie gravatar image ROSNewbie  ( 2021-06-26 12:46:10 -0500 )edit

The problems in your new code that I see

  1. You have defined keepCounting and percent as global variables, yet you also pass them into the countingProc function as parameters. Then the keepCounting you access inside the countingProc and callback functions aren't the same value.
    1. countingProc will start executing immediately before rospy.spin() and end in 10 seconds. You need an outer while loop that will keep looping until the ROS node is killed.
    2. Your keepCounting is set to 1 in the callback when counting should start, but your while loop publishes messages when keepCounting is zero.
janindu gravatar image janindu  ( 2021-06-26 21:33:53 -0500 )edit

Thank you so much for the help, it was a great insight into threading, but I have decided to go with the other solution. All the best!

ROSNewbie gravatar image ROSNewbie  ( 2021-06-27 14:57:43 -0500 )edit

Question Tools

1 follower

Stats

Asked: 2021-06-25 14:16:43 -0500

Seen: 739 times

Last updated: Jun 27 '21