diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..a3f9f81 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,60 @@ +# Copyright 2018 Proyectos y Sistemas de Mantenimiento SL (eProsima). +# +# 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. + + +# Set CMake version +cmake_minimum_required(VERSION 3.5) + + +# Set proyect name +project(micro_ros_agent) + + +# Find packages depencences +find_package(ament_cmake REQUIRED) +find_package(fastcdr REQUIRED CONFIG) +find_package(fastrtps REQUIRED CONFIG) +find_package(micrortps_agent REQUIRED CONFIG) +find_package(ament_cmake_python REQUIRED) + + +# Export dependencies to downstream packages +ament_export_dependencies(fastcdr) +ament_export_dependencies(fastrtps) +ament_export_dependencies(micrortps_agent) + + +# Install python files +ament_python_install_package(${PROJECT_NAME}) + + +# Register package resource in order to be called when a new msg package is generated +ament_index_register_resource("rosidl_typesupport_c") + + +# Install the package.xml file, and generate code for ``find_package`` so that other packages can get information about this package. +ament_package( + CONFIG_EXTRAS "micro_ros_agent-extras.cmake.in" +) + + +install( + PROGRAMS bin/micro_ros_agent + DESTINATION lib/micro_ros_agent +) + +install( + DIRECTORY cmake resource + DESTINATION share/${PROJECT_NAME} +) \ No newline at end of file diff --git a/bin/micro_ros_agent b/bin/micro_ros_agent new file mode 100644 index 0000000..a717221 --- /dev/null +++ b/bin/micro_ros_agent @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 + +import argparse +import os +import sys + +from rosidl_cmake import read_generator_arguments +from rosidl_parser import UnknownMessageType +from micro_ros_agent import generate_micro_ros_agent_xml_support + +def is_valid_file(parser, file_name): + if not os.path.exists(file_name): + parser.error("File does not exist: '{0}'".format(file_name)) + file_name_abs = os.path.abspath(file_name) + if not os.path.isfile(file_name_abs): + parser.error("Path exists but is not a file: '{0}'".format(file_name_abs)) + return file_name + + +def main(argv=sys.argv[1:]): + parser = argparse.ArgumentParser( + description='Generate xml files for micrortps agent.', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument( + '--generator-arguments-file', + required=True, + help='The location of the file containing the generator arguments') + args = parser.parse_args(argv) + + generator_args = read_generator_arguments(args.generator_arguments_file) + + try: + rc = generate_micro_ros_agent_xml_support(generator_args) + + return 0 + except UnknownMessageType as e: + print(str(e), file=sys.stderr) + return 1 + if rc: + return rc + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/cmake/micrortps_agent_xml_generator.cmake b/cmake/micrortps_agent_xml_generator.cmake new file mode 100644 index 0000000..6f269f0 --- /dev/null +++ b/cmake/micrortps_agent_xml_generator.cmake @@ -0,0 +1,69 @@ +# Copyright 2016-2018 Proyectos y Sistemas de Mantenimiento SL (eProsima). +# +# 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. + + +# list msg files +set(_ros_idl_files "") +foreach(_idl_file ${rosidl_generate_interfaces_IDL_FILES}) + get_filename_component(_extension "${_idl_file}" EXT) + # Skip .srv files + if(_extension STREQUAL ".msg") + list(APPEND _ros_idl_files "${_idl_file}") + endif() +endforeach() + + +# Set output dir +set(_output_path "${CMAKE_CURRENT_BINARY_DIR}/../micro_ros_agent/xml_gen") + + +# check if all templates exits +set(target_dependencies + "${micro_ros_agent_BIN}" + "${micro_ros_agent_GENERATOR_FILES}" + ${rosidl_generate_interfaces_IDL_FILES} + ${_dependency_files}) +foreach(dep ${target_dependencies}) + if(NOT EXISTS "${dep}") + message(FATAL_ERROR "Target dependency '${dep}' does not exist") + endif() +endforeach() + + +# generate script argument file +set(generator_arguments_file "${CMAKE_CURRENT_BINARY_DIR}/micro_ros_agent__arguments.json") +rosidl_write_generator_arguments( + "${generator_arguments_file}" + PACKAGE_NAME "${PROJECT_NAME}" + ROS_INTERFACE_FILES "${rosidl_generate_interfaces_IDL_FILES}" + ROS_INTERFACE_DEPENDENCIES "${_dependencies}" + OUTPUT_DIR "${_output_path}" + TEMPLATE_DIR "${micro_ros_agent_DEFAULT_PROFILES_DIR}" + TARGET_DEPENDENCIES ${target_dependencies} + ADDITIONAL_FILES ${_dds_idl_files} +) + + +# Execute python script +execute_process( + COMMAND ${PYTHON_EXECUTABLE} ${micro_ros_agent_BIN} + --generator-arguments-file "${generator_arguments_file}" + ) + + +#Install +install( + DIRECTORY "${_output_path}/" + DESTINATION "../micrortps_agent/bin" +) \ No newline at end of file diff --git a/micro_ros_agent-extras.cmake.in b/micro_ros_agent-extras.cmake.in new file mode 100644 index 0000000..aa4e047 --- /dev/null +++ b/micro_ros_agent-extras.cmake.in @@ -0,0 +1,14 @@ +find_package(ament_cmake_core QUIET REQUIRED) +ament_register_extension( + "rosidl_generate_interfaces" + "micro_ros_agent" + "micrortps_agent_xml_generator.cmake") + +set(micro_ros_agent_BIN "${micro_ros_agent_DIR}/../../../lib/micro_ros_agent/micro_ros_agent") +normalize_path(micro_ros_agent_BIN "${micro_ros_agent_BIN}") + +set(micro_ros_agent_GENERATOR_FILES "${micro_ros_agent_DIR}/../../../@PYTHON_INSTALL_DIR@/micro_ros_agent/__init__.py") +normalize_path(micro_ros_agent_GENERATOR_FILES "${micro_ros_agent_GENERATOR_FILES}") + +set(micro_ros_agent_DEFAULT_PROFILES_DIR "${micro_ros_agent_DIR}/../resource") +normalize_path(micro_ros_agent_DEFAULT_PROFILES_DIR "${micro_ros_agent_DEFAULT_PROFILES_DIR}") \ No newline at end of file diff --git a/micro_ros_agent/__init__.py b/micro_ros_agent/__init__.py new file mode 100644 index 0000000..34ad0b0 --- /dev/null +++ b/micro_ros_agent/__init__.py @@ -0,0 +1,241 @@ +# Copyright 2016-2018 Proyectos y Sistemas de Mantenimiento SL (eProsima). +# +# 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. + +import struct +import os +import sys + +if os.name == 'nt': + import win32con + import win32file + import pywintypes + __overlapped = pywintypes.OVERLAPPED() +elif os.name == 'posix': + import fcntl +else: + raise RuntimeError('PortaLocker only defined for nt and posix platforms') + +from rosidl_cmake import convert_camel_case_to_lower_case_underscore +from rosidl_cmake import expand_template +from rosidl_cmake import extract_message_types +from rosidl_cmake import get_newest_modification_time +from rosidl_parser import parse_message_file +from rosidl_parser import parse_service_file +from rosidl_parser import validate_field_types +from shutil import copyfile +from pathlib import Path + + +def generate_micro_ros_agent_xml_support(args): + pkg_name = args['package_name'] + known_msg_types = extract_message_types( + pkg_name, args['ros_interface_files'], args.get('ros_interface_dependencies', [])) + + functions = { + 'get_header_filename_from_msg_name': convert_camel_case_to_lower_case_underscore, + } + + + # Set file format + file_format = ".xml" + ros2_prefix = "rt/" + + + # Check destination dir + dest_dir = args['output_dir'] + if not os.path.exists(dest_dir): + os.makedirs(dest_dir) + + + # Check source dir + srcs_dir = os.path.join(dest_dir, "srcs") + if not os.path.exists(srcs_dir): + os.makedirs(srcs_dir) + + + # Copy all included xml files + for filename in os.listdir(args['template_dir']): + if filename.endswith(file_format): + template_src_path = os.path.join(args['template_dir'], filename) + template_dest_path = os.path.join(srcs_dir, filename) + if not os.path.isfile(template_dest_path): + copyfile(template_src_path, template_dest_path) + + + # Iterate throw all msgs/srvs + for idl_file in args['ros_interface_files']: + extension = os.path.splitext(idl_file)[1] + if extension == '.msg': + spec = parse_message_file(pkg_name, idl_file) + validate_field_types(spec, known_msg_types) + subfolder = os.path.basename(os.path.dirname(idl_file)) + + data = { + 'spec': spec, + 'pkg': spec.base_type.pkg_name, + 'msg': spec.msg_name, + 'type': spec.base_type.type, + 'subfolder': subfolder, + } + data.update(functions) + + + # Generate source file path + src_file = os.path.join(srcs_dir, "%s_%s_%s.xml" % (spec.base_type.pkg_name, subfolder, spec.msg_name)) + + + # Publisher + file_content = " \n" % (spec.base_type.pkg_name, subfolder, spec.msg_name) + file_content += " \n" % (spec.base_type.pkg_name, subfolder, spec.msg_name) + file_content += " NO_KEY\n" + file_content += " %s%s_%s_%s\n" % (ros2_prefix, spec.base_type.pkg_name, subfolder, spec.msg_name) + file_content += " %s::%s::dds_::%s_\n" % (spec.base_type.pkg_name, subfolder, spec.msg_name) + file_content += " \n" + file_content += " KEEP_LAST\n" + file_content += " 5\n" + file_content += " \n" + file_content += " \n" + file_content += " TRANSIENT_LOCAL\n" + file_content += " \n" + file_content += " \n" + file_content += " \n" + + + # Subscriber + file_content += " \n" % (spec.base_type.pkg_name, subfolder, spec.msg_name) + file_content += " \n" % (spec.base_type.pkg_name, subfolder, spec.msg_name) + file_content += " NO_KEY\n" + file_content += " %s%s_%s_%s\n" % (ros2_prefix, spec.base_type.pkg_name, subfolder, spec.msg_name) + file_content += " %s::%s::dds_::%s_\n" % (spec.base_type.pkg_name, subfolder, spec.msg_name) + file_content += " \n" + file_content += " KEEP_LAST\n" + file_content += " 5\n" + file_content += " \n" + file_content += " \n" + file_content += " TRANSIENT_LOCAL\n" + file_content += " \n" + file_content += " \n" + file_content += " \n" + + + # Topic + file_content += " \n" % (spec.base_type.pkg_name, subfolder, spec.msg_name) + file_content += " %s%s_%s_%s\n" % (ros2_prefix, spec.base_type.pkg_name, subfolder, spec.msg_name) + file_content += " %s::%s::dds_::%s_\n" % (spec.base_type.pkg_name, subfolder, spec.msg_name) + file_content += " \n" + + + # Write file content + fd1 = open(src_file, "w") + if os.name == 'nt': + # Lock + hfile1 = win32file._get_osfhandle(fd1.fileno()) + win32file.LockFileEx(hfile1, win32con.LOCKFILE_EXCLUSIVE_LOCK, 0, -0x10000, __overlapped) + + # Write + fd1.write(file_content) + + # Unlock + win32file.UnlockFileEx(hfile1, 0, -0x10000, __overlapped) + elif os.name == 'posix': + # Lock + fcntl.flock(fd1.fileno(), fcntl.LOCK_EX) + + # Write + fd1.write(file_content) + + # Unlock + fcntl.flock(fd1.fileno(), fcntl.LOCK_UN) + fd1.close() + + + # Open collect file + collec_file = os.path.join(args['output_dir'], "DEFAULT_FASTRTPS_PROFILES.xml") + fd2 = open(collec_file, "w") + if os.name == 'nt': + # Lock + hfile2 = win32file._get_osfhandle(fd2.fileno()) + win32file.LockFileEx(hfile2, win32con.LOCKFILE_EXCLUSIVE_LOCK, 0, -0x10000, __overlapped) + + # Generate head + fd2.write("\n") + + # Append all files contents + for filename in os.listdir(srcs_dir): + if filename.endswith(".xml"): + # Open + fd3 = open(os.path.join(srcs_dir, filename), "r+") + + # Lock + hfile3 = win32file._get_osfhandle(fd3.fileno()) + win32file.LockFileEx(hfile3, win32con.LOCKFILE_EXCLUSIVE_LOCK, 0, -0x10000, __overlapped) + + # Write + fd2.write(fd3.read()) + + # Unlock + win32file.UnlockFileEx(hfile3, 0, -0x10000, __overlapped) + fd3.close() + + # Generate tail + fd2.write("\n") + + # UnLock + win32file.UnlockFileEx(hfile2, 0, -0x10000, __overlapped) + elif os.name == 'posix': + # Lock + fcntl.flock(fd2.fileno(), fcntl.LOCK_EX) + + # Generate head + fd2.write("\n") + + # Append all files contents + for filename in os.listdir(srcs_dir): + if filename.endswith(".xml"): + # Open + fd3 = open(os.path.join(srcs_dir, filename), "r+") + + # Lock + fcntl.flock(fd3, fcntl.LOCK_EX) + + # Write + fd2.write(fd3.read()) + + # Unlock + fcntl.flock(fd3.fileno(), fcntl.LOCK_UN) + + # Close + fd3.close() + + # Generate tail + fd2.write("\n") + + # UnLock + fcntl.flock(fd2.fileno(), fcntl.LOCK_UN) + # Close file + fd2.close() + + + #elif extension == '.srv': + + #data = {'spec': spec} + #data.update(functions) + + #if not os.path.exists(args['output_dir']): + # os.makedirs(args['output_dir']) + + #f = open(os.path.join(args['output_dir'], 'demofile.txt'), 'w+') + #f.write("%s\n" % spec) + #f.close() + return 0 \ No newline at end of file diff --git a/package.xml b/package.xml new file mode 100644 index 0000000..461ed8a --- /dev/null +++ b/package.xml @@ -0,0 +1,31 @@ + + + + micro_ros_agent + 0.0.1 + DDS-XCRE agent implementation + Javier Moreno + Apache License 2.0 + + ament_cmake + ament_cmake + + rosidl_parser + + fastcdr + fastrtps + micrortps_agent + + ament_lint_auto + ament_lint_common + + rosidl_typesupport_c_packages + + + ament_cmake + + + + + + diff --git a/resource/StaticValues.xml b/resource/StaticValues.xml new file mode 100644 index 0000000..7444a10 --- /dev/null +++ b/resource/StaticValues.xml @@ -0,0 +1,39 @@ + + + + + INFINITE + + 0 + + DataReader_participant_subscriber + + + + + WITH_KEY + Square + ShapeType + + KEEP_LAST + 5 + + + TRANSIENT_LOCAL + + + + + + WITH_KEY + Square + ShapeType + + KEEP_LAST + 5 + + + TRANSIENT_LOCAL + + +