diff --git a/.github/workflows/black.yaml b/.github/workflows/black.yaml index ac9902d8..a170ee47 100644 --- a/.github/workflows/black.yaml +++ b/.github/workflows/black.yaml @@ -2,7 +2,6 @@ name: Black python lint on: workflow_call: - pull_request: push: jobs: diff --git a/rosbot_bringup/CMakeLists.txt b/rosbot_bringup/CMakeLists.txt deleted file mode 100644 index e6db2efd..00000000 --- a/rosbot_bringup/CMakeLists.txt +++ /dev/null @@ -1,12 +0,0 @@ -cmake_minimum_required(VERSION 3.10.2) -project(rosbot_bringup) - -find_package(ament_cmake REQUIRED) - -install(DIRECTORY - launch - config - DESTINATION share/${PROJECT_NAME} -) - -ament_package() \ No newline at end of file diff --git a/rosbot_bringup/launch/bringup.launch.py b/rosbot_bringup/launch/bringup.launch.py index aef42ba0..d1c8114b 100644 --- a/rosbot_bringup/launch/bringup.launch.py +++ b/rosbot_bringup/launch/bringup.launch.py @@ -1,6 +1,19 @@ +# Copyright 2023 Husarion +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + from launch import LaunchDescription from launch_ros.actions import Node, SetParameter -from launch.substitutions import PathJoinSubstitution from ament_index_python.packages import get_package_share_directory from launch.actions import IncludeLaunchDescription, DeclareLaunchArgument from launch.launch_description_sources import PythonLaunchDescriptionSource @@ -37,7 +50,10 @@ def generate_launch_description(): declare_mecanum_arg = DeclareLaunchArgument( "mecanum", default_value="False", - description="Whether to use mecanum drive controller (otherwise diff drive controller is used)", + description=( + "Whether to use mecanum drive controller " + "(otherwise diff drive controller is used)" + ), ) controller_launch = IncludeLaunchDescription( diff --git a/rosbot_bringup/package.xml b/rosbot_bringup/package.xml index 1cfae26f..c9d5f14a 100644 --- a/rosbot_bringup/package.xml +++ b/rosbot_bringup/package.xml @@ -13,14 +13,20 @@ https://github.com/husarion/rosbot_ros https://github.com/husarion/rosbot_ros/issues - ament_cmake - launch launch_ros rosbot_controller robot_localization + python3-pytest + launch + launch_ros + launch_pytest + + robot_localization + rosbot_controller + - ament_cmake + ament_python diff --git a/rosbot_bringup/resource/rosbot_bringup b/rosbot_bringup/resource/rosbot_bringup new file mode 100644 index 00000000..e69de29b diff --git a/rosbot_bringup/setup.cfg b/rosbot_bringup/setup.cfg new file mode 100644 index 00000000..a7544dba --- /dev/null +++ b/rosbot_bringup/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/rosbot_bringup +[install] +install_scripts=$base/lib/rosbot_bringup diff --git a/rosbot_bringup/setup.py b/rosbot_bringup/setup.py new file mode 100644 index 00000000..b31703b9 --- /dev/null +++ b/rosbot_bringup/setup.py @@ -0,0 +1,27 @@ +import os +from glob import glob +from setuptools import find_packages, setup + +package_name = "rosbot_bringup" + +setup( + name=package_name, + version="0.8.0", + packages=find_packages(exclude=["test"]), + data_files=[ + ("share/ament_index/resource_index/packages", ["resource/" + package_name]), + ("share/" + package_name, ["package.xml"]), + (os.path.join("share", package_name, "launch"), glob("launch/*.launch.py")), + (os.path.join("share", package_name, "config"), glob("config/*.yaml")), + ], + install_requires=["setuptools"], + zip_safe=True, + maintainer="Husarion", + maintainer_email="contact@husarion.com", + description="ROSbot 2, 2R, PRO bringup package", + license="Apache License 2.0", + tests_require=["pytest"], + entry_points={ + "console_scripts": [], + }, +) diff --git a/rosbot_bringup/test/bringup_test_node.py b/rosbot_bringup/test/bringup_test_node.py new file mode 100644 index 00000000..6da192ad --- /dev/null +++ b/rosbot_bringup/test/bringup_test_node.py @@ -0,0 +1,80 @@ +# Copyright 2021 Open Source Robotics Foundation, Inc. +# Copyright 2023 Husarion +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import rclpy + +from threading import Event +from threading import Thread + +from rclpy.node import Node + +from sensor_msgs.msg import JointState, Imu +from nav_msgs.msg import Odometry + + +class BringupTestNode(Node): + ROSBOT_HARDWARE_PUBLISHERS_RATE = 10.0 + + __test__ = False + + def __init__(self, name="test_node"): + super().__init__(name) + self.odom_msg_event = Event() + + def create_test_subscribers_and_publishers(self): + self.imu_publisher = self.create_publisher(Imu, "_imu/data_raw", 10) + + self.joint_states_publisher = self.create_publisher( + JointState, "_motors_response", 10 + ) + + self.odom_sub = self.create_subscription( + Odometry, "/odometry/filtered", self.odometry_callback, 10 + ) + self.timer = None + + def start_node_thread(self): + self.ros_spin_thread = Thread( + target=lambda node: rclpy.spin(node), args=(self,) + ) + self.ros_spin_thread.start() + + def odometry_callback(self, data): + self.odom_msg_event.set() + + def start_publishing_fake_hardware(self): + self.timer = self.create_timer( + 1.0 / self.ROSBOT_HARDWARE_PUBLISHERS_RATE, + self.publish_fake_hardware_messages, + ) + + def publish_fake_hardware_messages(self): + imu_msg = Imu() + imu_msg.header.stamp = self.get_clock().now().to_msg() + imu_msg.header.frame_id = "imu_link" + + joint_state_msg = JointState() + joint_state_msg.header.stamp = self.get_clock().now().to_msg() + joint_state_msg.name = [ + "fl_wheel_joint", + "fr_wheel_joint", + "rl_wheel_joint", + "rr_wheel_joint", + ] + joint_state_msg.position = [0.0, 0.0, 0.0, 0.0] + joint_state_msg.velocity = [0.0, 0.0, 0.0, 0.0] + + self.imu_publisher.publish(imu_msg) + self.joint_states_publisher.publish(joint_state_msg) diff --git a/rosbot_bringup/test/test_copyright.py b/rosbot_bringup/test/test_copyright.py new file mode 100644 index 00000000..f46f861d --- /dev/null +++ b/rosbot_bringup/test/test_copyright.py @@ -0,0 +1,23 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_copyright.main import main +import pytest + + +@pytest.mark.copyright +@pytest.mark.linter +def test_copyright(): + rc = main(argv=[".", "test"]) + assert rc == 0, "Found errors" diff --git a/rosbot_bringup/test/test_diff_drive_ekf.py b/rosbot_bringup/test/test_diff_drive_ekf.py new file mode 100644 index 00000000..ccad09db --- /dev/null +++ b/rosbot_bringup/test/test_diff_drive_ekf.py @@ -0,0 +1,66 @@ +# Copyright 2021 Open Source Robotics Foundation, Inc. +# Copyright 2023 Intel Corporation. All Rights Reserved. +# Copyright 2023 Husarion +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import launch_pytest +import pytest +import rclpy + +from ament_index_python.packages import get_package_share_directory +from launch import LaunchDescription +from launch.actions import IncludeLaunchDescription +from launch.substitutions import PathJoinSubstitution +from launch.launch_description_sources import PythonLaunchDescriptionSource +from bringup_test_node import BringupTestNode + + +@launch_pytest.fixture +def generate_test_description(): + rosbot_bringup = get_package_share_directory("rosbot_bringup") + bringup_launch = IncludeLaunchDescription( + PythonLaunchDescriptionSource( + PathJoinSubstitution( + [ + rosbot_bringup, + "launch", + "bringup.launch.py", + ] + ) + ), + launch_arguments={ + "use_sim": "False", + "mecanum": "False", + "use_gpu": "False", + }.items(), + ) + + return LaunchDescription([bringup_launch]) + + +@pytest.mark.launch(fixture=generate_test_description) +def test_bringup_startup_success(): + rclpy.init() + try: + node = BringupTestNode("test_bringup") + node.create_test_subscribers_and_publishers() + node.start_publishing_fake_hardware() + + node.start_node_thread() + msgs_received_flag = node.odom_msg_event.wait(timeout=10.0) + assert ( + msgs_received_flag + ), "Did not receive Odom message, check robot_localization node!" + finally: + rclpy.shutdown() diff --git a/rosbot_bringup/test/test_flake8.py b/rosbot_bringup/test/test_flake8.py new file mode 100644 index 00000000..ee79f31a --- /dev/null +++ b/rosbot_bringup/test/test_flake8.py @@ -0,0 +1,25 @@ +# Copyright 2017 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_flake8.main import main_with_errors +import pytest + + +@pytest.mark.flake8 +@pytest.mark.linter +def test_flake8(): + rc, errors = main_with_errors(argv=[]) + assert rc == 0, "Found %d code style errors / warnings:\n" % len( + errors + ) + "\n".join(errors) diff --git a/rosbot_bringup/test/test_mecanum_ekf.py b/rosbot_bringup/test/test_mecanum_ekf.py new file mode 100644 index 00000000..5c5567ce --- /dev/null +++ b/rosbot_bringup/test/test_mecanum_ekf.py @@ -0,0 +1,66 @@ +# Copyright 2021 Open Source Robotics Foundation, Inc. +# Copyright 2023 Intel Corporation. All Rights Reserved. +# Copyright 2023 Husarion +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import launch_pytest +import pytest +import rclpy + +from ament_index_python.packages import get_package_share_directory +from launch import LaunchDescription +from launch.actions import IncludeLaunchDescription +from launch.substitutions import PathJoinSubstitution +from launch.launch_description_sources import PythonLaunchDescriptionSource +from bringup_test_node import BringupTestNode + + +@launch_pytest.fixture +def generate_test_description(): + rosbot_bringup = get_package_share_directory("rosbot_bringup") + bringup_launch = IncludeLaunchDescription( + PythonLaunchDescriptionSource( + PathJoinSubstitution( + [ + rosbot_bringup, + "launch", + "bringup.launch.py", + ] + ) + ), + launch_arguments={ + "use_sim": "False", + "mecanum": "True", + "use_gpu": "False", + }.items(), + ) + + return LaunchDescription([bringup_launch]) + + +@pytest.mark.launch(fixture=generate_test_description) +def test_bringup_startup_success(): + rclpy.init() + try: + node = BringupTestNode("test_bringup") + node.create_test_subscribers_and_publishers() + node.start_publishing_fake_hardware() + + node.start_node_thread() + msgs_received_flag = node.odom_msg_event.wait(timeout=10.0) + assert ( + msgs_received_flag + ), "Did not receive Odom message, check robot_localization node!" + finally: + rclpy.shutdown() diff --git a/rosbot_bringup/test/test_pep257.py b/rosbot_bringup/test/test_pep257.py new file mode 100644 index 00000000..a2c3deb8 --- /dev/null +++ b/rosbot_bringup/test/test_pep257.py @@ -0,0 +1,23 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_pep257.main import main +import pytest + + +@pytest.mark.linter +@pytest.mark.pep257 +def test_pep257(): + rc = main(argv=[".", "test"]) + assert rc == 0, "Found code style errors / warnings" diff --git a/rosbot_controller/test/controllers_test_node.py b/rosbot_controller/test/controllers_test_node.py index ced4b807..26fdea3e 100644 --- a/rosbot_controller/test/controllers_test_node.py +++ b/rosbot_controller/test/controllers_test_node.py @@ -12,6 +12,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + import rclpy from threading import Event diff --git a/rosbot_controller/test/test_diff_drive_controllers.py b/rosbot_controller/test/test_diff_drive_controllers.py index f23ba219..52bcf9b7 100644 --- a/rosbot_controller/test/test_diff_drive_controllers.py +++ b/rosbot_controller/test/test_diff_drive_controllers.py @@ -65,7 +65,7 @@ def test_controllers_startup_fail(): assert ( not msgs_received_flag ), "Received Odom message, check rosbot_base_controller!" - msgs_received_flag = node.joint_state_msg_event.wait(timeout=10.0) + msgs_received_flag = node.imu_msg_event.wait(timeout=10.0) assert not msgs_received_flag, "Received Imu message, check imu_broadcaster!" finally: rclpy.shutdown() diff --git a/rosbot_controller/test/test_mecanum_controllers.py b/rosbot_controller/test/test_mecanum_controllers.py index 7bb8e84f..f88bb0e1 100644 --- a/rosbot_controller/test/test_mecanum_controllers.py +++ b/rosbot_controller/test/test_mecanum_controllers.py @@ -65,7 +65,7 @@ def test_controllers_startup_fail(): assert ( not msgs_received_flag ), "Received Odom message, check rosbot_base_controller!" - msgs_received_flag = node.joint_state_msg_event.wait(timeout=10.0) + msgs_received_flag = node.imu_msg_event.wait(timeout=10.0) assert not msgs_received_flag, "Received Imu message, check imu_broadcaster!" finally: rclpy.shutdown()