Skip to content

Commit

Permalink
Implement Foreign Mapping Check Override (#367)
Browse files Browse the repository at this point in the history
  • Loading branch information
methylDragon authored Jun 30, 2022
1 parent 1488b12 commit 4af421d
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 20 deletions.
70 changes: 66 additions & 4 deletions doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Each mapping rule can have one of three types:
1. A package mapping rule is defined by:

- a ``ros1_package_name``
- a ``ros2_package_name`` (which must be the same as the ROS 2 package this mapping rule is defined in)
- a ``ros2_package_name`` (which, by default, must be the same as the ROS 2 package this mapping rule is defined in)

2. A message mapping rule is defined by the attributes of a package mapping rule and:

Expand Down Expand Up @@ -66,7 +66,7 @@ In case of services, each mapping rule can have one of two types:
1. A package mapping rule is defined by:

- a ``ros1_package_name``
- a ``ros2_package_name`` (which must be the same as the ROS 2 package this mapping rule is defined in)
- a ``ros2_package_name`` (which, by default, must be the same as the ROS 2 package this mapping rule is defined in)

2. A service name mapping rule is defined by the attributes of a package mapping rule and:

Expand All @@ -78,13 +78,13 @@ A custom field mapping is currently not supported for services.
How can I install mapping rule files?
-------------------------------------

The mapping rule files must be exported in the ``package.xml`` in order to be processed by this package::
The mapping rule files must be exported in the ROS 2 package's ``package.xml`` in order to be processed by this package::

<export>
<ros1_bridge mapping_rules="my_mapping_rules.yaml"/>
</export>

The yaml files must also be installed in the ``CMakeLists.txt``::
The yaml files must also be installed in the ROS 2 package's ``CMakeLists.txt``::

install(
FILES my_mapping_rules.yaml
Expand Down Expand Up @@ -238,6 +238,68 @@ Run the bridge, reusing shells from above::
rostopic list
rostopic echo /joint_command


How can I define a mapping rules for a foreign package, from a foreign package?
-------------------------------------------------------------------------------

In the previous sections, it was stated that the ``ros2_package_name`` mapping rule must be the same as the ROS 2 package the mapping rule was defined in.
While this is **recommended** to prevent conflicting and/or duplicate rules, it is possible to override the check that enforces this with the ``enable_foreign_mappings`` field.

This will mean that, for every package mapping rule defined in the ``yaml`` file, the check for ROS 2 package name equality will be skipped.
Again, note that this is a dark art that should be wielded with responsibility, please be very careful with this!
If there are conflicting mapping rules, the last one that is parsed in sorting order is used!
This is usually hard to predict, so be very careful!

With ``enable_foreign_mappings`` set to ``true``, you can then specify mapping rules for ROS 2 packages that are not the same as the package your mapping rules file resides in::

-
enable_foreign_mappings: true
ros1_package_name: 'ros1_pkg_name'
ros1_service_name: 'ros1_srv_name'
ros2_package_name: 'ros2_FOREIGN_pkg_name' # the package with the message definition
ros2_service_name: 'ros2_srv_name'

You must also make the ``ros1_bridge_foreign_mapping`` ament resource available to the index in the mapping package's ``CMakeLists.txt``.
A good place to put the line is to do this is before the install rule for your mapping rules, like so::

-
ament_index_register_resource("ros1_bridge_foreign_mapping")
install(
FILES YOUR_MAPPING_RULE_FILE.yaml
DESTINATION share/${PROJECT_NAME})

**Note**: In this case, the package name you should put in ```ros2_package_name`` is the name of the package with the message definition.
In effect, it'll be a foreign package, relative to the mapping package you're defining your mapping rules in.

An example directory layout looks like this::

.
├─ ros1_msgs_ws
│ └─ src
│ └─ ros1_bridge_msgs
│ └─ msg
│ └─ ros1_msg_name.msg
├─ ros2_msgs_ws
│ └─ src
│ └─ ros2_bridge_msgs
│ │ ├─ msg
│ │ │ └─ ros2_msg_name.msg
│ └─ ros2_bridge_mappings
│ └─ # YAML file that defines mapping rules for bridge_msgs, or some other foreign msg package
└─ bridge_ws
└─ src
└─ ros1_bridge

In the above example, the mapping rule would look like this::

-
enable_foreign_mappings: true
ros1_package_name: 'ros1_bridge_msgs'
ros1_message_name: 'ros1_msg_name'
ros2_package_name: 'ros2_bridge_msgs' # this is a foreign package, relative to ros2_bridge_mappings!
ros2_message_name: 'ros2_msg_name'


Known Issues
------------

Expand Down
60 changes: 44 additions & 16 deletions ros1_bridge/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,10 +255,18 @@ def get_ros2_messages():
msgs = []
rules = []
# get messages from packages
resource_type = 'rosidl_interfaces'
resources = ament_index_python.get_resources(resource_type)
for package_name, prefix_path in resources.items():
pkgs.append(package_name)
resources = {
key: (val, 'rosidl_interfaces') for key, val
in ament_index_python.get_resources('rosidl_interfaces').items()
}
resources.update({
key: (val, 'ros1_bridge_foreign_mapping') for key, val
in ament_index_python.get_resources('ros1_bridge_foreign_mapping').items()
})
for package_name, val_tuple in resources.items():
prefix_path, resource_type = val_tuple
if resource_type == 'rosidl_interfaces': # Required, otherwise linking fails
pkgs.append(package_name)
resource, _ = ament_index_python.get_resource(resource_type, package_name)
interfaces = resource.splitlines()
message_names = {
Expand Down Expand Up @@ -308,10 +316,19 @@ def get_ros2_services():
pkgs = []
srvs = []
rules = []
resources = {
key: (val, 'rosidl_interfaces') for key, val
in ament_index_python.get_resources('rosidl_interfaces').items()
}
resources.update({
key: (val, 'ros1_bridge_foreign_mapping') for key, val
in ament_index_python.get_resources('ros1_bridge_foreign_mapping').items()
})
resource_type = 'rosidl_interfaces'
resources = ament_index_python.get_resources(resource_type)
for package_name, prefix_path in resources.items():
pkgs.append(package_name)
for package_name, val_tuple in resources.items():
prefix_path, resource_type = val_tuple
if resource_type == 'rosidl_interfaces': # Required, otherwise linking fails
pkgs.append(package_name)
resource, _ = ament_index_python.get_resource(resource_type, package_name)
interfaces = resource.splitlines()
service_names = {
Expand Down Expand Up @@ -376,25 +393,36 @@ class MappingRule:
__slots__ = [
'ros1_package_name',
'ros2_package_name',
'package_mapping'
'package_mapping',
'foreign_mapping'
]

def __init__(self, data, expected_package_name):
if all(n in data for n in ('ros1_package_name', 'ros2_package_name')):
if data['ros2_package_name'] != expected_package_name:
if (data['ros2_package_name'] != expected_package_name
and not data.get('enable_foreign_mappings')):
raise Exception(
('Ignoring rule which affects a different ROS 2 package (%s) '
'then the one it is defined in (%s)') %
(data['ros2_package_name'], expected_package_name))
'then the one it is defined in (%s)\n\n'
'(Please set `enable_foreign_mappings` to `true` if '
'you explicitly want the rule to apply.)') %
(data['ros2_package_name'], expected_package_name)
)
self.ros1_package_name = data['ros1_package_name']
self.ros2_package_name = data['ros2_package_name']
self.package_mapping = (len(data) == 2)
self.foreign_mapping = bool(data.get('enable_foreign_mappings'))
self.package_mapping = (
len(data) == (2 + int('enable_foreign_mappings' in data))
)
else:
raise Exception('Ignoring a rule without a ros1_package_name and/or ros2_package_name')

def is_package_mapping(self):
return self.package_mapping

def is_foreign_mapping(self):
return self.foreign_mapping

def __repr__(self):
return self.__str__()

Expand Down Expand Up @@ -422,10 +450,10 @@ def __init__(self, data, expected_package_name):
if 'fields_2_to_1' in data:
for ros2_field_name, ros1_field_name in data['fields_2_to_1'].items():
self.fields_1_to_2[ros1_field_name] = ros2_field_name
elif len(data) > 4:
elif len(data) > 4 + int('enable_foreign_mappings' in data):
raise RuntimeError(
'Mapping for package %s contains unknown field(s)' % self.ros2_package_name)
elif len(data) > 2:
elif len(data) > 2 + int('enable_foreign_mappings' in data):
raise RuntimeError(
'Mapping for package %s contains unknown field(s)' % self.ros2_package_name)

Expand Down Expand Up @@ -467,10 +495,10 @@ def __init__(self, data, expected_package_name):
for ros1_field_name, ros2_field_name in data['response_fields_1_to_2'].items():
self.response_fields_1_to_2[ros1_field_name] = ros2_field_name
expected_keys += 1
elif len(data) > expected_keys:
elif len(data) > expected_keys + int('enable_foreign_mappings' in data):
raise RuntimeError(
'Mapping for package %s contains unknown field(s)' % self.ros2_package_name)
elif len(data) > 2:
elif len(data) > 2 + int('enable_foreign_mappings' in data):
raise RuntimeError(
'Mapping for package %s contains unknown field(s)' % self.ros2_package_name)

Expand Down

0 comments on commit 4af421d

Please sign in to comment.