├── pytrollercli ├── README.md ├── pytrollercli │ ├── __init__.py │ ├── api │ │ ├── __init__.py │ │ └── create.py │ ├── verb │ │ ├── __init__.py │ │ └── create.py │ ├── command │ │ ├── __init__.py │ │ └── pytroller.py │ └── resource │ │ ├── __init__.py │ │ ├── package │ │ ├── controller_plugin.xml.em │ │ ├── package.xml.em │ │ └── CMakeLists.txt.em │ │ ├── test │ │ ├── test_params.yaml.em │ │ ├── test_load_pytroller.cpp.em │ │ ├── test_pytroller.hpp.em │ │ └── test_pytroller.cpp.em │ │ ├── src │ │ ├── pytroller_parameters.yaml.em │ │ ├── pytroller_logic.pyx.em │ │ └── pytroller.cpp.em │ │ ├── script │ │ └── pytroller_logic_impl.py.em │ │ └── include │ │ ├── visibility_control.h.em │ │ └── pytroller.hpp.em ├── resource │ └── pytrollercli ├── CHANGELOG.rst ├── package.xml └── setup.py ├── pytroller_tools ├── pytroller_tools │ ├── __init__.py │ └── generate_pxd.py ├── resource │ └── pytroller_tools ├── setup.cfg ├── CHANGELOG.rst ├── package.xml ├── test │ ├── test_pep257.py │ ├── test_flake8.py │ └── test_copyright.py ├── setup.py └── LICENSE ├── .gitignore ├── pytroller ├── CMakeLists.txt ├── CHANGELOG.rst └── package.xml ├── .github ├── dependabot.yml └── workflows │ ├── ci-format.yml │ └── ci.yml ├── CONTRIBUTING.md ├── README.md ├── .pre-commit-config.yaml └── LICENSE /pytrollercli/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pytrollercli/pytrollercli/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pytrollercli/resource/pytrollercli: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pytroller_tools/pytroller_tools/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pytroller_tools/resource/pytroller_tools: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pytrollercli/pytrollercli/api/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pytrollercli/pytrollercli/verb/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pytrollercli/pytrollercli/command/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pytrollercli/pytrollercli/resource/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | build/ 3 | log/ 4 | install/ 5 | __pycache__/ 6 | -------------------------------------------------------------------------------- /pytroller_tools/setup.cfg: -------------------------------------------------------------------------------- 1 | [develop] 2 | script_dir=$base/lib/pytroller_tools 3 | [install] 4 | install_scripts=$base/lib/pytroller_tools 5 | -------------------------------------------------------------------------------- /pytroller/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8) 2 | project(pytroller NONE) 3 | find_package(ament_cmake REQUIRED) 4 | ament_package() 5 | -------------------------------------------------------------------------------- /pytroller/CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 2 | Changelog for package pytroller 3 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 4 | 5 | 0.0.1 (2023-09-20) 6 | ------------------- 7 | -------------------------------------------------------------------------------- /pytrollercli/CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 2 | Changelog for package pytrollercli 3 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 4 | 5 | 0.0.1 (2023-09-20) 6 | ------------------- 7 | -------------------------------------------------------------------------------- /pytroller_tools/CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 2 | Changelog for package pytroller_tools 3 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 4 | 5 | 0.0.1 (2023-09-20) 6 | ------------------- 7 | -------------------------------------------------------------------------------- /pytrollercli/pytrollercli/resource/package/controller_plugin.xml.em: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | Python controller for ros2_control 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /pytrollercli/pytrollercli/resource/test/test_params.yaml.em: -------------------------------------------------------------------------------- 1 | load_@(pytroller_name): 2 | ros__parameters: 3 | interface_full_names: 4 | - "joint1/position" 5 | - "joint2/position" 6 | command_topic_name: "~/commands" 7 | command_topic_type: "std_msgs/msg/Float64MultiArray" 8 | 9 | test_@(pytroller_name): 10 | ros__parameters: 11 | interface_full_names: 12 | - "joint1/position" 13 | - "joint2/position" 14 | command_topic_name: "~/commands" 15 | command_topic_type: "std_msgs/msg/Float64MultiArray" 16 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "github-actions" 9 | # Workflow files stored in the 10 | # default location of `.github/workflows` 11 | directory: "/" 12 | schedule: 13 | interval: "weekly" 14 | -------------------------------------------------------------------------------- /pytrollercli/pytrollercli/resource/src/pytroller_parameters.yaml.em: -------------------------------------------------------------------------------- 1 | @(pytroller_name): 2 | interface_full_names: { 3 | type: string_array, 4 | default_value: [], 5 | description: "Name (WARNING, full names, e.g., 'joint_1/effort') of the interface(s) to command", 6 | } 7 | command_topic_name: { 8 | type: string, 9 | default_value: "", 10 | description: "Optional. Name of the subscribed command topic" 11 | } 12 | command_topic_type: { 13 | type: string, 14 | default_value: "", 15 | description: "Optional. Type of the subscribed command topic" 16 | } 17 | -------------------------------------------------------------------------------- /.github/workflows/ci-format.yml: -------------------------------------------------------------------------------- 1 | # This is a format job. Pre-commit has a first-party GitHub action, so we use 2 | # that: https://github.com/pre-commit/action 3 | 4 | name: Format 5 | 6 | on: 7 | workflow_dispatch: 8 | pull_request: 9 | 10 | jobs: 11 | pre-commit: 12 | name: Format 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: actions/setup-python@v5.0.0 17 | with: 18 | python-version: '3.10' 19 | - name: Install system hooks 20 | run: sudo apt install -qq clang-format-14 cppcheck 21 | - uses: pre-commit/action@v3.0.1 22 | with: 23 | extra_args: --all-files --hook-stage manual 24 | -------------------------------------------------------------------------------- /pytroller/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | pytroller 5 | 0.1.0 6 | Meta-package aggregating the pytroller packages and documentation 7 | Maciej Bednarczyk 8 | Apache License 2.0 9 | 10 | ament_cmake 11 | pytrollercli 12 | pytroller_tools 13 | 14 | 15 | ament_cmake 16 | 17 | 18 | -------------------------------------------------------------------------------- /pytroller_tools/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | pytroller_tools 5 | 0.1.0 6 | TODO: Package description 7 | mcbed 8 | Apache-2.0 9 | 10 | ament_copyright 11 | ament_flake8 12 | ament_pep257 13 | python3-pytest 14 | 15 | 16 | ament_python 17 | 18 | 19 | -------------------------------------------------------------------------------- /pytrollercli/pytrollercli/resource/script/pytroller_logic_impl.py.em: -------------------------------------------------------------------------------- 1 | # Copyright 2023 ICube-Robotics 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | def pytroller_logic_impl(period, states, commands, msg, params): 16 | 17 | return commands 18 | -------------------------------------------------------------------------------- /pytroller_tools/test/test_pep257.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from ament_pep257.main import main 16 | import pytest 17 | 18 | 19 | @pytest.mark.linter 20 | @pytest.mark.pep257 21 | def test_pep257(): 22 | rc = main(argv=['.', 'test']) 23 | assert rc == 0, 'Found code style errors / warnings' 24 | -------------------------------------------------------------------------------- /pytrollercli/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | pytrollercli 5 | 0.1.0 6 | 7 | The Pytroller command line tools for ROS2 Control. 8 | 9 | Maciej Bednarczyk 10 | Apache License 2.0 11 | 12 | rcl_interfaces 13 | rclpy 14 | ros2cli 15 | rosidl_runtime_py 16 | 17 | ament_copyright 18 | ament_flake8 19 | ament_pep257 20 | ament_xmllint 21 | 22 | 23 | ament_python 24 | 25 | 26 | -------------------------------------------------------------------------------- /pytroller_tools/test/test_flake8.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from ament_flake8.main import main_with_errors 16 | import pytest 17 | 18 | 19 | @pytest.mark.flake8 20 | @pytest.mark.linter 21 | def test_flake8(): 22 | rc, errors = main_with_errors(argv=[]) 23 | assert rc == 0, \ 24 | 'Found %d code style errors / warnings:\n' % len(errors) + \ 25 | '\n'.join(errors) 26 | -------------------------------------------------------------------------------- /pytrollercli/pytrollercli/resource/package/package.xml.em: -------------------------------------------------------------------------------- 1 | 2 | 3 | @pytroller_name 4 | 0.0.1 5 | Python controller for ros2_control. 6 | @maintainer_name 7 | 8 | Apache License 2.0 9 | 10 | ament_cmake 11 | 12 | controller_interface 13 | generate_parameter_library 14 | hardware_interface 15 | pluginlib 16 | rclcpp 17 | rclcpp_lifecycle 18 | realtime_tools 19 | cython3 20 | 21 | pytroller_tools 22 | 23 | ament_cmake_gmock 24 | controller_manager 25 | ros2_control_test_assets 26 | 27 | 28 | ament_cmake 29 | 30 | 31 | -------------------------------------------------------------------------------- /pytroller_tools/test/test_copyright.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 Open Source Robotics Foundation, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from ament_copyright.main import main 16 | import pytest 17 | 18 | 19 | # Remove the `skip` decorator once the source file(s) have a copyright header 20 | @pytest.mark.skip(reason='No copyright header has been placed in the generated source file.') 21 | @pytest.mark.copyright 22 | @pytest.mark.linter 23 | def test_copyright(): 24 | rc = main(argv=['.', 'test']) 25 | assert rc == 0, 'Found errors' 26 | -------------------------------------------------------------------------------- /pytrollercli/pytrollercli/verb/create.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 ICube-Robotics 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import os 16 | 17 | from ros2cli.verb import VerbExtension 18 | 19 | from pytrollercli.api.create import create_pytroller 20 | 21 | 22 | class CreateVerb(VerbExtension): 23 | """Create a new pytroller for ros2_control.""" 24 | 25 | def add_arguments(self, parser, cli_name): 26 | parser.add_argument( 27 | 'pytroller_name', 28 | default='pytroller', 29 | help='The pytroller name') 30 | parser.add_argument( 31 | '--destination-directory', 32 | default=os.curdir, 33 | help='Directory where to create the package directory') 34 | 35 | def main(self, *, args): 36 | create_pytroller( 37 | args.pytroller_name, 38 | args.destination_directory, 39 | ) 40 | -------------------------------------------------------------------------------- /pytrollercli/pytrollercli/command/pytroller.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 ICube-Robotics 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | from ros2cli.command import add_subparsers_on_demand 17 | from ros2cli.command import CommandExtension 18 | 19 | 20 | class PytrollerCommand(CommandExtension): 21 | """Various control related sub-commands.""" 22 | 23 | def add_arguments(self, parser, cli_name): 24 | self._subparser = parser 25 | # get verb extensions and let them add their arguments 26 | add_subparsers_on_demand(parser, cli_name, "_verb", "pytrollercli.verb", required=False) 27 | 28 | def main(self, *, parser, args): 29 | if not hasattr(args, "_verb"): 30 | # in case no verb was passed 31 | self._subparser.print_help() 32 | return 0 33 | 34 | extension = getattr(args, "_verb") 35 | 36 | # call the verb's main method 37 | return extension.main(args=args) 38 | -------------------------------------------------------------------------------- /pytroller_tools/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Copyright 2023 ICube-Robotics 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | from setuptools import find_packages, setup 18 | 19 | package_name = 'pytroller_tools' 20 | 21 | setup( 22 | name=package_name, 23 | version='0.0.1', 24 | packages=find_packages(exclude=['test']), 25 | data_files=[ 26 | ('share/ament_index/resource_index/packages', 27 | ['resource/' + package_name]), 28 | ('share/' + package_name, ['package.xml']), 29 | ], 30 | install_requires=['setuptools'], 31 | zip_safe=True, 32 | maintainer='Maciej Bednarczyk', 33 | maintainer_email='mcbed.robotics@gmail.com', 34 | url="https://github.com/ICube-Robotics/pytroller", 35 | description='Tools for Pytrollers: Python controllers for ros2_control', 36 | license='Apache-2.0', 37 | tests_require=['pytest'], 38 | entry_points={ 39 | 'console_scripts': [ 40 | "generate_pxd = pytroller_tools.generate_pxd:main", 41 | ], 42 | }, 43 | ) 44 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | jobs: 9 | CI: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Prepare 13 | run: | 14 | mkdir -p ${{github.workspace}}/src 15 | - uses: actions/checkout@v4 16 | with: 17 | path: src/pytroller 18 | 19 | - name: Build 20 | uses: addnab/docker-run-action@v3 21 | with: 22 | image: ros:humble 23 | options: -v ${{github.workspace}}/:/ros/ 24 | run: | 25 | cd /ros 26 | apt update && apt upgrade 27 | sudo apt install python3-pip -y 28 | pip3 install cython 29 | . /opt/ros/humble/setup.sh 30 | rosdep install --ignore-src --from-paths . -y -r 31 | colcon build --cmake-args -DCMAKE_BUILD_TYPE=Release 32 | . install/setup.sh 33 | ros2 pytroller create my_pytroller 34 | rosdep install --ignore-src --from-paths . -y -r 35 | colcon build --cmake-args -DCMAKE_BUILD_TYPE=Release 36 | 37 | - name: Test 38 | uses: addnab/docker-run-action@v3 39 | with: 40 | image: ros:humble 41 | options: -v ${{github.workspace}}/:/ros/ 42 | run: | 43 | cd /ros 44 | apt update && apt upgrade 45 | sudo apt install python3-pip -y 46 | pip3 install cython 47 | . /opt/ros/humble/setup.sh 48 | rosdep install --ignore-src --from-paths . -y -r 49 | colcon build --cmake-args -DCMAKE_BUILD_TYPE=Release 50 | . install/setup.sh 51 | ros2 pytroller create my_pytroller 52 | rosdep install --ignore-src --from-paths . -y -r 53 | colcon build --cmake-args -DCMAKE_BUILD_TYPE=Release 54 | colcon test 55 | colcon test-result 56 | -------------------------------------------------------------------------------- /pytrollercli/pytrollercli/resource/test/test_load_pytroller.cpp.em: -------------------------------------------------------------------------------- 1 | // Copyright 2023 ICube-Robotics 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | #include 17 | 18 | #include "controller_manager/controller_manager.hpp" 19 | #include "hardware_interface/resource_manager.hpp" 20 | #include "rclcpp/executors/single_threaded_executor.hpp" 21 | #include "ros2_control_test_assets/descriptions.hpp" 22 | 23 | TEST(TestLoad@(pytroller_class), load_plugin) 24 | { 25 | pluginlib::ClassLoader plugin_loader{ 26 | "controller_interface", "controller_interface::ControllerInterface"}; 27 | ASSERT_NO_THROW( 28 | plugin_loader.createSharedInstance("@(pytroller_name)/@(pytroller_class)") 29 | ); 30 | } 31 | 32 | TEST(TestLoad@(pytroller_class), load_controller) 33 | { 34 | std::shared_ptr executor = 35 | std::make_shared(); 36 | 37 | controller_manager::ControllerManager cm( 38 | std::make_unique( 39 | ros2_control_test_assets::minimal_robot_urdf), 40 | executor, "test_controller_manager"); 41 | 42 | ASSERT_NE( 43 | cm.load_controller( 44 | "load_@(pytroller_name)", 45 | "@(pytroller_name)/@(pytroller_class)" 46 | ), 47 | nullptr 48 | ); 49 | } 50 | 51 | int main(int argc, char ** argv) 52 | { 53 | ::testing::InitGoogleTest(&argc, argv); 54 | rclcpp::init(argc, argv); 55 | int result = RUN_ALL_TESTS(); 56 | rclcpp::shutdown(); 57 | return result; 58 | } 59 | -------------------------------------------------------------------------------- /pytrollercli/pytrollercli/resource/src/pytroller_logic.pyx.em: -------------------------------------------------------------------------------- 1 | # Copyright 2023 ICube-Robotics 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # distutils: language = c++ 16 | 17 | from libcpp.unordered_map cimport unordered_map 18 | from libcpp.vector cimport vector 19 | from libcpp.string cimport string 20 | 21 | ########################################################################################################################################################## 22 | 23 | include "../script/@(pytroller_name)_logic_impl.py" 24 | 25 | include "@(pytroller_name)_parameters.pxd" 26 | 27 | ########################################################################################################################################################## 28 | 29 | import importlib 30 | from rclpy.serialization import deserialize_message 31 | 32 | cdef public int @(pytroller_name)_logic( 33 | double period, 34 | unordered_map[string, double] states, 35 | unordered_map[string, double] & commands, 36 | vector[int] & msg, 37 | Params param 38 | ): 39 | try: 40 | command_message = None 41 | # Read command msg if applicable (i.e., there is one...) 42 | if (len(msg) > 0): 43 | mt = param.command_topic_type.decode("utf-8").split('/') 44 | messagetype = getattr(importlib.import_module('.'.join(mt[:2])), mt[-1]) 45 | command_message = deserialize_message(bytes(msg), type(messagetype())) 46 | # Either way, call python logic 47 | (&commands)[0] = pytroller_logic_impl(period, states, commands, command_message, param) 48 | except Exception as error: 49 | print("An exception occurred:", error) 50 | return -1 51 | return 0 52 | -------------------------------------------------------------------------------- /pytrollercli/setup.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 ICube-Robotics 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from setuptools import find_packages 16 | from setuptools import setup 17 | 18 | package_name = "pytrollercli" 19 | 20 | setup( 21 | name=package_name, 22 | version="0.0.1", 23 | packages=find_packages(exclude=["test"]), 24 | data_files=[ 25 | ("share/" + package_name, ["package.xml"]), 26 | ("share/ament_index/resource_index/packages", ["resource/" + package_name]), 27 | ], 28 | install_requires=["ros2cli"], 29 | zip_safe=True, 30 | author="Maciej Bednarczyk", 31 | author_email="mcbed.robotics@gmail.com", 32 | maintainer="Maciej Bednarczyk", 33 | maintainer_email="mcbed.robotics@gmail.com", 34 | url="https://github.com/mcbed/pytroller", 35 | keywords=[], 36 | classifiers=[ 37 | "Environment :: Console", 38 | "Intended Audience :: Developers", 39 | "License :: OSI Approved :: Apache Software License", 40 | "Programming Language :: Python", 41 | ], 42 | description="Pytroller command interface.", 43 | long_description="""\ 44 | Pytroller command interface.""", 45 | license="Apache License, Version 2.0", 46 | tests_require=["pytest"], 47 | entry_points={ 48 | "ros2cli.command": [ 49 | "pytroller = pytrollercli.command.pytroller:PytrollerCommand", 50 | ], 51 | "pytrollercli.verb": [ 52 | "create = pytrollercli.verb.create:CreateVerb", 53 | 54 | ], 55 | }, 56 | package_data={ 57 | 'pytrollercli': [ 58 | 'resource/**/*', 59 | ], 60 | }, 61 | ) 62 | -------------------------------------------------------------------------------- /pytrollercli/pytrollercli/resource/include/visibility_control.h.em: -------------------------------------------------------------------------------- 1 | // Copyright 2020 PAL Robotics S.L. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* This header must be included by all rclcpp headers which declare symbols 16 | * which are defined in the rclcpp library. When not building the rclcpp 17 | * library, i.e. when using the headers in other package's code, the contents 18 | * of this header change the visibility of certain symbols which the rclcpp 19 | * library cannot have, but the consuming code must have inorder to link. 20 | */ 21 | 22 | #ifndef @(pytroller_name.upper())__VISIBILITY_CONTROL_H_ 23 | #define @(pytroller_name.upper())__VISIBILITY_CONTROL_H_ 24 | 25 | // This logic was borrowed (then namespaced) from the examples on the gcc wiki: 26 | // https://gcc.gnu.org/wiki/Visibility 27 | 28 | #if defined _WIN32 || defined __CYGWIN__ 29 | #ifdef __GNUC__ 30 | #define @(pytroller_name.upper())_EXPORT __attribute__((dllexport)) 31 | #define @(pytroller_name.upper())_IMPORT __attribute__((dllimport)) 32 | #else 33 | #define @(pytroller_name.upper())_EXPORT __declspec(dllexport) 34 | #define @(pytroller_name.upper())_IMPORT __declspec(dllimport) 35 | #endif 36 | #ifdef @(pytroller_name.upper())_BUILDING_DLL 37 | #define @(pytroller_name.upper())_PUBLIC @(pytroller_name.upper())_EXPORT 38 | #else 39 | #define @(pytroller_name.upper())_PUBLIC @(pytroller_name.upper())_IMPORT 40 | #endif 41 | #define @(pytroller_name.upper())_PUBLIC_TYPE @(pytroller_name.upper())_PUBLIC 42 | #define @(pytroller_name.upper())_LOCAL 43 | #else 44 | #define @(pytroller_name.upper())_EXPORT __attribute__((visibility("default"))) 45 | #define @(pytroller_name.upper())_IMPORT 46 | #if __GNUC__ >= 4 47 | #define @(pytroller_name.upper())_PUBLIC __attribute__((visibility("default"))) 48 | #define @(pytroller_name.upper())_LOCAL __attribute__((visibility("hidden"))) 49 | #else 50 | #define @(pytroller_name.upper())_PUBLIC 51 | #define @(pytroller_name.upper())_LOCAL 52 | #endif 53 | #define @(pytroller_name.upper())_PUBLIC_TYPE 54 | #endif 55 | 56 | #endif // @(pytroller_name.upper())__VISIBILITY_CONTROL_H_ 57 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | Thank you for your interest in contributing to `pytroller`. Whether it's a bug report, new feature, correction, or additional documentation, we greatly value feedback and contributions from the community. 3 | 4 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary information to effectively respond to your bug report or contribution. 5 | 6 | 7 | ## Reporting Bugs/Feature Requests 8 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 9 | 10 | When filing an issue, please check [existing open][issues], or [recently closed][closed-issues], issues to make sure somebody else hasn't already reported the issue. 11 | 12 | Please try to include as much information as you can. Details like these are incredibly useful: 13 | 14 | * A reproducible test case or series of steps 15 | * The version of our code being used 16 | * Any modifications you've made relevant to the bug 17 | * Anything unusual about your environment or deployment 18 | 19 | 20 | ## Contributing via Pull Requests 21 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 22 | 23 | 1. You are working against the latest source on the *main* branch. 24 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 25 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 26 | 27 | To send us a pull request, please: 28 | 29 | 1. Fork the repository. 30 | 2. Modify the source; please focus on the specific change you are contributing. 31 | If you also reformat all the code, it will be hard for us to focus on your change. 32 | 3. Ensure local tests pass. 33 | 4. Commit to your fork using clear commit messages. 34 | 5. Send a pull request, answering any default questions in the pull request interface. 35 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 36 | 37 | GitHub provides additional documentation on [forking a repository](https://help.github.com/articles/fork-a-repo/) and [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 38 | 39 | 40 | ## Licensing 41 | Any contribution that you make to this repository will be under the Apache 2 License, as dictated by that [license](http://www.apache.org/licenses/LICENSE-2.0.html): 42 | 43 | ~~~ 44 | 5. Submission of Contributions. Unless You explicitly state otherwise, 45 | any Contribution intentionally submitted for inclusion in the Work 46 | by You to the Licensor shall be under the terms and conditions of 47 | this License, without any additional terms or conditions. 48 | Notwithstanding the above, nothing herein shall supersede or modify 49 | the terms of any separate license agreement you may have executed 50 | with Licensor regarding such Contributions. 51 | ~~~ 52 | -------------------------------------------------------------------------------- /pytrollercli/pytrollercli/resource/test/test_pytroller.hpp.em: -------------------------------------------------------------------------------- 1 | // Copyright 2023 ICube-Robotics 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // author: Maciej Bednarczyk 16 | 17 | #ifndef TEST_@(pytroller_name.upper())_HPP_ 18 | #define TEST_@(pytroller_name.upper())_HPP_ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include "gmock/gmock.h" 26 | 27 | #include "hardware_interface/handle.hpp" 28 | #include "hardware_interface/types/hardware_interface_type_values.hpp" 29 | #include "@(pytroller_name)/@(pytroller_name).hpp" 30 | 31 | using hardware_interface::CommandInterface; 32 | using hardware_interface::StateInterface; 33 | 34 | // subclassing and friending so we can access member variables 35 | class Friend@(pytroller_class) : public @(pytroller_name)::@(pytroller_class) 36 | { 37 | FRIEND_TEST(Test@(pytroller_class), CommandSuccessTest); 38 | FRIEND_TEST(Test@(pytroller_class), WrongCommandCheckTest); 39 | FRIEND_TEST(Test@(pytroller_class), CommandSuccessTestWithConstraints); 40 | }; 41 | 42 | class Test@(pytroller_class) : public ::testing::Test 43 | { 44 | public: 45 | static void SetUpTestCase(); 46 | static void TearDownTestCase(); 47 | 48 | void SetUp(); 49 | void TearDown(); 50 | 51 | void SetUpController(); 52 | void SetUpHandles(); 53 | 54 | protected: 55 | std::unique_ptr controller_; 56 | 57 | const std::vector joint_names_ = {"joint1", "joint2"}; 58 | // dummy joint state values used for tests 59 | std::vector joint_commands_ = { 60 | std::numeric_limits::quiet_NaN(), 61 | std::numeric_limits::quiet_NaN() 62 | }; 63 | std::vector joint_states_position_ = {0.0, 0.0}; 64 | std::vector joint_states_velocity_ = {0.0, 0.0}; 65 | 66 | CommandInterface joint1_ci_{joint_names_[0], hardware_interface::HW_IF_POSITION, &joint_commands_[0]}; 67 | CommandInterface joint2_ci_{joint_names_[1], hardware_interface::HW_IF_POSITION, &joint_commands_[1]}; 68 | StateInterface joint1_sip_{joint_names_[0], hardware_interface::HW_IF_POSITION, &joint_states_position_[0]}; 69 | StateInterface joint2_sip_{joint_names_[1], hardware_interface::HW_IF_POSITION, &joint_states_position_[1]}; 70 | StateInterface joint1_siv_{joint_names_[0], hardware_interface::HW_IF_VELOCITY, &joint_states_velocity_[0]}; 71 | StateInterface joint2_siv_{joint_names_[1], hardware_interface::HW_IF_VELOCITY, &joint_states_velocity_[1]}; 72 | }; 73 | 74 | #endif // TEST_MPI_CONTROLLER_HPP_ 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pytroller 2 | [![Licence](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 3 | [![Build](https://github.com/ICube-Robotics/pytroller/actions/workflows/ci.yml/badge.svg)](https://github.com/ICube-Robotics/pytroller/actions/workflows/ci.yml) 4 | 5 | Python controller generator for `ros2_control`. 6 | 7 | ## Usage 8 | 9 | Generate the controller package using : 10 | ```shell 11 | $ ros2 pytroller create my_pytroller --destination-directory controllers 12 | ``` 13 | 14 | The raw Python controller logic script `my_pytroller_logic_impl.py` to be implemented is located the `script` directory inside the newly created controller package. 15 | 16 | This Python script containing the definition of the `pytroller_logic_impl` function that takes as input 17 | - `state` : type `dict` {`joint/interface`, value} : current joint states on all available interfaces 18 | - `commands` : type `dict` {`joint/interface`, value} : commands to be assigned to joint command interfaces 19 | - `msg` : command message from subscriber 20 | - `params` : type `dict` {`parameter`, value} : node parameters 21 | 22 | For example : 23 | 24 | ```python 25 | # my_pytroller/script/my_pytroller_logic_impl.py 26 | 27 | from math import cos, sin 28 | 29 | def pytroller_logic_impl(period, states, commands, msg, params): 30 | 31 | commands['joint1/effort'.encode('ascii')] = msg.data[0] 32 | commands['joint2/effort'.encode('ascii')] = msg.data[1] 33 | 34 | return commands 35 | ``` 36 | 37 | Install all dependencies and build the controller using 38 | ```shell 39 | $ rosdep install --ignore-src --from-paths . -y -r 40 | $ colcon build 41 | ``` 42 | 43 | ## Parameters 44 | Pytrollers use parameters generated by the [generate_parameter_library](https://github.com/PickNikRobotics/generate_parameter_library) package and are defined in the `my_pytroller_parameters.yaml` file : 45 | 46 | ```yaml 47 | my_pytroller: 48 | interface_full_names: { 49 | type: string_array, 50 | default_value: [], 51 | description: "Full names of the interface(s) to command", 52 | } 53 | command_topic_name: { 54 | type: string, 55 | default_value: "" # "~/commands", 56 | description: "Name of the subscribed command topic" 57 | } 58 | command_topic_type: { 59 | type: string, 60 | default_value: "" # "std_msgs/msg/Float64MultiArray", 61 | description: "Type of the subscribed command topic" 62 | } 63 | ``` 64 | Additional parameters can be added to this configuration file. To do so, refer to the [generate_parameter_library](https://github.com/PickNikRobotics/generate_parameter_library) package documentation. 65 | 66 | The values of the defined parameters are set in the controller configuration file used for ros2-control. 67 | 68 | ## Contacts ## 69 | ![icube](https://icube.unistra.fr/fileadmin/templates/DUN/icube/images/logo.png) 70 | 71 | [ICube Laboratory](https://icube.unistra.fr), [University of Strasbourg](https://www.unistra.fr/), France 72 | 73 | __Maciej Bednarczyk:__ [mcbed.robotics@gmail.com](mailto:mcbed.robotics@gmail.com), @github: [mcbed](https://github.com/mcbed) 74 | -------------------------------------------------------------------------------- /pytrollercli/pytrollercli/resource/include/pytroller.hpp.em: -------------------------------------------------------------------------------- 1 | // Copyright 2023 ICube-Robotics 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // author: Maciej Bednarczyk 16 | 17 | #ifndef @(pytroller_name.upper())__@(pytroller_name.upper())_HPP_ 18 | #define @(pytroller_name.upper())__@(pytroller_name.upper())_HPP_ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include "controller_interface/controller_interface.hpp" 26 | #include "@(pytroller_name)/visibility_control.h" 27 | #include "rclcpp/serialized_message.hpp" 28 | #include "rclcpp_lifecycle/node_interfaces/lifecycle_node_interface.hpp" 29 | #include "rclcpp_lifecycle/state.hpp" 30 | #include "realtime_tools/realtime_buffer.h" 31 | #include "@(pytroller_name)_parameters.hpp" 32 | 33 | namespace @(pytroller_name) 34 | { 35 | /** 36 | * \brief Python controller for a set of joints and interfaces. 37 | * 38 | * Subscribes to: 39 | * - \b commands : The commands to apply. 40 | */ 41 | class @(pytroller_class) : public controller_interface::ControllerInterface 42 | { 43 | public: 44 | @(pytroller_name.upper())_PUBLIC 45 | @(pytroller_class)(); 46 | 47 | @(pytroller_name.upper())_PUBLIC 48 | ~@(pytroller_class)() = default; 49 | 50 | @(pytroller_name.upper())_PUBLIC 51 | controller_interface::InterfaceConfiguration command_interface_configuration() const override; 52 | 53 | @(pytroller_name.upper())_PUBLIC 54 | controller_interface::InterfaceConfiguration state_interface_configuration() const override; 55 | 56 | @(pytroller_name.upper())_PUBLIC 57 | controller_interface::CallbackReturn on_init() override; 58 | 59 | @(pytroller_name.upper())_PUBLIC 60 | controller_interface::CallbackReturn on_configure( 61 | const rclcpp_lifecycle::State & previous_state) override; 62 | 63 | @(pytroller_name.upper())_PUBLIC 64 | controller_interface::CallbackReturn on_activate( 65 | const rclcpp_lifecycle::State & previous_state) override; 66 | 67 | @(pytroller_name.upper())_PUBLIC 68 | controller_interface::CallbackReturn on_deactivate( 69 | const rclcpp_lifecycle::State & previous_state) override; 70 | 71 | @(pytroller_name.upper())_PUBLIC 72 | controller_interface::return_type update( 73 | const rclcpp::Time & time, const rclcpp::Duration & period) override; 74 | 75 | protected: 76 | void declare_parameters(); 77 | controller_interface::CallbackReturn read_parameters(); 78 | 79 | std::shared_ptr param_listener_; 80 | Params params_; 81 | 82 | std::vector command_interface_types_; 83 | std::unordered_map states_; 84 | std::unordered_map commands_; 85 | 86 | realtime_tools::RealtimeBuffer> rt_command_ptr_; 87 | rclcpp::GenericSubscription::SharedPtr command_subscriber_; 88 | }; 89 | 90 | } // namespace @(pytroller_name) 91 | 92 | #endif // @(pytroller_name.upper())__FORWARD_CONTROLLERS_BASE_HPP_ 93 | -------------------------------------------------------------------------------- /pytroller_tools/pytroller_tools/generate_pxd.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Copyright 2023 ICube-Robotics 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import argparse 18 | import sys 19 | 20 | 21 | def extract_params_struct(content): 22 | struct_name = "struct Params {" 23 | struct2_name = "struct StackParams {" 24 | struct_start = content.find(struct_name) 25 | struct_end = content.find(struct2_name) 26 | return_struct = '' 27 | if struct_start != -1: 28 | struct_content = content[struct_start:struct_end] 29 | for line in struct_content.splitlines()[1:-4]: 30 | match line.strip().split(' = ')[0].split(' ')[0]: 31 | case 'bool': 32 | return_struct += 'bool' 33 | case 'double': 34 | return_struct += 'float' 35 | case 'int64_t': 36 | return_struct += 'int' 37 | case 'std::string': 38 | return_struct += 'string' 39 | case 'std::vector': 40 | return_struct += 'vector[bool]' 41 | case 'std::vector': 42 | return_struct += 'vector[float]' 43 | case 'std::vector': 44 | return_struct += 'vector[int64_t]' 45 | case 'std::vector': 46 | return_struct += 'vector[string]' 47 | 48 | return_struct += ' ' + line.strip().split(' = ')[0].split(' ')[1] + '\n' 49 | return return_struct 50 | 51 | return None 52 | 53 | 54 | def read_cpp_file(file_path): 55 | with open(file_path) as file: 56 | return file.read() 57 | 58 | 59 | def run(output_pxd_file, input_header_file): 60 | cpp_content = read_cpp_file(input_header_file) 61 | 62 | params_struct_content = extract_params_struct(cpp_content) 63 | namespace_start = cpp_content.find("namespace") 64 | namespace_end = cpp_content.find("{", namespace_start) 65 | namespace = cpp_content[namespace_start+10:namespace_end-1] 66 | 67 | if params_struct_content: 68 | pxd_file = '# distutils: language = c++\n\n' 69 | pxd_file += 'from libcpp.vector cimport vector\n' 70 | pxd_file += 'from libcpp.string cimport string\n\n' 71 | pxd_file += 'cdef extern from "' + input_header_file + '" namespace "' + namespace + '":\n' 72 | pxd_file += '\tcdef struct Params:\n' 73 | for line in params_struct_content.splitlines(): 74 | pxd_file += '\t\t' + line + '\n' 75 | 76 | with open(output_pxd_file, "w") as text_file: 77 | text_file.write(pxd_file) 78 | 79 | else: 80 | print('Params struct not found in the file.') 81 | 82 | 83 | def parse_args(): 84 | parser = argparse.ArgumentParser() 85 | parser.add_argument("output_pxd_file") 86 | parser.add_argument("input_cpp_header_file") 87 | return parser.parse_args() 88 | 89 | 90 | def main(): 91 | args = parse_args() 92 | output_file = args.output_pxd_file 93 | input_file = args.input_cpp_header_file 94 | 95 | run(output_file, input_file) 96 | 97 | 98 | if __name__ == "__main__": 99 | sys.exit(main()) 100 | -------------------------------------------------------------------------------- /pytrollercli/pytrollercli/resource/test/test_pytroller.cpp.em: -------------------------------------------------------------------------------- 1 | // Copyright 2023 ICube-Robotics 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // author: Maciej Bednarczyk 16 | 17 | #include 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include "hardware_interface/loaned_command_interface.hpp" 26 | #include "hardware_interface/loaned_state_interface.hpp" 27 | #include "hardware_interface/types/hardware_interface_return_values.hpp" 28 | #include "lifecycle_msgs/msg/state.hpp" 29 | #include "rclcpp/utilities.hpp" 30 | #include "rclcpp_lifecycle/node_interfaces/lifecycle_node_interface.hpp" 31 | #include "test_@(pytroller_name).hpp" 32 | 33 | using CallbackReturn = rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn; 34 | using hardware_interface::LoanedCommandInterface; 35 | using hardware_interface::LoanedStateInterface; 36 | 37 | namespace 38 | { 39 | rclcpp::WaitResultKind wait_for(rclcpp::SubscriptionBase::SharedPtr subscription) 40 | { 41 | rclcpp::WaitSet wait_set; 42 | wait_set.add_subscription(subscription); 43 | const auto timeout = std::chrono::seconds(10); 44 | return wait_set.wait(timeout).kind(); 45 | } 46 | } // namespace 47 | 48 | void Test@(pytroller_class)::SetUpTestCase() { rclcpp::init(0, nullptr); } 49 | 50 | void Test@(pytroller_class)::TearDownTestCase() { rclcpp::shutdown(); } 51 | 52 | void Test@(pytroller_class)::SetUp() 53 | { 54 | controller_ = std::make_unique(); 55 | } 56 | 57 | void Test@(pytroller_class)::TearDown() { controller_.reset(nullptr); } 58 | 59 | void Test@(pytroller_class)::SetUpController() 60 | { 61 | const auto result = controller_->init("test_@(pytroller_name)"); 62 | ASSERT_EQ(result, controller_interface::return_type::OK); 63 | 64 | std::vector command_ifs; 65 | command_ifs.emplace_back(joint1_ci_); 66 | command_ifs.emplace_back(joint2_ci_); 67 | 68 | std::vector state_ifs; 69 | state_ifs.emplace_back(joint1_sip_); 70 | state_ifs.emplace_back(joint1_siv_); 71 | state_ifs.emplace_back(joint2_sip_); 72 | state_ifs.emplace_back(joint2_siv_); 73 | 74 | controller_->assign_interfaces(std::move(command_ifs), std::move(state_ifs)); 75 | } 76 | 77 | TEST_F(Test@(pytroller_class), ConfigureAndActivateParamsSuccess) 78 | { 79 | SetUpController(); 80 | 81 | // configure successful 82 | ASSERT_EQ(controller_->on_configure(rclcpp_lifecycle::State()), CallbackReturn::SUCCESS); 83 | ASSERT_EQ(controller_->on_activate(rclcpp_lifecycle::State()), CallbackReturn::SUCCESS); 84 | } 85 | 86 | TEST_F(Test@(pytroller_class), NoCommandCheckTest) 87 | { 88 | SetUpController(); 89 | 90 | ASSERT_EQ(controller_->on_configure(rclcpp_lifecycle::State()), CallbackReturn::SUCCESS); 91 | ASSERT_EQ(controller_->on_activate(rclcpp_lifecycle::State()), CallbackReturn::SUCCESS); 92 | 93 | // update successful, no command received yet 94 | ASSERT_EQ( 95 | controller_->update(rclcpp::Time(0), rclcpp::Duration::from_seconds(0.005)), 96 | controller_interface::return_type::OK); 97 | 98 | // // check joint commands are still the default ones 99 | // ASSERT_EQ(joint1_ci_.get_value(), std::numeric_limits::quiet_NaN()); 100 | // ASSERT_EQ(joint2_ci_.get_value(), std::numeric_limits::quiet_NaN()); 101 | } 102 | 103 | int main(int argc, char ** argv) 104 | { 105 | ::testing::InitGoogleTest(&argc, argv); 106 | rclcpp::init(argc, argv); 107 | int result = RUN_ALL_TESTS(); 108 | rclcpp::shutdown(); 109 | return result; 110 | } 111 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # To use: 2 | # pip install pre-commit 3 | # pre-commit run -a 4 | # 5 | # (to run on all files) 6 | # pre-commit run --all-files 7 | # 8 | # Or: 9 | # 10 | # pre-commit install # (runs every time you commit in git) 11 | # 12 | # To update this file: 13 | # 14 | # pre-commit autoupdate 15 | # 16 | # See https://github.com/pre-commit/pre-commit 17 | 18 | # exclude: 19 | repos: 20 | # Standard hooks 21 | - repo: https://github.com/pre-commit/pre-commit-hooks 22 | rev: v4.4.0 23 | hooks: 24 | - id: check-added-large-files 25 | - id: check-ast 26 | - id: check-case-conflict 27 | - id: check-docstring-first 28 | - id: check-merge-conflict 29 | - id: check-symlinks 30 | - id: check-xml 31 | - id: check-yaml 32 | - id: debug-statements 33 | - id: end-of-file-fixer 34 | - id: mixed-line-ending 35 | - id: trailing-whitespace 36 | exclude_types: [rst] 37 | - id: fix-byte-order-marker 38 | 39 | # Python hooks 40 | - repo: https://github.com/asottile/pyupgrade 41 | rev: v3.3.1 42 | hooks: 43 | - id: pyupgrade 44 | args: [--py36-plus] 45 | 46 | # PyDocStyle 47 | - repo: https://github.com/PyCQA/pydocstyle 48 | rev: 6.3.0 49 | hooks: 50 | - id: pydocstyle 51 | args: ["--ignore=D100,D101,D102,D103,D104,D105,D106,D107,D203,D212,D404"] 52 | 53 | - repo: https://github.com/pycqa/flake8 54 | rev: 6.0.0 55 | hooks: 56 | - id: flake8 57 | args: ["--extend-ignore=E501"] 58 | 59 | # Uncrustify 60 | - repo: local 61 | hooks: 62 | - id: ament_uncrustify 63 | name: ament_uncrustify 64 | description: Uncrustify. 65 | stages: [commit] 66 | entry: ament_uncrustify 67 | language: system 68 | files: \.(h\+\+|h|hh|hxx|hpp|cuh|c|cc|cpp|cu|c\+\+|cxx|tpp|txx)$ 69 | args: ["--reformat"] 70 | 71 | # CPP hooks 72 | - repo: local 73 | hooks: 74 | - id: clang-format 75 | name: clang-format 76 | description: Format files with ClangFormat. 77 | entry: clang-format-14 78 | language: system 79 | files: \.(c|cc|cxx|cpp|frag|glsl|h|hpp|hxx|ih|ispc|ipp|java|js|m|proto|vert)$ 80 | args: ['-fallback-style=none', '-i'] 81 | 82 | # - repo: local 83 | # hooks: 84 | # - id: ament_cppcheck 85 | # name: ament_cppcheck 86 | # description: Static code analysis of C/C++ files. 87 | # stages: [commit] 88 | # entry: ament_cppcheck 89 | # language: system 90 | # files: \.(h\+\+|h|hh|hxx|hpp|cuh|c|cc|cpp|cu|c\+\+|cxx|tpp|txx)$ 91 | 92 | # Maybe use https://github.com/cpplint/cpplint instead 93 | - repo: local 94 | hooks: 95 | - id: ament_cpplint 96 | name: ament_cpplint 97 | description: Static code analysis of C/C++ files. 98 | stages: [commit] 99 | entry: ament_cpplint 100 | language: system 101 | files: \.(h\+\+|h|hh|hxx|hpp|cuh|c|cc|cpp|cu|c\+\+|cxx|tpp|txx)$ 102 | args: ["--linelength=100", "--filter=-whitespace/newline"] 103 | 104 | # Cmake hooks 105 | - repo: local 106 | hooks: 107 | - id: ament_lint_cmake 108 | name: ament_lint_cmake 109 | description: Check format of CMakeLists.txt files. 110 | stages: [commit] 111 | entry: ament_lint_cmake 112 | language: system 113 | files: CMakeLists\.txt$ 114 | 115 | # Copyright 116 | - repo: local 117 | hooks: 118 | - id: ament_copyright 119 | name: ament_copyright 120 | description: Check if copyright notice is available in all files. 121 | stages: [commit] 122 | entry: ament_copyright 123 | language: system 124 | 125 | # Docs - RestructuredText hooks 126 | - repo: https://github.com/PyCQA/doc8 127 | rev: v1.1.1 128 | hooks: 129 | - id: doc8 130 | args: ['--max-line-length=100', '--ignore=D001'] 131 | exclude: CHANGELOG\.rst$ 132 | 133 | - repo: https://github.com/pre-commit/pygrep-hooks 134 | rev: v1.10.0 135 | hooks: 136 | - id: rst-backticks 137 | exclude: CHANGELOG\.rst$ 138 | - id: rst-directive-colons 139 | - id: rst-inline-touching-normal 140 | 141 | # Spellcheck in comments and docs 142 | # skipping of *.svg files is not working... 143 | - repo: https://github.com/codespell-project/codespell 144 | rev: v2.2.2 145 | hooks: 146 | - id: codespell 147 | args: ['--write-changes'] 148 | exclude: CHANGELOG\.rst|\.(svg|pyc)$ 149 | -------------------------------------------------------------------------------- /pytrollercli/pytrollercli/resource/package/CMakeLists.txt.em: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | project(@(pytroller_name) LANGUAGES CXX) 3 | 4 | if(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)") 5 | add_compile_options(-Wall -Wextra -Wpedantic -Wconversion) 6 | endif() 7 | 8 | set(THIS_PACKAGE_INCLUDE_DEPENDS 9 | controller_interface 10 | generate_parameter_library 11 | hardware_interface 12 | pluginlib 13 | rclcpp 14 | rclcpp_lifecycle 15 | realtime_tools 16 | ) 17 | 18 | find_package(ament_cmake REQUIRED) 19 | foreach(Dependency IN ITEMS ${THIS_PACKAGE_INCLUDE_DEPENDS}) 20 | find_package(${Dependency} REQUIRED) 21 | endforeach() 22 | 23 | find_package(PythonLibs REQUIRED) 24 | include_directories(${PYTHON_INCLUDE_DIRS}) 25 | 26 | # Set the parameter header file name 27 | set(PARAM_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/@(pytroller_name)_parameters/include) 28 | set(PARAM_HEADER_FILE ${PARAM_INCLUDE_DIR}/@(pytroller_name)_parameters.hpp) 29 | 30 | # Make logic build directory 31 | set(LOGIC_DIR ${CMAKE_CURRENT_BINARY_DIR}/${LIB_NAME}/@(pytroller_name)_logic) 32 | set(LOGIC_INCLUDE_DIR ${LOGIC_DIR}/include/@(pytroller_name)) 33 | file(MAKE_DIRECTORY ${LOGIC_DIR}) 34 | file(MAKE_DIRECTORY ${LOGIC_INCLUDE_DIR}) 35 | 36 | file (REMOVE ${LOGIC_DIR}/@(pytroller_name)_logic.cpp) 37 | file (REMOVE ${LOGIC_DIR}/@(pytroller_name)_logic.h) 38 | file (REMOVE ${LOGIC_INCLUDE_DIR}/@(pytroller_name)_logic.h) 39 | 40 | add_custom_command( 41 | OUTPUT ${LOGIC_DIR}/@(pytroller_name)_logic.cpp ${LOGIC_DIR}/@(pytroller_name)_logic.h 42 | COMMAND cython3 -3 --cplus ${CMAKE_CURRENT_SOURCE_DIR}/src/@(pytroller_name)_logic.pyx 43 | -o ${LOGIC_DIR}/@(pytroller_name)_logic.cpp -I ${PARAM_INCLUDE_DIR} 44 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src 45 | DEPENDS 46 | ${CMAKE_CURRENT_SOURCE_DIR}/src/@(pytroller_name)_logic.pyx 47 | ${CMAKE_CURRENT_SOURCE_DIR}/script/@(pytroller_name)_logic_impl.py 48 | ) 49 | 50 | # Copy the header file into the include directory 51 | add_custom_command( 52 | OUTPUT ${LOGIC_INCLUDE_DIR}/@(pytroller_name)_logic.h 53 | COMMAND ${CMAKE_COMMAND} -E copy 54 | ${LOGIC_DIR}/@(pytroller_name)_logic.h ${LOGIC_INCLUDE_DIR}/@(pytroller_name)_logic.h 55 | DEPENDS ${LOGIC_DIR}/@(pytroller_name)_logic.h 56 | ) 57 | 58 | generate_parameter_library( 59 | @(pytroller_name)_parameters 60 | src/@(pytroller_name)_parameters.yaml 61 | ) 62 | 63 | file (REMOVE ${PARAM_INCLUDE_DIR}/@(pytroller_name)_parameters.pxd) 64 | 65 | # Generate the pxd for the library 66 | add_custom_command( 67 | OUTPUT ${PARAM_INCLUDE_DIR}/@(pytroller_name)_parameters.pxd 68 | COMMAND ros2 run pytroller_tools generate_pxd ${PARAM_INCLUDE_DIR}/@(pytroller_name)_parameters.pxd ${PARAM_HEADER_FILE} 69 | DEPENDS ${PARAM_HEADER_FILE} 70 | ) 71 | 72 | add_library(@(pytroller_name) SHARED 73 | src/@(pytroller_name).cpp 74 | ${LOGIC_DIR}/@(pytroller_name)_logic.cpp 75 | ${LOGIC_INCLUDE_DIR}/@(pytroller_name)_logic.h 76 | ${PARAM_INCLUDE_DIR}/@(pytroller_name)_parameters.pxd 77 | ) 78 | target_compile_features(@(pytroller_name) PUBLIC cxx_std_17) 79 | target_include_directories(@(pytroller_name) PUBLIC 80 | $ 81 | $ 82 | ) 83 | target_include_directories(@(pytroller_name) PUBLIC 84 | $ 85 | $ 86 | ) 87 | target_link_libraries(@(pytroller_name) PUBLIC 88 | @(pytroller_name)_parameters 89 | ${PYTHON_LIBRARIES} 90 | ) 91 | ament_target_dependencies(@(pytroller_name) PUBLIC ${THIS_PACKAGE_INCLUDE_DEPENDS}) 92 | # Causes the visibility macros to use dllexport rather than dllimport, 93 | # which is appropriate when building the dll but not consuming it. 94 | target_compile_definitions(@(pytroller_name) PRIVATE "PYTROLLER_BUILDING_DLL") 95 | pluginlib_export_plugin_description_file(controller_interface controller_plugin.xml) 96 | 97 | 98 | if(BUILD_TESTING) 99 | find_package(ament_lint_auto REQUIRED) 100 | find_package(ament_cmake_gmock REQUIRED) 101 | find_package(controller_manager REQUIRED) 102 | find_package(hardware_interface REQUIRED) 103 | find_package(ros2_control_test_assets REQUIRED) 104 | 105 | ament_lint_auto_find_test_dependencies() 106 | 107 | # Load test 108 | add_rostest_with_parameters_gmock( 109 | test_load_@(pytroller_name) 110 | test/test_load_@(pytroller_name).cpp 111 | ${CMAKE_CURRENT_SOURCE_DIR}/test/test_params.yaml 112 | ) 113 | target_link_libraries(test_load_@(pytroller_name) 114 | @(pytroller_name) 115 | ) 116 | ament_target_dependencies(test_load_@(pytroller_name) 117 | controller_manager 118 | hardware_interface 119 | ros2_control_test_assets 120 | ) 121 | 122 | # Controller test 123 | add_rostest_with_parameters_gmock( 124 | test_@(pytroller_name) 125 | test/test_@(pytroller_name).cpp 126 | ${CMAKE_CURRENT_SOURCE_DIR}/test/test_params.yaml 127 | ) 128 | 129 | target_link_libraries(test_@(pytroller_name) 130 | @(pytroller_name) 131 | ) 132 | 133 | ament_target_dependencies(test_load_@(pytroller_name) 134 | controller_manager 135 | hardware_interface 136 | ) 137 | 138 | endif() 139 | 140 | install( 141 | DIRECTORY ${LOGIC_DIR}/include 142 | DESTINATION include/@(pytroller_name) 143 | ) 144 | install( 145 | DIRECTORY include/ 146 | DESTINATION include/@(pytroller_name) 147 | ) 148 | install( 149 | TARGETS 150 | @(pytroller_name) 151 | @(pytroller_name)_parameters 152 | EXPORT export_@(pytroller_name) 153 | RUNTIME DESTINATION bin 154 | ARCHIVE DESTINATION lib 155 | LIBRARY DESTINATION lib 156 | INCLUDES DESTINATION include 157 | ) 158 | 159 | ament_export_targets(export_@(pytroller_name) HAS_LIBRARY_TARGET) 160 | ament_export_dependencies(${THIS_PACKAGE_INCLUDE_DEPENDS}) 161 | ament_package() 162 | -------------------------------------------------------------------------------- /pytrollercli/pytrollercli/api/create.py: -------------------------------------------------------------------------------- 1 | # Copyright 2023 ICube-Robotics 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | from io import StringIO 16 | import os 17 | import sys 18 | 19 | import em 20 | import importlib.resources as importlib_resources 21 | 22 | 23 | def _expand_template(template_file, data, output_file): 24 | output = StringIO() 25 | interpreter = em.Interpreter( 26 | output=output, 27 | options={ 28 | em.BUFFERED_OPT: True, 29 | em.RAW_OPT: True, 30 | }, 31 | globals=data, 32 | ) 33 | with open(template_file) as h: 34 | try: 35 | interpreter.file(h) 36 | content = output.getvalue() 37 | except Exception as e: 38 | if os.path.exists(output_file): 39 | os.remove(output_file) 40 | print("Exception when expanding '%s' into '%s': %s" % 41 | (template_file, output_file, e), file=sys.stderr) 42 | raise 43 | finally: 44 | interpreter.shutdown() 45 | 46 | if os.path.exists(output_file): 47 | with open(output_file) as h: 48 | if h.read() == content: 49 | return 50 | else: 51 | os.makedirs(os.path.dirname(output_file), exist_ok=True) 52 | 53 | with open(output_file, 'w') as h: 54 | h.write(content) 55 | 56 | 57 | def _create_folder(folder_name, base_directory, exist_ok=True): 58 | folder_path = os.path.join(base_directory, folder_name) 59 | 60 | print('creating folder', folder_path) 61 | os.makedirs(folder_path, exist_ok=exist_ok) 62 | 63 | return folder_path 64 | 65 | 66 | def _create_template_file( 67 | template_subdir, template_file_name, output_directory, output_file_name, template_config 68 | ): 69 | full_package = 'pytrollercli.resource.' + template_subdir 70 | with importlib_resources.path(full_package, template_file_name) as path: 71 | template_path = str(path) 72 | if not os.path.exists(template_path): 73 | raise FileNotFoundError('template not found:', template_path) 74 | 75 | output_file_path = os.path.join(output_directory, output_file_name) 76 | 77 | print('creating', output_file_path) 78 | _expand_template(template_path, template_config, output_file_path) 79 | 80 | 81 | def create_package_environment(pytroller_name, destination_directory): 82 | package_directory = _create_folder(pytroller_name, destination_directory) 83 | 84 | package_xml_config = { 85 | 'pytroller_name': pytroller_name, 86 | 'maintainer_name': 'maintainer', 87 | 'maintainer_email': 'maintainer@email.com' 88 | } 89 | _create_template_file( 90 | 'package', 91 | 'package.xml.em', 92 | package_directory, 93 | 'package.xml', 94 | package_xml_config) 95 | 96 | source_directory = None 97 | include_directory = None 98 | print('creating source and include folder') 99 | source_directory = _create_folder('src', package_directory) 100 | source_directory = _create_folder('script', package_directory) 101 | source_directory = _create_folder('test', package_directory) 102 | include_directory = _create_folder(pytroller_name, package_directory + os.sep + 'include') 103 | 104 | return package_directory, source_directory, include_directory 105 | 106 | 107 | def populate_ament_cmake(package_name, package_directory): 108 | class_name = package_name.replace('_', ' ').title() 109 | class_name = ''.join(x for x in class_name if not x.isspace()) 110 | cmakelists_config = { 111 | 'pytroller_name': package_name, 112 | } 113 | _create_template_file( 114 | 'package', 115 | 'CMakeLists.txt.em', 116 | package_directory, 117 | 'CMakeLists.txt', 118 | cmakelists_config) 119 | 120 | plugin_config = { 121 | 'pytroller_name': package_name, 122 | 'pytroller_class': class_name, 123 | } 124 | _create_template_file( 125 | 'package', 126 | 'controller_plugin.xml.em', 127 | package_directory, 128 | 'controller_plugin.xml', 129 | plugin_config) 130 | 131 | 132 | def populate_logic(package_name, source_directory): 133 | cmakelists_config = { 134 | 'pytroller_name': package_name, 135 | } 136 | _create_template_file( 137 | 'src', 138 | 'pytroller_logic.pyx.em', 139 | source_directory, 140 | package_name + '_logic.pyx', 141 | cmakelists_config) 142 | 143 | 144 | def populate_logic_impl(package_name, script_directory): 145 | # os.popen('cp ' + logic_script + ' ' + source_directory + os.sep + package_name +'_logic_impl.py') 146 | impl_config = { 147 | 'pytroller_name': package_name, 148 | } 149 | _create_template_file( 150 | 'script', 151 | 'pytroller_logic_impl.py.em', 152 | script_directory, 153 | package_name + '_logic_impl.py', 154 | impl_config) 155 | 156 | 157 | def populate_cpp_library(package_name, source_directory, include_directory): 158 | class_name = package_name.replace('_', ' ').title() 159 | class_name = ''.join(x for x in class_name if not x.isspace()) 160 | cpp_header_config = { 161 | 'pytroller_name': package_name, 162 | 'pytroller_class': class_name, 163 | } 164 | _create_template_file( 165 | 'include', 166 | 'pytroller.hpp.em', 167 | include_directory, 168 | package_name + '.hpp', 169 | cpp_header_config) 170 | 171 | cpp_library_config = { 172 | 'pytroller_name': package_name, 173 | 'pytroller_class': class_name 174 | } 175 | _create_template_file( 176 | 'src', 177 | 'pytroller.cpp.em', 178 | source_directory, 179 | package_name + '.cpp', 180 | cpp_library_config) 181 | 182 | visibility_config = { 183 | 'pytroller_name': package_name, 184 | } 185 | _create_template_file( 186 | 'include', 187 | 'visibility_control.h.em', 188 | include_directory, 189 | 'visibility_control.h', 190 | visibility_config) 191 | 192 | parameters_config = { 193 | 'pytroller_name': package_name, 194 | } 195 | _create_template_file( 196 | 'src', 197 | 'pytroller_parameters.yaml.em', 198 | source_directory, 199 | package_name + '_parameters.yaml', 200 | parameters_config) 201 | 202 | 203 | def populate_test(package_name, source_directory): 204 | class_name = package_name.replace('_', ' ').title() 205 | class_name = ''.join(x for x in class_name if not x.isspace()) 206 | load_test_config = { 207 | 'pytroller_name': package_name, 208 | 'pytroller_class': class_name 209 | } 210 | _create_template_file( 211 | 'test', 212 | 'test_load_pytroller.cpp.em', 213 | source_directory, 214 | 'test_load_' + package_name + '.cpp', 215 | load_test_config) 216 | 217 | test_param_config = { 218 | 'pytroller_name': package_name, 219 | } 220 | _create_template_file( 221 | 'test', 222 | 'test_params.yaml.em', 223 | source_directory, 224 | 'test_params.yaml', 225 | test_param_config) 226 | 227 | test_header_config = { 228 | 'pytroller_name': package_name, 229 | 'pytroller_class': class_name 230 | } 231 | _create_template_file( 232 | 'test', 233 | 'test_pytroller.hpp.em', 234 | source_directory, 235 | 'test_' + package_name + '.hpp', 236 | test_header_config) 237 | 238 | test_lib_config = { 239 | 'pytroller_name': package_name, 240 | 'pytroller_class': class_name 241 | } 242 | _create_template_file( 243 | 'test', 244 | 'test_pytroller.cpp.em', 245 | source_directory, 246 | 'test_' + package_name + '.cpp', 247 | test_lib_config) 248 | 249 | 250 | def create_pytroller(pytroller_name, destination_directory): 251 | create_package_environment(pytroller_name, destination_directory) 252 | populate_ament_cmake(pytroller_name, destination_directory+'/'+pytroller_name) 253 | populate_cpp_library(pytroller_name, 254 | destination_directory+'/'+pytroller_name+"/src", 255 | destination_directory+'/'+pytroller_name+"/include/"+pytroller_name) 256 | populate_logic(pytroller_name, 257 | destination_directory+'/'+pytroller_name+"/src") 258 | populate_logic_impl(pytroller_name, 259 | destination_directory+'/'+pytroller_name+"/script") 260 | populate_test(pytroller_name, 261 | destination_directory+'/'+pytroller_name+"/test") 262 | -------------------------------------------------------------------------------- /pytrollercli/pytrollercli/resource/src/pytroller.cpp.em: -------------------------------------------------------------------------------- 1 | // Copyright 2023 ICube-Robotics 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // author: Maciej Bednarczyk 16 | 17 | #include 18 | 19 | #include "@(pytroller_name)/@(pytroller_name).hpp" 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "controller_interface/helpers.hpp" 29 | #include "hardware_interface/loaned_command_interface.hpp" 30 | #include "rclcpp/logging.hpp" 31 | #include "rclcpp/qos.hpp" 32 | 33 | #include 34 | #include "@(pytroller_name)/@(pytroller_name)_logic.h" 35 | 36 | namespace @(pytroller_name) 37 | { 38 | @(pytroller_class)::@(pytroller_class)() 39 | : controller_interface::ControllerInterface(), 40 | rt_command_ptr_(nullptr), 41 | command_subscriber_(nullptr) 42 | { 43 | // Workaround to fix undeclared symbols in cpython 44 | // https://stackoverflow.com/questions/49784583/numpy-import-fails-on-multiarray-extension-library-when-called-from-embedded-pyt 45 | // TODO : remove ASAP 46 | dlopen("libpython3.10.so", RTLD_LAZY | RTLD_GLOBAL); 47 | } 48 | 49 | controller_interface::CallbackReturn @(pytroller_class)::on_init() 50 | { 51 | try 52 | { 53 | declare_parameters(); 54 | } 55 | catch (const std::exception & e) 56 | { 57 | fprintf(stderr, "Exception thrown during init stage with message: %s \n", e.what()); 58 | return controller_interface::CallbackReturn::ERROR; 59 | } 60 | 61 | if (PyImport_AppendInittab("@(pytroller_name)_logic", PyInit_@(pytroller_name)_logic) == -1) { 62 | fprintf(stderr, "Error: could not extend in-built modules table\n"); 63 | return controller_interface::CallbackReturn::ERROR; 64 | } 65 | 66 | Py_Initialize(); 67 | PyImport_ImportModule("@(pytroller_name)_logic"); 68 | 69 | return controller_interface::CallbackReturn::SUCCESS; 70 | } 71 | 72 | controller_interface::CallbackReturn @(pytroller_class)::on_configure( 73 | const rclcpp_lifecycle::State & /*previous_state*/) 74 | { 75 | auto ret = this->read_parameters(); 76 | if (ret != controller_interface::CallbackReturn::SUCCESS) 77 | { 78 | return ret; 79 | } 80 | 81 | for (auto index = 0ul; index < command_interfaces_.size(); ++index) 82 | { 83 | commands_.insert({command_interfaces_[index].get_name(), command_interfaces_[index].get_value()}); 84 | } 85 | 86 | for (auto index = 0ul; index < state_interfaces_.size(); ++index) 87 | { 88 | states_.insert({state_interfaces_[index].get_name(), state_interfaces_[index].get_value()}); 89 | } 90 | 91 | if (params_.command_topic_name.empty() != params_.command_topic_type.empty()) { 92 | RCLCPP_FATAL( 93 | get_node()->get_logger(), 94 | "The parameters 'command_topic_name' and 'command_topic_type' should BOTH be empty OR set!"); 95 | return controller_interface::CallbackReturn::ERROR; 96 | } 97 | if (!params_.command_topic_name.empty()) { 98 | command_subscriber_ = get_node()->create_generic_subscription( 99 | params_.command_topic_name, params_.command_topic_type, rclcpp::SystemDefaultsQoS(), 100 | [this](std::shared_ptr msg) { rt_command_ptr_.writeFromNonRT(msg); }); 101 | } 102 | else { 103 | command_subscriber_ = nullptr; 104 | } 105 | 106 | RCLCPP_INFO(get_node()->get_logger(), "configure successful"); 107 | return controller_interface::CallbackReturn::SUCCESS; 108 | } 109 | 110 | controller_interface::InterfaceConfiguration 111 | @(pytroller_class)::command_interface_configuration() const 112 | { 113 | controller_interface::InterfaceConfiguration command_interfaces_config; 114 | command_interfaces_config.type = controller_interface::interface_configuration_type::INDIVIDUAL; 115 | command_interfaces_config.names = command_interface_types_; 116 | 117 | return command_interfaces_config; 118 | } 119 | 120 | controller_interface::InterfaceConfiguration @(pytroller_class)::state_interface_configuration() 121 | const 122 | { 123 | return controller_interface::InterfaceConfiguration{ 124 | controller_interface::interface_configuration_type::ALL}; 125 | } 126 | 127 | controller_interface::CallbackReturn @(pytroller_class)::on_activate( 128 | const rclcpp_lifecycle::State & /*previous_state*/) 129 | { 130 | std::vector> 131 | ordered_interfaces; 132 | if ( 133 | !controller_interface::get_ordered_interfaces( 134 | command_interfaces_, command_interface_types_, std::string(""), ordered_interfaces) || 135 | command_interface_types_.size() != ordered_interfaces.size()) 136 | { 137 | RCLCPP_ERROR( 138 | get_node()->get_logger(), "Expected %zu command interfaces, got %zu", 139 | command_interface_types_.size(), ordered_interfaces.size()); 140 | return controller_interface::CallbackReturn::ERROR; 141 | } 142 | 143 | // reset command buffer if a command came through callback when controller was inactive 144 | rt_command_ptr_ = realtime_tools::RealtimeBuffer>(nullptr); 145 | 146 | RCLCPP_INFO(get_node()->get_logger(), "activate successful"); 147 | return controller_interface::CallbackReturn::SUCCESS; 148 | } 149 | 150 | controller_interface::CallbackReturn @(pytroller_class)::on_deactivate( 151 | const rclcpp_lifecycle::State & /*previous_state*/) 152 | { 153 | // reset command buffer 154 | rt_command_ptr_ = realtime_tools::RealtimeBuffer>(nullptr); 155 | return controller_interface::CallbackReturn::SUCCESS; 156 | } 157 | 158 | controller_interface::return_type @(pytroller_class)::update( 159 | const rclcpp::Time & /*time*/, const rclcpp::Duration & period) 160 | { 161 | // update parameters if changed 162 | if (param_listener_->is_old(params_)) { 163 | params_ = param_listener_->get_params(); 164 | } 165 | 166 | // Read command msg if needed 167 | std::vector message; 168 | if (command_subscriber_) { 169 | auto msg = rt_command_ptr_.readFromRT(); 170 | if (!msg || !(*msg)) { 171 | // no command received yet 172 | return controller_interface::return_type::OK; 173 | } 174 | // fill message buffer to be passed to python 175 | message = std::vector( 176 | (*msg)->get_rcl_serialized_message().buffer, 177 | (*msg)->get_rcl_serialized_message().buffer + (*msg)->size() 178 | ); 179 | } 180 | 181 | // fill commands and states maps to be passed to python 182 | for (auto index = 0ul; index < command_interfaces_.size(); ++index) { 183 | commands_[command_interfaces_[index].get_name()] = std::numeric_limits::quiet_NaN(); 184 | } 185 | for (auto index = 0ul; index < state_interfaces_.size(); ++index) { 186 | states_[state_interfaces_[index].get_name()] = state_interfaces_[index].get_value(); 187 | } 188 | 189 | // run cython function 190 | if (@(pytroller_name)_logic(period.seconds(), states_, commands_, message, params_)) { 191 | RCLCPP_ERROR_THROTTLE( 192 | get_node()->get_logger(), *(get_node()->get_clock()), 1000, 193 | "@(pytroller_name) logic failed."); 194 | return controller_interface::return_type::ERROR; 195 | } 196 | 197 | // retrieve commands from python and fill command interfaces 198 | for (auto index = 0ul; index < command_interfaces_.size(); ++index) { 199 | command_interfaces_[index].set_value(commands_[command_interfaces_[index].get_name()]); 200 | } 201 | 202 | return controller_interface::return_type::OK; 203 | } 204 | 205 | void @(pytroller_class)::declare_parameters() 206 | { 207 | param_listener_ = std::make_shared(get_node()); 208 | } 209 | 210 | controller_interface::CallbackReturn @(pytroller_class)::read_parameters() 211 | { 212 | if (!param_listener_) 213 | { 214 | RCLCPP_ERROR(get_node()->get_logger(), "Error encountered during init"); 215 | return controller_interface::CallbackReturn::ERROR; 216 | } 217 | params_ = param_listener_->get_params(); 218 | 219 | if (params_.interface_full_names.empty()) 220 | { 221 | RCLCPP_ERROR(get_node()->get_logger(), "'interface_full_names' parameter was empty"); 222 | return controller_interface::CallbackReturn::ERROR; 223 | } 224 | 225 | for (const auto & interface_name : params_.interface_full_names) { 226 | command_interface_types_.push_back(interface_name); 227 | } 228 | 229 | return controller_interface::CallbackReturn::SUCCESS; 230 | } 231 | 232 | } // namespace @(pytroller_name) 233 | 234 | #include "pluginlib/class_list_macros.hpp" 235 | 236 | PLUGINLIB_EXPORT_CLASS( 237 | @(pytroller_name)::@(pytroller_class), controller_interface::ControllerInterface) 238 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /pytroller_tools/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | --------------------------------------------------------------------------------