Skip to content

Commit

Permalink
update to use rosidl_parser and .idl files rather than rosidl_adapter…
Browse files Browse the repository at this point in the history
… and .msg files (#292)

Signed-off-by: William Woodall <william@osrfoundation.org>
  • Loading branch information
wjwwood committed Nov 18, 2020
1 parent 719c411 commit 01b55fa
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 58 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Expand Up @@ -96,7 +96,7 @@ foreach(package_name ${ros2_interface_packages})
if(NOT "${package_name}" STREQUAL "builtin_interfaces")
list(APPEND generated_files "${generated_path}/${package_name}_factories.cpp")
list(APPEND generated_files "${generated_path}/${package_name}_factories.hpp")
foreach(interface_file ${${package_name}_INTERFACE_FILES})
foreach(interface_file ${${package_name}_IDL_FILES})
file(TO_CMAKE_PATH "${interface_file}" interface_name)
get_filename_component(interface_basename "${interface_name}" NAME_WE)
# skipping actions and request and response messages of services
Expand Down
84 changes: 55 additions & 29 deletions resource/interface_factories.cpp.em
Expand Up @@ -20,6 +20,10 @@
@
@{
from ros1_bridge import camel_case_to_lower_case_underscore
from rosidl_parser.definition import AbstractNestedType
from rosidl_parser.definition import AbstractSequence
from rosidl_parser.definition import BoundedSequence
from rosidl_parser.definition import NamespacedType
}@
#include "@(ros2_package_name)_factories.hpp"

Expand Down Expand Up @@ -118,35 +122,46 @@ Factory<
@{
ros1_field_selection = '.'.join((str(field.name) for field in ros1_fields))
ros2_field_selection = '.'.join((str(field.name) for field in ros2_fields))

if isinstance(ros2_fields[-1].type, NamespacedType):
namespaces = ros2_fields[-1].type.namespaces
assert len(namespaces) == 2 and namespaces[1] == 'msg', \
"messages not using the '<pkg_name>, msg, <type_name>' triplet are not supported"
}
@[ if not ros2_fields[-1].type.is_array]@
@[ if not isinstance(ros2_fields[-1].type, AbstractNestedType)]@
// convert non-array field
@[ if not ros2_fields[-1].type.pkg_name]@
@[ if not isinstance(ros2_fields[-1].type, NamespacedType)]@
// convert primitive field
ros2_msg.@(ros2_field_selection) = ros1_msg.@(ros1_field_selection);
@[ elif ros2_fields[-1].type.pkg_name == 'builtin_interfaces']@
@[ elif ros2_fields[-1].type.namespaces[0] == 'builtin_interfaces']@
// convert builtin field
ros1_bridge::convert_1_to_2(ros1_msg.@(ros1_field_selection), ros2_msg.@(ros2_field_selection));
@[ else]@
// convert sub message field
Factory<
@(ros1_fields[-1].pkg_name)::@(ros1_fields[-1].msg_name),
@(ros2_fields[-1].type.pkg_name)::msg::@(ros2_fields[-1].type.type)
@(ros2_fields[-1].type.namespaces[0])::msg::@(ros2_fields[-1].type.name)
>::convert_1_to_2(
ros1_msg.@(ros1_field_selection), ros2_msg.@(ros2_field_selection));
@[ end if]@
@[ else]@
// convert array field
@[ if not ros2_fields[-1].type.array_size or ros2_fields[-1].type.is_upper_bound]@
// ensure array size
@[ if ros2_fields[-1].type.is_upper_bound]@
// check boundary
assert(ros1_msg.@(ros1_field_selection).size() <= @(ros2_fields[-1].type.array_size));
// convert array or sequence field
@[ if isinstance(ros2_fields[-1].type, AbstractSequence)]@
// dynamically sized sequence, ensure destination sequence/vector size is large enough
@[ if isinstance(ros2_fields[-1].type, BoundedSequence)]@
// bounded size sequence, check that the ros 1 vector size is not larger than the upper bound for the target
assert(ros1_msg.@(ros1_field_selection).size() <= @(ros2_fields[-1].type.maximum_size));
@[ end if]@
// dynamic arrays must be resized
// resize ros2 field to match the ros1 field
ros2_msg.@(ros2_field_selection).resize(ros1_msg.@(ros1_field_selection).size());
@[ else]@
// statically sized array
static_assert(
sizeof(ros2_msg.@(ros2_field_selection)) >= sizeof(ros1_msg.@(ros1_field_selection)),
"destination array not large enough for source array"
);
@[ end if]@
@[ if not ros2_fields[-1].type.pkg_name]@
@[ if not isinstance(ros2_fields[-1].type.value_type, NamespacedType)]@
// convert primitive array elements
std::copy(
ros1_msg.@(ros1_field_selection).begin(),
Expand All @@ -155,22 +170,22 @@ ros2_field_selection = '.'.join((str(field.name) for field in ros2_fields))
@[ else]@
// copy element wise since the type is different
{
auto ros1_it = ros1_msg.@(ros1_field_selection).begin();
auto ros1_it = ros1_msg.@(ros1_field_selection).cbegin();
auto ros2_it = ros2_msg.@(ros2_field_selection).begin();
for (
;
ros1_it != ros1_msg.@(ros1_field_selection).end() &&
ros1_it != ros1_msg.@(ros1_field_selection).cend() &&
ros2_it != ros2_msg.@(ros2_field_selection).end();
++ros1_it, ++ros2_it
)
{
// convert sub message element
@[ if ros2_fields[-1].type.pkg_name == 'builtin_interfaces']@
@[ if ros2_fields[-1].type.value_type.namespaces[0] == 'builtin_interfaces']@
ros1_bridge::convert_1_to_2(*ros1_it, *ros2_it);
@[ else]@
Factory<
@(ros1_fields[-1].pkg_name)::@(ros1_fields[-1].msg_name),
@(ros2_fields[-1].type.pkg_name)::msg::@(ros2_fields[-1].type.type)
@(ros2_fields[-1].type.value_type.namespaces[0])::msg::@(ros2_fields[-1].type.value_type.name)
>::convert_1_to_2(
*ros1_it, *ros2_it);
@[ end if]@
Expand Down Expand Up @@ -198,31 +213,42 @@ Factory<
@{
ros2_field_selection = '.'.join((str(field.name) for field in ros2_fields))
ros1_field_selection = '.'.join((str(field.name) for field in ros1_fields))

if isinstance(ros2_fields[-1].type, NamespacedType):
namespaces = ros2_fields[-1].type.namespaces
assert len(namespaces) == 2 and namespaces[1] == 'msg', \
"messages not using the '<pkg_name>, msg, <type_name>' triplet are not supported"
}
@[ if not ros2_fields[-1].type.is_array]@
@[ if not isinstance(ros2_fields[-1].type, AbstractNestedType)]@
// convert non-array field
@[ if not ros2_fields[-1].type.pkg_name]@
@[ if not isinstance(ros2_fields[-1].type, NamespacedType)]@
// convert primitive field
ros1_msg.@(ros1_field_selection) = ros2_msg.@(ros2_field_selection);
@[ elif ros2_fields[-1].type.pkg_name == 'builtin_interfaces']@
@[ elif ros2_fields[-1].type.namespaces[0] == 'builtin_interfaces']@
// convert builtin field
ros1_bridge::convert_2_to_1(ros2_msg.@(ros2_field_selection), ros1_msg.@(ros1_field_selection));
@[ else]@
// convert sub message field
Factory<
@(ros1_fields[-1].pkg_name)::@(ros1_fields[-1].msg_name),
@(ros2_fields[-1].type.pkg_name)::msg::@(ros2_fields[-1].type.type)
@(ros2_fields[-1].type.namespaces[0])::msg::@(ros2_fields[-1].type.name)
>::convert_2_to_1(
ros2_msg.@(ros2_field_selection), ros1_msg.@(ros1_field_selection));
@[ end if]@
@[ else]@
// convert array field
@[ if not ros2_fields[-1].type.array_size or ros2_fields[-1].type.is_upper_bound]@
// ensure array size
// dynamic arrays must be resized
// convert array or sequence field
@[ if isinstance(ros2_fields[-1].type, AbstractSequence)]@
// dynamically sized sequence, ensure destination vector size is large enough
// resize ros1 field to match the ros2 field
ros1_msg.@(ros1_field_selection).resize(ros2_msg.@(ros2_field_selection).size());
@[ else]@
// statically sized array
static_assert(
sizeof(ros1_msg.@(ros1_field_selection)) >= sizeof(ros2_msg.@(ros2_field_selection)),
"destination array not large enough for source array"
);
@[ end if]@
@[ if not ros2_fields[-1].type.pkg_name]@
@[ if not isinstance(ros2_fields[-1].type.value_type, NamespacedType)]@
// convert primitive array elements
std::copy(
ros2_msg.@(ros2_field_selection).begin(),
Expand All @@ -231,22 +257,22 @@ ros1_field_selection = '.'.join((str(field.name) for field in ros1_fields))
@[ else]@
// copy element wise since the type is different
{
auto ros2_it = ros2_msg.@(ros2_field_selection).begin();
auto ros2_it = ros2_msg.@(ros2_field_selection).cbegin();
auto ros1_it = ros1_msg.@(ros1_field_selection).begin();
for (
;
ros2_it != ros2_msg.@(ros2_field_selection).end() &&
ros2_it != ros2_msg.@(ros2_field_selection).cend() &&
ros1_it != ros1_msg.@(ros1_field_selection).end();
++ros2_it, ++ros1_it
)
{
// convert sub message element
@[ if ros2_fields[-1].type.pkg_name == 'builtin_interfaces']@
@[ if ros2_fields[-1].type.value_type.namespaces[0] == 'builtin_interfaces']@
ros1_bridge::convert_2_to_1(*ros2_it, *ros1_it);
@[ else]@
Factory<
@(ros1_fields[-1].pkg_name)::@(ros1_fields[-1].msg_name),
@(ros2_fields[-1].type.pkg_name)::msg::@(ros2_fields[-1].type.type)
@(ros2_fields[-1].type.value_type.namespaces[0])::msg::@(ros2_fields[-1].type.value_type.name)
>::convert_2_to_1(
*ros2_it, *ros1_it);
@[ end if]@
Expand Down
101 changes: 73 additions & 28 deletions ros1_bridge/__init__.py
Expand Up @@ -25,6 +25,7 @@

import rosidl_adapter.parser
from rosidl_cmake import expand_template
import rosidl_parser.parser

import yaml

Expand Down Expand Up @@ -681,11 +682,17 @@ def consume_field(field):
def get_ros2_selected_fields(ros2_field_selection, parent_ros2_spec, msg_idx):
selected_fields = []
fields = ros2_field_selection.split('.')
current_field = [f for f in parent_ros2_spec.fields if f.name == fields[0]][0]
current_field = [
member for member in parent_ros2_spec.structure.members
if member.name == fields[0]
][0]
selected_fields.append(current_field)
for field in fields[1:]:
parent_ros2_spec = load_ros2_message(msg_idx.ros2_get_from_field(current_field))
current_field = [f for f in parent_ros2_spec.fields if f.name == field][0]
current_field = [
member for member in parent_ros2_spec.structure.members
if member.name == field
][0]
selected_fields.append(current_field)
return tuple(selected_fields)

Expand Down Expand Up @@ -749,12 +756,11 @@ def determine_field_mapping(ros1_msg, ros2_msg, mapping_rules, msg_idx):
ros1_field_missing_in_ros2 = False

for ros1_field in ros1_spec.parsed_fields():
for ros2_field in ros2_spec.fields:
if ros1_field.name.lower() == ros2_field.name:
for ros2_member in ros2_spec.structure.members:
if ros1_field.name.lower() == ros2_member.name:
# get package name and message name from ROS 1 field type
if ros2_field.type.pkg_name:
update_ros1_field_information(ros1_field, ros1_msg.package_name)
mapping.add_field_pair(ros1_field, ros2_field)
update_ros1_field_information(ros1_field, ros1_msg.package_name)
mapping.add_field_pair(ros1_field, ros2_member)
break
else:
# this allows fields to exist in ROS 1 but not in ROS 2
Expand All @@ -764,9 +770,9 @@ def determine_field_mapping(ros1_msg, ros2_msg, mapping_rules, msg_idx):
# if some fields exist in ROS 1 but not in ROS 2
# check that no fields exist in ROS 2 but not in ROS 1
# since then it might be the case that those have been renamed and should be mapped
for ros2_field in ros2_spec.fields:
for ros2_member in ros2_spec.structure.members:
for ros1_field in ros1_spec.parsed_fields():
if ros1_field.name.lower() == ros2_field.name:
if ros1_field.name.lower() == ros2_member.name:
break
else:
# if fields from both sides are not mappable the whole message is not mappable
Expand Down Expand Up @@ -798,14 +804,45 @@ def load_ros1_service(ros1_srv):


def load_ros2_message(ros2_msg):
message_path = os.path.join(
ros2_msg.prefix_path, 'share', ros2_msg.package_name, 'msg',
ros2_msg.message_name + '.msg')
try:
spec = rosidl_adapter.parser.parse_message_file(ros2_msg.package_name, message_path)
except rosidl_adapter.parser.InvalidSpecification:
return None
return spec
message_basepath = os.path.join(ros2_msg.prefix_path, 'share')
message_relative_path = \
os.path.join(ros2_msg.package_name, 'msg', ros2_msg.message_name)
message_path = os.path.join(message_basepath, message_relative_path)
# Check to see if the message is defined as a .msg file or an .idl file,
# but preferring '.idl' if both exist.
if os.path.exists(message_path + '.idl'):
message_path += '.idl'
message_relative_path += '.idl'
elif os.path.exists(message_path + '.msg'):
message_path += '.msg'
message_relative_path += '.msg'
else:
raise RuntimeError(
f"message '{ros2_msg.package_name}/msg/{ros2_msg.message_name}' "
f"was not found in prefix '{ros2_msg.prefix_path}' with either "
f"file extension '.msg' or '.idl'")
# We don't support .msg files, but that shouldn't be a problem since an .idl
# version should have been created when the package was built by rosidl_adapter.
if message_path.endswith('.msg'):
raise RuntimeError(
"ros1_bridge cannot process ROS 2 message definitions that lack a '.idl' version, "
"which normally does not occur as rosidl_adapter should create a '.idl' version "
"when building the message package which contains the original '.msg' file."
)
if not message_path.endswith('.idl'):
raise RuntimeError(
f"message_path '{message_path}' unexpectedly does not end with '.idl'"
)
idl_locator = \
rosidl_parser.definition.IdlLocator(message_basepath, message_relative_path)
spec = rosidl_parser.parser.parse_idl_file(idl_locator)
messages = spec.content.get_elements_of_type(rosidl_parser.definition.Message)
if len(messages) != 1:
raise RuntimeError(
"unexpectedly found multiple message definitions when processing "
f"message '{ros2_msg.package_name}/msg/{ros2_msg.message_name}'"
)
return messages[0]


def load_ros2_service(ros2_srv):
Expand Down Expand Up @@ -844,26 +881,34 @@ def __init__(self, ros1_msg, ros2_msg):
self.fields_2_to_1 = OrderedDict()
self.depends_on_ros2_messages = set()

def add_field_pair(self, ros1_fields, ros2_fields):
def add_field_pair(self, ros1_fields, ros2_members):
"""
Add a new mapping for a pair of ROS 1 and ROS 2 messages.
:type ros1_fields: either a genmsg.msgs.Field object with additional attributes `pkg_name`
and `msg_name` as defined by `update_ros1_field_information`, or a tuple of objects of
that type
:type ros2_field: either a rosidl_adapter.parser.Field object, or a tuple objects of
that type
:type ros2_members: a single, or list of, rosidl_parser.definition.Member object(s)
"""
if not isinstance(ros1_fields, tuple):
ros1_fields = (ros1_fields,)
if not isinstance(ros2_fields, tuple):
ros2_fields = (ros2_fields, )
self.fields_1_to_2[ros1_fields] = ros2_fields
self.fields_2_to_1[ros2_fields] = ros1_fields
for ros2_field in ros2_fields:
if ros2_field.type.pkg_name and ros2_field.type.pkg_name != 'builtin_interfaces':
self.depends_on_ros2_messages.add(
Message(ros2_field.type.pkg_name, ros2_field.type.type))
if not isinstance(ros2_members, tuple):
ros2_members = (ros2_members, )
self.fields_1_to_2[ros1_fields] = ros2_members
self.fields_2_to_1[ros2_members] = ros1_fields
for ros2_member in ros2_members:
# If the member is not a namespaced type, skip.
if not isinstance(ros2_member.type, rosidl_parser.definition.NamespacedType):
continue
# If it is, then the type will have a namespaced name, e.g. (std_msgs, msg, String)
# If it is not of the standard ('<package name>', 'msg', '<type>'), skip it
if len(ros2_member.type.namespaces) != 2 or ros2_member.type.namespaces[1] != 'msg':
continue
# Extract the package name and message name
pkg_name = ros2_member.type.namespaces[0]
msg_name = ros2_member.type.name
if pkg_name != 'builtin_interfaces':
self.depends_on_ros2_messages.add(Message(pkg_name, msg_name))


def camel_case_to_lower_case_underscore(value):
Expand Down

0 comments on commit 01b55fa

Please sign in to comment.