-
Notifications
You must be signed in to change notification settings - Fork 170
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for topic QOS for ros2topic bw, delay and hz #935
base: rolling
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,6 +21,9 @@ | |
|
||
from rclpy.duration import Duration | ||
from rclpy.expand_topic_name import expand_topic_name | ||
from rclpy.qos import QoSDurabilityPolicy | ||
from rclpy.qos import QoSPresetProfiles | ||
from rclpy.qos import QoSReliabilityPolicy | ||
from rclpy.topic_or_service_is_hidden import topic_or_service_is_hidden | ||
from rclpy.validate_full_topic_name import validate_full_topic_name | ||
from ros2cli.node.strategy import NodeStrategy | ||
|
@@ -253,3 +256,80 @@ def add_qos_arguments(parser: ArgumentParser, subscribe_or_publish: str, default | |
help=( | ||
f'Quality of service liveliness lease duration setting to {subscribe_or_publish} ' | ||
'with (overrides liveliness lease duration value of --qos-profile option')) | ||
|
||
|
||
def extract_qos_arguments(args): | ||
class QosArgs: | ||
pass | ||
|
||
qos = QosArgs() | ||
qos.qos_profile = args.qos_profile | ||
qos.qos_reliability = args.qos_reliability | ||
qos.qos_durability = args.qos_durability | ||
qos.qos_depth = args.qos_depth | ||
qos.qos_history = args.qos_history | ||
qos.qos_liveliness = args.qos_liveliness | ||
qos.qos_liveliness_lease_duration_seconds = args.qos_liveliness_lease_duration_seconds | ||
|
||
return qos | ||
|
||
|
||
def choose_qos(node, topic_name: str, qos_args): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i think moving this to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. besides that, i see There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure what you mean. From what I understand apart from the adaptive QoS configuration |
||
if (qos_args.qos_reliability is not None or | ||
qos_args.qos_durability is not None or | ||
qos_args.qos_depth is not None or | ||
qos_args.qos_history is not None or | ||
qos_args.qos_liveliness is not None or | ||
qos_args.qos_liveliness_lease_duration_seconds is not None): | ||
|
||
return qos_profile_from_short_keys( | ||
qos_args.qos_profile, | ||
reliability=qos_args.qos_reliability, | ||
durability=qos_args.qos_durability, | ||
depth=qos_args.qos_depth, | ||
history=qos_args.qos_history, | ||
liveliness=qos_args.qos_liveliness, | ||
liveliness_lease_duration_s=qos_args.qos_liveliness_lease_duration_seconds) | ||
|
||
qos_profile = QoSPresetProfiles.get_from_short_key(qos_args.qos_profile) | ||
reliability_reliable_endpoints_count = 0 | ||
durability_transient_local_endpoints_count = 0 | ||
|
||
pubs_info = node.get_publishers_info_by_topic(topic_name) | ||
publishers_count = len(pubs_info) | ||
if publishers_count == 0: | ||
return qos_profile | ||
|
||
for info in pubs_info: | ||
if (info.qos_profile.reliability == QoSReliabilityPolicy.RELIABLE): | ||
reliability_reliable_endpoints_count += 1 | ||
if (info.qos_profile.durability == QoSDurabilityPolicy.TRANSIENT_LOCAL): | ||
durability_transient_local_endpoints_count += 1 | ||
|
||
# If all endpoints are reliable, ask for reliable | ||
if reliability_reliable_endpoints_count == publishers_count: | ||
qos_profile.reliability = QoSReliabilityPolicy.RELIABLE | ||
else: | ||
if reliability_reliable_endpoints_count > 0: | ||
print( | ||
'Some, but not all, publishers are offering ' | ||
'QoSReliabilityPolicy.RELIABLE. Falling back to ' | ||
'QoSReliabilityPolicy.BEST_EFFORT as it will connect ' | ||
'to all publishers' | ||
) | ||
qos_profile.reliability = QoSReliabilityPolicy.BEST_EFFORT | ||
|
||
# If all endpoints are transient_local, ask for transient_local | ||
if durability_transient_local_endpoints_count == publishers_count: | ||
qos_profile.durability = QoSDurabilityPolicy.TRANSIENT_LOCAL | ||
else: | ||
if durability_transient_local_endpoints_count > 0: | ||
print( | ||
'Some, but not all, publishers are offering ' | ||
'QoSDurabilityPolicy.TRANSIENT_LOCAL. Falling back to ' | ||
'QoSDurabilityPolicy.VOLATILE as it will connect ' | ||
'to all publishers' | ||
) | ||
qos_profile.durability = QoSDurabilityPolicy.VOLATILE | ||
|
||
return qos_profile |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,9 +34,11 @@ | |
import traceback | ||
|
||
import rclpy | ||
from rclpy.qos import qos_profile_sensor_data | ||
from ros2cli.node.direct import add_arguments as add_direct_node_arguments | ||
from ros2cli.node.direct import DirectNode | ||
from ros2topic.api import add_qos_arguments | ||
from ros2topic.api import choose_qos | ||
from ros2topic.api import extract_qos_arguments | ||
from ros2topic.api import get_msg_class | ||
from ros2topic.api import positive_int | ||
from ros2topic.api import TopicNameCompleter | ||
|
@@ -62,19 +64,21 @@ class BwVerb(VerbExtension): | |
|
||
def add_arguments(self, parser, cli_name): | ||
arg = parser.add_argument( | ||
'topic', | ||
'topic_name', | ||
Comment on lines
-65
to
+67
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need to change this? Is this related to what this PR is fixing? just checking. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not needed but I initially implemented this feature by removing all parameters from |
||
help='Topic name to monitor for bandwidth utilization') | ||
arg.completer = TopicNameCompleter( | ||
include_hidden_topics_key='include_hidden_topics') | ||
add_qos_arguments(parser, 'subscribe', 'sensor_data') | ||
parser.add_argument( | ||
'--window', '-w', type=positive_int, default=DEFAULT_WINDOW_SIZE, | ||
'--window', '-w', dest='window_size', type=positive_int, default=DEFAULT_WINDOW_SIZE, | ||
Comment on lines
-70
to
+73
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here, is this required for this PR? |
||
help='maximum window size, in # of messages, for calculating rate ' | ||
f'(default: {DEFAULT_WINDOW_SIZE})', metavar='WINDOW') | ||
add_direct_node_arguments(parser) | ||
|
||
def main(self, *, args): | ||
qos_args = extract_qos_arguments(args) | ||
with DirectNode(args) as node: | ||
_rostopic_bw(node.node, args.topic, window_size=args.window) | ||
_rostopic_bw(node.node, args.topic_name, qos_args, window_size=args.window_size) | ||
|
||
|
||
class ROSTopicBandwidth(object): | ||
|
@@ -150,20 +154,22 @@ def print_bw(self): | |
print(f'{bw} from {n} messages\n\tMessage size mean: {mean} min: {min_s} max: {max_s}') | ||
|
||
|
||
def _rostopic_bw(node, topic, window_size=DEFAULT_WINDOW_SIZE): | ||
def _rostopic_bw(node, topic, qos, window_size=DEFAULT_WINDOW_SIZE): | ||
"""Periodically print the received bandwidth of a topic to console until shutdown.""" | ||
# pause bw until topic is published | ||
msg_class = get_msg_class(node, topic, blocking=True, include_hidden_topics=True) | ||
if msg_class is None: | ||
node.destroy_node() | ||
return | ||
|
||
qos_profile = choose_qos(node, topic_name=topic, qos_args=qos) | ||
|
||
rt = ROSTopicBandwidth(node, window_size) | ||
node.create_subscription( | ||
msg_class, | ||
topic, | ||
rt.callback, | ||
qos_profile_sensor_data, | ||
qos_profile, | ||
raw=True | ||
) | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not understand why this is needed here. it does not do anything but passing all variables into another class object, can you elaborate this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's definitely useless. I had a few alternatives in mind but none that I was satisfied with:
args
to the_rostopic_verb
functions as theqos
parameter. But in that case I might as well remove the other arguments from_rostopic_verb
sinceargs
has all of them_rostopic_verb
andchoose_qos
I'm not sure what would best fit the style of this package. What do you think ?