Source code for dynrec.discovery

# Copyright 2026 Juan Daniel Suárez González
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Finding the recorder on the graph, and deciding which one is meant.

Takes a graph listing as an argument rather than fetching one, so the rule can be tested without
a ROS graph -- the same split `ros2 dynrec` made, and for the same reason: this is the part with
judgement in it.
"""

from dynrec.errors import AmbiguousRecorder, RecorderNotFound

#: The service every recorder offers, used to identify one on the graph.
GET_STATUS_TYPE = 'rosbag2_dynamic_recorder_interfaces/srv/GetStatus'
GET_STATUS_SUFFIX = '/get_status'


[docs] def recorder_nodes(service_names_and_types): """Fully qualified names of the recorders visible on the graph, sorted. A recorder is identified by offering `~/get_status` of our own type, not by node name. A recorder launched under a different name or pushed into a namespace is therefore still found, and an unrelated node that happens to be called `rosbag2_dynamic_recorder` is not. """ found = set() for name, types in service_names_and_types: if name.endswith(GET_STATUS_SUFFIX) and GET_STATUS_TYPE in types: found.add(name[:-len(GET_STATUS_SUFFIX)]) return sorted(found)
[docs] def node_name(requested): """A recorder name as the graph spells it: one leading slash, whatever the caller typed.""" return '/' + requested.strip('/')
[docs] def choose_recorder(discovered, requested=None): """Pick the recorder to talk to, or raise explaining why that is not possible. A requested name is trusted even when discovery did not turn it up: graph discovery can lag, and refusing a name that is about to appear would be worse than trying it and timing out. """ if requested: return node_name(requested) if not discovered: raise RecorderNotFound( 'no rosbag2_dynamic_recorder found on the graph. ' 'Is one running, and is ROS_DOMAIN_ID the same for both?') if len(discovered) > 1: raise AmbiguousRecorder( 'several recorders are running; name the one you mean: ' + ', '.join(discovered), discovered) return discovered[0]