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()