-
Notifications
You must be signed in to change notification settings - Fork 0
callbacks.py ‐ Using the CheckBoxCallback class
There is a lot of boilerplate involved for making a checkbox menu option for an interactive marker in RViz. In order to simplify the creation of such menu options, I created the template class CheckBoxCallback
in callbacks.py
, which simplifies the process by automatically handling the work of toggling the checkbox state and sending the updated menu item state to the corresponding menu handler and interactive marker server. It also surfaces four abstract functions for user-defined code that is to be executed whenever the button is clicked. The functions are as follows:
Whether these functions are run when the button is clicked depends on whether the checkbox was enabled or disabled.
-
_on_enable
: This function is run when the checkbox is toggled from off to on -
_on_disable
: This function is run when the checkbox is toggled from on to off
These functions are run every time the button is clicked, regardless of whether the checkbox was enabled or disabled. Use these functions if you have code that needs to be executed every time a button is pressed (for example, to call a toggle function).
-
_before_either
: This function is run before_on_enable
/_on_disable
is run -
_after_either
: This function is run after_on_enable
/_on_disable
is run
EVEN IF YOU DO NOT NEED ALL FOUR OF THESE FUNCTIONS TO DO SOMETHING, YOU STILL MUST IMPLEMENT THEM IN SOME CAPACITY, EVEN IF ALL THEY DO IS pass
. THE CLASS WAS DESIGNED THIS WAY TO ENSURE THAT USERS ARE PRECISELY AWARE OF THE ORDER THEIR FUNCTIONS ARE CALLED.
The call order for these functions when the button is clicked is as follows:
flowchart TD
classDef class1 fill:blue, stroke:white, color:white
classDef class2 fill:green, stroke:white, color:white
classDef class3 fill:red, stroke:white, color:white
id1(["Button is Clicked"])
id2["_before_either() is called"]
class id1 class1
class id2 class1
id3{"Checkbox State is Checked"}
class id3 class1
id4a["_on_enable() is called"]
id5a["Checkbox is automatically enabled"]
class id4a,id5a class3
id4b["_on_disable() is called"]
id5b["Checkbox is automatically disabled"]
class id4b,id5b class2
id6(["_after_either() is called"])
class id6 class1
id1 ---> id2
id2 ---> id3
id3 -- Checkbox is disabled --> id4a
id4a ---> id5a
id5a ---> id6
id3 -- Checkbox is enabled --> id4b
id4b ---> id5b
id5b ---> id6
The following section explains how to use this class to add a checkbox button to the context menu of an interactive marker.
This tutorial will make an example Checkbox button that does the following:
- Print a user-given string whenever the button is clicked
- Track the amount of times that the box has been checked, and print that count whenever the box is checked
- Track the amount of times that the box has been unchecked, and print that count whenever the box is unchecked
- Track the amount of times that the button has been clicked in total, and print that whenever the button is clicked
However, this tutorial can still be used as a guide for making a checkbox button with your own functionality. Simply replace the relevant functions with your own code.
In callbacks.py, create the callback object for your button as a subclass of the CheckBoxCallback
class as such:
class ExampleCheckboxCallback(CheckBoxCallback):
Its __init__
method will need to at least have the parameters menu_handler
and marker_server
, as they are needed to update the checkbox's "checked" state. Any additional parameters your callback needs for its functionality will go here. For example, this callback will take in a string to be logged to the ROS console whenever the button is pressed, so the init function will take in an extra parameter for that string. We will also need to initialize the tallies for the button-click totals.
Regardless of what extra parameters you need, the init function will always need to call the superclass's init with the menu_handler
and marker_server
parameters.
With that all in mind, __init__
for our example class is as follows:
class ExampleCheckboxCallback(CheckBoxCallback):
__init__(self, menu_handler, marker_server, user_string):
super().__init__(menu_handler, marker_server)
self.s = user_string
self.clicks = 0
self.checks = 0
self.unchecks = 0
As stated above, regardless of whether you need them to actually do anything, all four of the functions _before_either
, _after_either
, _on_enable
, and _on_disable
need to be defined in some way.
For our example callback, we have two state-independent operations for when the button is clicked: printing that user-defined string passed to our __init__
, and incrementing & printing the total of how many times the button has been clicked. It doesn't matter whether this is done before or after the state-dependent code is run, so we'll have it run after the state-dependent code.
Since we're putting all of our state-independent code into _after_either
, our _before_either
can simply be
def _before_either(self):
pass
However, for the sake of the demonstration, we'll have the _before_either
method log a message to demonstrate how it's the first thing run when the button is clicked.
def _before_either(self):
rospy.loginfo("_before_either has been called")
Our _after_either
will then be as follows:
def _after_either(self):
rospy.loginfo(f"The string is {self.s}")
self.clicks += 1
rospy.loginfo(f"Total button clicks: {self.clicks}")
We also want the button to track and print how many times it has been checked & unchecked. Those functions, respectively, are:
def _on_enable(self):
self.checks += 1
rospy.loginfo(f"The box has been checked {self.checks} time(s)")
def _on_disable(self):
self.unchecks += 1
rospy.loginfo(f"The box has been checked {self.unchecks} time(s)")
All together, our class is now
class ExampleCheckboxCallback(CheckBoxCallback):
def __init__(self, menu_handler, marker_server, user_string):
super().__init__(menu_handler, marker_server)
self.s = user_string
self.clicks = 0
self.checks = 0
self.unchecks = 0
def _before_either(self):
rospy.loginfo("_before_either has been called")
def _after_either(self):
rospy.loginfo(f"The string is {self.s}")
self.clicks += 1
rospy.loginfo(f"Total button clicks: {self.clicks}")
def _on_enable(self):
self.checks += 1
rospy.loginfo(f"The box has been checked {self.checks} time(s)")
def _on_disable(self):
self.unchecks += 1
rospy.loginfo(f"The box has been checked {self.unchecks} time(s)")
Now that the callback for the button has been created, all that's left is creating the actual button in RViz. In this example, I'll be adding the button to the chair_marker and therefor using its corresponding MenuHandler
and `InteractiveMarkerServer.
menu_handler.insert("Example Checkbox", callback=ExampleCheckboxCallback(menu_handler=menu_handler,
marker_server=server,
user_string="foo"))
After the call to the menu handler's insert
method, don't forget to set an initial state for the checkbox. The insert
method returns a handler to the menu item, which you pass to the setCheckState
method.
handle = menu_handler.insert("Example Checkbox", callback=ExampleCheckboxCallback(menu_handler=menu_handler,
marker_server=server,
user_string="foo"))
menu_handler.setCheckState(handle, menu_handler.UNCHECKED)
All together, in-action: