diff --git a/README.md b/README.md
index 633e44f3..3e7a1f2e 100644
--- a/README.md
+++ b/README.md
@@ -88,6 +88,7 @@ Launch arguments are largely common to both simulation and physical robot. Howev
| 🤖🖥️ | `components_config_path` | Additional components configuration file. Components described in this file are dynamically included in Panther's urdf. Panther options are described in [the manual](https://husarion.com/manuals/panther/panther-options).
***string:*** [`components.yaml`](./panther_description/config/components.yaml) |
| 🤖🖥️ | `controller_config_path` | Path to controller configuration file. A path to custom configuration can be specified here.
***string:*** [`{wheel_type}_controller.yaml`](./panther_controller/config/) |
| 🤖 | `disable_manager` | Enable or disable manager_bt_node.
***bool:*** `False` |
+| 🤖🖥️ | `docking_bt_project_path` | Path to BehaviorTree project file, responsible for docking management.
***string:*** [`DockingBT.btproj`](./panther_manager/behavior_trees/DockingBT.btproj) |
| 🤖🖥️ | `fuse_gps` | Include GPS for data fusion.
***bool:*** `False` |
| 🖥️ | `gz_bridge_config_path` | Path to the parameter_bridge configuration file.
***string:*** [`gz_bridge.yaml`](./panther_gazebo/config/gz_bridge.yaml) |
| 🖥️ | `gz_gui` | Run simulation with specific GUI layout.
***string:*** [`teleop.config`](https://github.com/husarion/husarion_gz_worlds/blob/main/config/teleop.config) |
@@ -104,6 +105,7 @@ Launch arguments are largely common to both simulation and physical robot. Howev
| 🖥️ | `robots` | The list of the robots spawned in the simulation e.g. `robots:='robot1={x: 1.0, y: -2.0}; robot2={x: 1.0, y: -4.0}'`
***string:*** `''` |
| 🤖🖥️ | `safety_bt_project_path` | Path to BehaviorTree project file, responsible for safety and shutdown management.
***string:*** [`PantherSafetyBT.btproj`](./panther_manager/behavior_trees/PantherSafetyBT.btproj) |
| 🤖🖥️ | `shutdown_hosts_config_path` | Path to file with list of hosts to request shutdown.
***string:*** [`shutdown_hosts_config.yaml`](./panther_manager/config/shutdown_hosts_config.yaml) |
+| 🤖🖥️ | `use_docking` | Enable docking server.
***bool:*** `True` |
| 🤖🖥️ | `use_ekf` | Enable or disable EKF.
***bool:*** `True` |
| 🤖🖥️ | `use_sim` | Whether simulation is used.
***bool:*** `False` |
| 🤖🖥️ | `user_led_animations_file` | Path to a YAML file with a description of the user-defined animations.
***string:*** `''` |
diff --git a/panther_diagnostics/src/system_monitor_node.cpp b/panther_diagnostics/src/system_monitor_node.cpp
index bc921a75..5da51464 100644
--- a/panther_diagnostics/src/system_monitor_node.cpp
+++ b/panther_diagnostics/src/system_monitor_node.cpp
@@ -28,7 +28,6 @@
#include "panther_utils/ros_utils.hpp"
#include "panther_diagnostics/filesystem.hpp"
-#include "panther_diagnostics/types.hpp"
namespace panther_diagnostics
{
diff --git a/panther_docking/README.md b/panther_docking/README.md
index a8a314f0..0fa6c19f 100644
--- a/panther_docking/README.md
+++ b/panther_docking/README.md
@@ -46,7 +46,7 @@ The package contains a `PantherChargingDock` plugin for the [opennav_docking](ht
- `base_frame` [*string*, default: **base_link**]: A base frame id of a robot.
- `fixed_frame` [*string*, default: **odom**]: A fixed frame id of a robot.
-- `.external_detection_timeout` [*double*, default: **0.2**]: A timeout in seconds for dock pose.
+- `.external_detection_timeout` [*double*, default: **0.2**]: A timeout in seconds for looking up a transformation from an april tag of a dock to a base frame id.
- `.docking_distance_threshold` [*double*, default: **0.05**]: A threshold of a distance between a robot pose and a dock pose to declare if docking succeed.
- `.docking_yaw_threshold` [*double*, default: **0.3**]: A threshold of a difference of yaw angles between a robot pose and a dock pose to declare if docking succeed.
- `.staging_x_offset` [*double*, default: **-0.7**]: A staging pose is defined by offsetting a dock pose in axis X.
diff --git a/panther_docking/launch/docking.launch.py b/panther_docking/launch/docking.launch.py
index 5749c0b6..d9b9764c 100644
--- a/panther_docking/launch/docking.launch.py
+++ b/panther_docking/launch/docking.launch.py
@@ -17,7 +17,6 @@
from launch.conditions import IfCondition
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.substitutions import (
- EnvironmentVariable,
LaunchConfiguration,
PathJoinSubstitution,
PythonExpression,
@@ -28,21 +27,6 @@
def generate_launch_description():
- use_sim = LaunchConfiguration("use_sim")
- declare_use_sim_arg = DeclareLaunchArgument(
- "use_sim",
- default_value="False",
- description="Whether simulation is used",
- choices=[True, False, "True", "False", "true", "false", "1", "0"],
- )
-
- namespace = LaunchConfiguration("namespace")
- declare_namespace_arg = DeclareLaunchArgument(
- "namespace",
- default_value=EnvironmentVariable("ROBOT_NAMESPACE", default_value=""),
- description="Add namespace to all launched nodes.",
- )
-
docking_server_config_path = LaunchConfiguration("docking_server_config_path")
declare_docking_server_config_path_arg = DeclareLaunchArgument(
"docking_server_config_path",
@@ -52,6 +36,17 @@ def generate_launch_description():
description=("Path to docking server configuration file."),
)
+ declare_use_docking_arg = DeclareLaunchArgument(
+ "use_docking",
+ default_value="True",
+ description="Enable docking server.",
+ choices=["True", "False", "true", "false"],
+ )
+
+ namespace = LaunchConfiguration("namespace")
+ use_docking = LaunchConfiguration("use_docking")
+ use_sim = LaunchConfiguration("use_sim")
+
log_level = LaunchConfiguration("log_level")
declare_log_level = DeclareLaunchArgument(
"log_level",
@@ -82,19 +77,22 @@ def generate_launch_description():
docking_server_node = Node(
package="opennav_docking",
executable="opennav_docking",
+ namespace=namespace,
parameters=[
namespaced_docking_server_config,
{"use_sim_time": use_sim},
],
arguments=["--ros-args", "--log-level", log_level, "--log-level", "rcl:=INFO"],
- namespace=namespace,
+ remappings=[("~/transition_event", "~/_transition_event")],
emulate_tty=True,
+ condition=IfCondition(use_docking),
)
docking_server_activate_node = Node(
package="nav2_lifecycle_manager",
executable="lifecycle_manager",
name="nav2_docking_lifecycle_manager",
+ namespace=namespace,
parameters=[
{
"autostart": True,
@@ -104,7 +102,7 @@ def generate_launch_description():
"use_sim_time": use_sim,
},
],
- namespace=namespace,
+ condition=IfCondition(use_docking),
)
dock_pose_publisher = Node(
@@ -144,8 +142,7 @@ def generate_launch_description():
return LaunchDescription(
[
- declare_use_sim_arg,
- declare_namespace_arg,
+ declare_use_docking_arg,
declare_docking_server_config_path_arg,
declare_log_level,
declare_use_wibotic_info_arg,
diff --git a/panther_docking/launch/station.launch.py b/panther_docking/launch/station.launch.py
index b60fbeb4..c0f382ff 100644
--- a/panther_docking/launch/station.launch.py
+++ b/panther_docking/launch/station.launch.py
@@ -17,6 +17,7 @@
import imageio
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument, OpaqueFunction
+from launch.conditions import IfCondition
from launch.substitutions import (
Command,
EnvironmentVariable,
@@ -28,10 +29,11 @@
from launch_ros.actions import Node
from launch_ros.parameter_descriptions import ParameterValue
from launch_ros.substitutions import FindPackageShare
-from moms_apriltag import TagGenerator2
def generate_apriltag_and_get_path(tag_id):
+ from moms_apriltag import TagGenerator2
+
tag_generator = TagGenerator2("tag36h11")
tag_image = tag_generator.generate(tag_id, scale=1000)
@@ -45,6 +47,7 @@ def launch_setup(context, *args, **kwargs):
namespace = LaunchConfiguration("namespace").perform(context)
apriltag_id = int(LaunchConfiguration("apriltag_id").perform(context))
apriltag_size = LaunchConfiguration("apriltag_size").perform(context)
+ use_docking = LaunchConfiguration("use_docking").perform(context)
apriltag_image_path = generate_apriltag_and_get_path(apriltag_id)
@@ -85,12 +88,20 @@ def launch_setup(context, *args, **kwargs):
remappings=[("robot_description", "station_description")],
namespace=namespace,
emulate_tty=True,
+ condition=IfCondition(use_docking),
)
return [station_state_pub_node]
def generate_launch_description():
+ declare_use_docking_arg = DeclareLaunchArgument(
+ "use_docking",
+ default_value="True",
+ description="Enable docking server.",
+ choices=["True", "False", "true", "false"],
+ )
+
declare_namespace_arg = DeclareLaunchArgument(
"namespace",
default_value=EnvironmentVariable("ROBOT_NAMESPACE", default_value=""),
@@ -111,6 +122,7 @@ def generate_launch_description():
return LaunchDescription(
[
+ declare_use_docking_arg,
declare_namespace_arg,
declare_apriltag_id,
declare_apriltag_size,
diff --git a/panther_gazebo/config/apriltag.yaml b/panther_gazebo/config/apriltag.yaml
new file mode 100644
index 00000000..920c6f99
--- /dev/null
+++ b/panther_gazebo/config/apriltag.yaml
@@ -0,0 +1,23 @@
+---
+/**:
+ ros__parameters:
+ image_transport: raw
+ family: 36h11
+ size: 0.06
+ profile: false
+
+
+ max_hamming: 0
+ detector:
+ threads: 1
+ decimate: 2.0
+ blur: 0.0
+ refine: true
+ sharpening: 0.25
+ debug: false
+
+
+ tag:
+ ids: [0]
+ frames: [/main_apriltag_link]
+ sizes: [0.06]
diff --git a/panther_gazebo/launch/apriltag.launch.py b/panther_gazebo/launch/apriltag.launch.py
new file mode 100644
index 00000000..deeaa24e
--- /dev/null
+++ b/panther_gazebo/launch/apriltag.launch.py
@@ -0,0 +1,62 @@
+# Copyright 2024 Husarion sp. z o.o.
+#
+# 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.actions import DeclareLaunchArgument
+from launch.conditions import IfCondition
+from launch.substitutions import LaunchConfiguration, PathJoinSubstitution
+from launch_ros.actions import Node
+from launch_ros.substitutions import FindPackageShare
+from nav2_common.launch import ReplaceString
+
+
+def generate_launch_description():
+ use_docking = LaunchConfiguration("use_docking")
+ use_sim = LaunchConfiguration("use_sim")
+ namespace = LaunchConfiguration("namespace")
+
+ apriltag_config_path = LaunchConfiguration("apriltag_config_path")
+ apriltag_config_path_arg = DeclareLaunchArgument(
+ "apriltag_config_path",
+ default_value=PathJoinSubstitution(
+ [FindPackageShare("panther_gazebo"), "config", "apriltag.yaml"]
+ ),
+ description=("Path to apriltag configuration file."),
+ )
+
+ namespaced_apriltag_config_path = ReplaceString(
+ source_file=apriltag_config_path,
+ replacements={"": namespace, "//": "/"},
+ )
+
+ apriltag_node = Node(
+ package="apriltag_ros",
+ executable="apriltag_node",
+ parameters=[{"use_sim_time": use_sim}, namespaced_apriltag_config_path],
+ namespace=namespace,
+ emulate_tty=True,
+ remappings={
+ "camera_info": "camera/color/camera_info",
+ "image_rect": "camera/color/image_raw",
+ "detections": "docking/april_tags",
+ }.items(),
+ condition=IfCondition(use_docking),
+ )
+
+ return LaunchDescription(
+ [
+ apriltag_config_path_arg,
+ apriltag_node,
+ ]
+ )
diff --git a/panther_gazebo/launch/simulate_robot.launch.py b/panther_gazebo/launch/simulate_robot.launch.py
index ad7252e7..8f7fa280 100644
--- a/panther_gazebo/launch/simulate_robot.launch.py
+++ b/panther_gazebo/launch/simulate_robot.launch.py
@@ -185,6 +185,32 @@ def generate_launch_description():
emulate_tty=True,
)
+ docking_launch = IncludeLaunchDescription(
+ PythonLaunchDescriptionSource(
+ PathJoinSubstitution(
+ [
+ FindPackageShare("panther_docking"),
+ "launch",
+ "docking.launch.py",
+ ]
+ ),
+ ),
+ launch_arguments={"namespace": namespace, "use_sim": "True"}.items(),
+ )
+
+ apriltag_launch = IncludeLaunchDescription(
+ PythonLaunchDescriptionSource(
+ PathJoinSubstitution(
+ [
+ FindPackageShare("panther_gazebo"),
+ "launch",
+ "apriltag.launch.py",
+ ]
+ ),
+ ),
+ launch_arguments={"namespace": namespace, "use_sim": "True"}.items(),
+ )
+
return LaunchDescription(
[
declare_battery_config_path_arg,
@@ -200,5 +226,7 @@ def generate_launch_description():
ekf_launch,
simulate_components,
gz_bridge,
+ docking_launch,
+ apriltag_launch,
]
)
diff --git a/panther_gazebo/package.xml b/panther_gazebo/package.xml
index 89e7baaa..ecadf84c 100644
--- a/panther_gazebo/package.xml
+++ b/panther_gazebo/package.xml
@@ -27,6 +27,7 @@
std_msgs
std_srvs
+ apriltag_ros
controller_manager
husarion_gz_worlds
launch
@@ -34,6 +35,7 @@
nav2_common
panther_controller
panther_description
+ panther_docking
panther_lights
panther_localization
panther_manager
diff --git a/panther_manager/CMakeLists.txt b/panther_manager/CMakeLists.txt
index 1f7bcfb9..1ba4ddde 100644
--- a/panther_manager/CMakeLists.txt
+++ b/panther_manager/CMakeLists.txt
@@ -11,14 +11,17 @@ set(PACKAGE_DEPENDENCIES
behaviortree_cpp
behaviortree_ros2
libssh
+ geometry_msgs
+ opennav_docking_msgs
panther_msgs
panther_utils
rclcpp
rclcpp_action
sensor_msgs
+ std_msgs
std_srvs
- yaml-cpp
- opennav_docking_msgs)
+ tf2_geometry_msgs
+ yaml-cpp)
foreach(PACKAGE IN ITEMS ${PACKAGE_DEPENDENCIES})
find_package(${PACKAGE} REQUIRED)
@@ -59,9 +62,13 @@ add_library(undock_robot_bt_node SHARED
src/plugins/action/undock_robot_node.cpp)
list(APPEND plugin_libs undock_robot_bt_node)
-add_library(are_buttons_pressed_bt_node SHARED
- src/plugins/condition/are_buttons_pressed.cpp)
-list(APPEND plugin_libs are_buttons_pressed_bt_node)
+add_library(check_bool_msg_bt_node SHARED
+ src/plugins/condition/check_bool_msg.cpp)
+list(APPEND plugin_libs check_bool_msg_bt_node)
+
+add_library(check_joy_msg_bt_node SHARED
+ src/plugins/condition/check_joy_msg.cpp)
+list(APPEND plugin_libs check_joy_msg_bt_node)
add_library(tick_after_timeout_bt_node SHARED
src/plugins/decorator/tick_after_timeout_node.cpp)
@@ -75,32 +82,25 @@ endforeach()
add_executable(
safety_manager_node src/safety_manager_node_main.cpp
src/safety_manager_node.cpp src/behavior_tree_manager.cpp)
-ament_target_dependencies(
- safety_manager_node
- behaviortree_ros2
- panther_msgs
- panther_utils
- rclcpp
- sensor_msgs
- std_msgs)
+ament_target_dependencies(safety_manager_node ${PACKAGE_DEPENDENCIES})
target_link_libraries(safety_manager_node ${plugin_libs})
add_executable(
lights_manager_node src/lights_manager_node_main.cpp
src/lights_manager_node.cpp src/behavior_tree_manager.cpp)
-ament_target_dependencies(
- lights_manager_node
- behaviortree_ros2
- panther_msgs
- panther_utils
- rclcpp
- sensor_msgs
- std_msgs)
+ament_target_dependencies(lights_manager_node ${PACKAGE_DEPENDENCIES})
target_link_libraries(lights_manager_node ${plugin_libs})
+add_executable(
+ docking_manager_node
+ src/docking_manager_node_main.cpp src/docking_manager_node.cpp
+ src/behavior_tree_manager.cpp)
+ament_target_dependencies(docking_manager_node ${PACKAGE_DEPENDENCIES})
+target_link_libraries(docking_manager_node ${plugin_libs})
+
install(TARGETS ${plugin_libs} DESTINATION lib)
-install(TARGETS safety_manager_node lights_manager_node
+install(TARGETS safety_manager_node lights_manager_node docking_manager_node
DESTINATION lib/${PROJECT_NAME})
install(DIRECTORY behavior_trees config launch
@@ -161,10 +161,16 @@ if(BUILD_TESTING)
list(APPEND plugin_tests ${PROJECT_NAME}_test_undock_robot_node)
ament_add_gtest(
- ${PROJECT_NAME}_test_are_buttons_pressed
- test/plugins/condition/test_are_buttons_pressed.cpp
- src/plugins/condition/are_buttons_pressed.cpp)
- list(APPEND plugin_tests ${PROJECT_NAME}_test_are_buttons_pressed)
+ ${PROJECT_NAME}_test_check_bool_msg
+ test/plugins/condition/test_check_bool_msg.cpp
+ src/plugins/condition/check_bool_msg.cpp)
+ list(APPEND plugin_tests ${PROJECT_NAME}_test_check_bool_msg)
+
+ ament_add_gtest(
+ ${PROJECT_NAME}_test_check_joy_msg
+ test/plugins/condition/test_check_joy_msg.cpp
+ src/plugins/condition/check_joy_msg.cpp)
+ list(APPEND plugin_tests ${PROJECT_NAME}_test_check_joy_msg)
ament_add_gtest(
${PROJECT_NAME}_test_tick_after_timeout_node
@@ -198,7 +204,7 @@ if(BUILD_TESTING)
PUBLIC $
$)
ament_target_dependencies(${PROJECT_NAME}_test_behavior_tree_utils
- behaviortree_cpp behaviortree_ros2 panther_utils)
+ ${PACKAGE_DEPENDENCIES})
ament_add_gtest(
${PROJECT_NAME}_test_behavior_tree_manager
@@ -217,15 +223,8 @@ if(BUILD_TESTING)
${PROJECT_NAME}_test_lights_manager_node
PUBLIC $
$)
- ament_target_dependencies(
- ${PROJECT_NAME}_test_lights_manager_node
- behaviortree_cpp
- behaviortree_ros2
- panther_msgs
- panther_utils
- rclcpp
- sensor_msgs
- std_msgs)
+ ament_target_dependencies(${PROJECT_NAME}_test_lights_manager_node
+ ${PACKAGE_DEPENDENCIES})
ament_add_gtest(
${PROJECT_NAME}_test_lights_behavior_tree
@@ -235,15 +234,8 @@ if(BUILD_TESTING)
${PROJECT_NAME}_test_lights_behavior_tree
PUBLIC $
$)
- ament_target_dependencies(
- ${PROJECT_NAME}_test_lights_behavior_tree
- behaviortree_cpp
- behaviortree_ros2
- panther_msgs
- panther_utils
- rclcpp
- sensor_msgs
- std_msgs)
+ ament_target_dependencies(${PROJECT_NAME}_test_lights_behavior_tree
+ ${PACKAGE_DEPENDENCIES})
ament_add_gtest(
${PROJECT_NAME}_test_safety_manager_node test/test_safety_manager_node.cpp
@@ -252,15 +244,8 @@ if(BUILD_TESTING)
${PROJECT_NAME}_test_safety_manager_node
PUBLIC $
$)
- ament_target_dependencies(
- ${PROJECT_NAME}_test_safety_manager_node
- behaviortree_cpp
- behaviortree_ros2
- panther_msgs
- panther_utils
- rclcpp
- sensor_msgs
- std_msgs)
+ ament_target_dependencies(${PROJECT_NAME}_test_safety_manager_node
+ ${PACKAGE_DEPENDENCIES})
ament_add_gtest(
${PROJECT_NAME}_test_safety_behavior_tree
@@ -270,16 +255,8 @@ if(BUILD_TESTING)
${PROJECT_NAME}_test_safety_behavior_tree
PUBLIC $
$)
- ament_target_dependencies(
- ${PROJECT_NAME}_test_safety_behavior_tree
- behaviortree_cpp
- behaviortree_ros2
- panther_msgs
- panther_utils
- rclcpp
- sensor_msgs
- std_msgs
- std_srvs)
+ ament_target_dependencies(${PROJECT_NAME}_test_safety_behavior_tree
+ ${PACKAGE_DEPENDENCIES})
endif()
ament_package()
diff --git a/panther_manager/CONFIGURATION.md b/panther_manager/CONFIGURATION.md
index 3317e43a..a0a5beb0 100644
--- a/panther_manager/CONFIGURATION.md
+++ b/panther_manager/CONFIGURATION.md
@@ -93,6 +93,14 @@ For a BehaviorTree project to work correctly, it must contain a tree with correc
### Trees
+#### Docking
+
+A tree responsible for waiting for the joystick command and trigger `Docking`/`Undocking` action.
+
+
+
+
+
#### Lights
A tree responsible for scheduling animations displayed on the Bumper Lights based on the Husarion Panther robot's system state.
@@ -206,6 +214,7 @@ To use your customized project, you can modify the `bt_project_file` ROS paramet
Groot2 also provides a real-time visualization tool that allows you to see and debug actively running trees. To use this tool with trees launched with the `panther_manager` package, you need to specify the port associated with the tree you want to visualize. The ports for each tree are listed below:
+- Docking tree: `10.15.20.2:4444`
- Lights tree: `10.15.20.2:5555`
- Safety tree: `10.15.20.2:6666`
- Shutdown tree: `10.15.20.2:7777`
diff --git a/panther_manager/behavior_trees/DockingBT.btproj b/panther_manager/behavior_trees/DockingBT.btproj
new file mode 100644
index 00000000..2752c0d3
--- /dev/null
+++ b/panther_manager/behavior_trees/DockingBT.btproj
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+Topic name for sensor_msgs/Joy message type
+ Max timeout to accept the msg
+ Vector of provided axis inputs
+ Vector of provided button inputs
+
+
+ bool value indicating whether to use dock ID
+ dock ID
+ type of the dock
+ maximum staging time
+ bool value indicating whether to navigate to
+ staging pose
+ action name to call
+
+
+ type of the dock
+ maximum time to get back to the staging pose
+ action name to call
+
+
+
diff --git a/panther_manager/behavior_trees/docking.xml b/panther_manager/behavior_trees/docking.xml
new file mode 100644
index 00000000..042e221b
--- /dev/null
+++ b/panther_manager/behavior_trees/docking.xml
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Topic name for sensor_msgs/Joy message type
+ Max timeout to accept the msg
+ Vector of provided axis inputs
+ Vector of provided button inputs
+
+
+ bool value indicating whether to use dock ID
+ dock ID
+ type of the dock
+ maximum staging time
+ bool value indicating whether to navigate to
staging pose
+ action name to call
+
+
+ type of the dock
+ maximum time to get back to the staging pose
+ action name to call
+
+
+
+
diff --git a/panther_manager/config/docking_manager.yaml b/panther_manager/config/docking_manager.yaml
new file mode 100644
index 00000000..28879a8b
--- /dev/null
+++ b/panther_manager/config/docking_manager.yaml
@@ -0,0 +1,12 @@
+/**:
+ docking_manager:
+ ros__parameters:
+ timer_frequency: 50.0
+ bt_server_port: 4444
+ ros_communication_timeout:
+ availability: 2.0
+ response: 1.0
+ ros_plugin_libs:
+ - check_joy_msg_bt_node
+ - dock_robot_bt_node
+ - undock_robot_bt_node
diff --git a/panther_manager/config/lights_manager.yaml b/panther_manager/config/lights_manager.yaml
index cea2a5e4..f9033f68 100644
--- a/panther_manager/config/lights_manager.yaml
+++ b/panther_manager/config/lights_manager.yaml
@@ -2,6 +2,7 @@
lights_manager:
ros__parameters:
timer_frequency: 10.0
+ bt_server_port: 5555
battery:
percent:
window_len: 6
diff --git a/panther_manager/include/panther_manager/behavior_tree_utils.hpp b/panther_manager/include/panther_manager/behavior_tree_utils.hpp
index 5ee2331e..db7f3212 100644
--- a/panther_manager/include/panther_manager/behavior_tree_utils.hpp
+++ b/panther_manager/include/panther_manager/behavior_tree_utils.hpp
@@ -26,6 +26,9 @@
#include "behaviortree_cpp/utils/shared_library.h"
#include "behaviortree_ros2/plugins.hpp"
+#include
+#include
+
namespace panther_manager::behavior_tree_utils
{
@@ -93,28 +96,75 @@ inline std::string GetLoggerPrefix(const std::string & bt_node_name)
namespace BT
{
/**
- * @brief Converts a string to a vector of integers.
+ * @brief Converts a string to a vector of float.
*
* @param str The string to convert.
- * @return std::vector The vector of integers.
+ * @return std::vector The vector of float.
*
- * @throw BT::RuntimeError Throws when there is no input or cannot parse int.
+ * @throw BT::RuntimeError Throws when there is no input or cannot parse float.
*/
template <>
-inline std::vector convertFromString>(StringView str)
+inline std::vector convertFromString>(StringView str)
{
- auto parts = BT::splitString(str, ';');
- if (!parts.size()) {
- throw BT::RuntimeError("invalid input");
- } else {
- std::vector result;
- for (auto & part : parts) {
- result.push_back(convertFromString(part));
- }
-
- return result;
+ auto parts = splitString(str, ';');
+ std::vector output;
+ output.reserve(parts.size());
+ for (const StringView & part : parts) {
+ output.push_back(convertFromString(part));
}
+ return output;
}
+
+/**
+ * @brief Converts a string to a PoseStamped message.
+ *
+ * The string format should be "x;y;z;roll;pitch;yaw;frame_id" where:
+ * - x, y, z: Position coordinates.
+ * - roll, pitch, yaw: Rotation around axes XYZ.
+ * - frame_id: Coordinate frame ID (string).
+ *
+ * @param str The string to convert.
+ * @return geometry_msgs::msg::PoseStamped The converted PoseStamped message.
+ *
+ * @throw BT::RuntimeError Throws if the input is invalid or cannot be parsed.
+ */
+template <>
+inline geometry_msgs::msg::PoseStamped convertFromString(
+ StringView str)
+{
+ constexpr std::size_t expected_parts_size = 7;
+
+ auto parts = splitString(str, ';');
+ if (parts.size() != expected_parts_size) {
+ throw BT::RuntimeError(
+ "Invalid input for PoseStamped. Expected " + std::to_string(expected_parts_size) +
+ " values: x;y;z;roll;pitch;yaw;frame_id");
+ }
+
+ geometry_msgs::msg::PoseStamped pose_stamped;
+
+ try {
+ pose_stamped.pose.position.x = convertFromString(parts[0]);
+ pose_stamped.pose.position.y = convertFromString(parts[1]);
+ pose_stamped.pose.position.z = convertFromString(parts[2]);
+
+ const auto roll = convertFromString(parts[3]);
+ const auto pitch = convertFromString(parts[4]);
+ const auto yaw = convertFromString(parts[5]);
+ tf2::Quaternion quaternion;
+ quaternion.setRPY(roll, pitch, yaw);
+ pose_stamped.pose.orientation = tf2::toMsg(quaternion);
+
+ pose_stamped.header.frame_id = convertFromString(parts[6]);
+ pose_stamped.header.stamp = rclcpp::Clock().now();
+
+ } catch (const std::exception & e) {
+ throw BT::RuntimeError("Failed to convert string to PoseStamped: " + std::string(e.what()));
+ }
+
+ return pose_stamped;
+}
+
} // namespace BT
#endif // PANTHER_MANAGER_BEHAVIOR_TREE_UTILS_HPP_
diff --git a/panther_manager/include/panther_manager/docking_manager_node.hpp b/panther_manager/include/panther_manager/docking_manager_node.hpp
new file mode 100644
index 00000000..7c311b75
--- /dev/null
+++ b/panther_manager/include/panther_manager/docking_manager_node.hpp
@@ -0,0 +1,67 @@
+// Copyright 2024 Husarion sp. z o.o.
+//
+// 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.
+
+#ifndef PANTHER_MANAGER_DOCKING_MANAGER_NODE_HPP_
+#define PANTHER_MANAGER_DOCKING_MANAGER_NODE_HPP_
+
+#include
+#include
+
+#include
+#include
+
+#include
+
+#include "panther_manager/behavior_tree_manager.hpp"
+#include "panther_utils/moving_average.hpp"
+
+namespace panther_manager
+{
+
+using BoolMsg = std_msgs::msg::Bool;
+
+/**
+ * @brief This class is responsible for creating a BehaviorTree responsible for docking management,
+ * spinning it, and updating blackboard entries based on subscribed topics.
+ */
+class DockingManagerNode : public rclcpp::Node
+{
+public:
+ DockingManagerNode(
+ const std::string & node_name, const rclcpp::NodeOptions & options = rclcpp::NodeOptions());
+ ~DockingManagerNode() = default;
+
+ /**
+ * @brief Initializes the docking manager, setting up parameters and behavior tree.
+ * @throws std::runtime_error if initialization fails
+ */
+ void Initialize();
+
+protected:
+ void DeclareParameters();
+ void RegisterBehaviorTree();
+
+ std::unique_ptr docking_tree_manager_;
+
+private:
+ void TimerCB();
+
+ rclcpp::TimerBase::SharedPtr docking_tree_timer_;
+
+ BT::BehaviorTreeFactory factory_;
+};
+
+} // namespace panther_manager
+
+#endif // PANTHER_MANAGER_DOCKING_MANAGER_NODE_HPP_
diff --git a/panther_manager/include/panther_manager/plugins/action/call_set_bool_service_node.hpp b/panther_manager/include/panther_manager/plugins/action/call_set_bool_service_node.hpp
index adafe1d4..96f40482 100644
--- a/panther_manager/include/panther_manager/plugins/action/call_set_bool_service_node.hpp
+++ b/panther_manager/include/panther_manager/plugins/action/call_set_bool_service_node.hpp
@@ -36,7 +36,8 @@ class CallSetBoolService : public BT::RosServiceNode
static BT::PortsList providedPorts()
{
- return providedBasicPorts({BT::InputPort("data", "true / false value")});
+ return providedBasicPorts(
+ {BT::InputPort("data", "Boolean value to send with the service request.")});
}
virtual bool setRequest(typename Request::SharedPtr & request) override;
diff --git a/panther_manager/include/panther_manager/plugins/action/call_set_led_animation_service_node.hpp b/panther_manager/include/panther_manager/plugins/action/call_set_led_animation_service_node.hpp
index 38a915a5..a0dcff1e 100644
--- a/panther_manager/include/panther_manager/plugins/action/call_set_led_animation_service_node.hpp
+++ b/panther_manager/include/panther_manager/plugins/action/call_set_led_animation_service_node.hpp
@@ -36,11 +36,11 @@ class CallSetLedAnimationService : public BT::RosServiceNode("id", "animation ID"),
- BT::InputPort("param", "optional parameter"),
- BT::InputPort("repeating", "indicates if animation should repeat"),
- });
+ return providedBasicPorts(
+ {BT::InputPort("id", "Animation ID to trigger."),
+ BT::InputPort("param", "Optional animation parameter."),
+ BT::InputPort(
+ "repeating", "Specifies whether the animation should repeated continuously.")});
}
virtual bool setRequest(typename Request::SharedPtr & request) override;
diff --git a/panther_manager/include/panther_manager/plugins/action/dock_robot_node.hpp b/panther_manager/include/panther_manager/plugins/action/dock_robot_node.hpp
index 2e4a5c03..7793bd21 100644
--- a/panther_manager/include/panther_manager/plugins/action/dock_robot_node.hpp
+++ b/panther_manager/include/panther_manager/plugins/action/dock_robot_node.hpp
@@ -48,22 +48,31 @@ class DockRobot : public BT::RosActionNode("use_dock_id", true, "Whether to use the dock's ID or dock pose fields"),
- BT::InputPort("dock_id", "Dock ID or name to use"),
- BT::InputPort("dock_type", "The dock plugin type, if using dock pose"),
- BT::InputPort(
- "max_staging_time", 1000.0, "Maximum time to navigate to the staging pose"),
- BT::InputPort(
- "navigate_to_staging_pose", true, "Whether to autonomously navigate to staging pose"),
+ return providedBasicPorts(
+ {BT::InputPort(
+ "use_dock_id", true, "Determines whether to use the dock's ID or dock pose fields."),
+ BT::InputPort(
+ "dock_id",
+ "Specifies the dock's ID or name from the dock database (used if 'use_dock_id' is true)."),
+ BT::InputPort(
+ "dock_pose",
+ "Specifies the dock's pose (used if 'use_dock_id' is false). Format: "
+ "\"x;y;z;roll;pitch;yaw;frame_id\""),
+ BT::InputPort(
+ "dock_type",
+ "Defines the type of dock being used when docking via pose. Not needed if only one dock "
+ "type is available."),
+ BT::InputPort(
+ "max_staging_time", 120.0,
+ "Maximum time allowed (in seconds) for navigating to the dock's staging pose."),
+ BT::InputPort(
+ "navigate_to_staging_pose", true,
+ "Specifies whether the robot should autonomously navigate to the staging pose."),
- BT::OutputPort(
- "success", "If the action was successful"),
- BT::OutputPort(
- "error_code", "Contextual error code, if any"),
- BT::OutputPort(
- "num_retries", "Number of retries attempted"),
- });
+ BT::OutputPort(
+ "error_code", "Returns an error code indicating the reason for failure, if any."),
+ BT::OutputPort(
+ "num_retries", "Reports the number of retry attempts made during the docking process.")});
}
};
diff --git a/panther_manager/include/panther_manager/plugins/action/shutdown_hosts_from_file_node.hpp b/panther_manager/include/panther_manager/plugins/action/shutdown_hosts_from_file_node.hpp
index 949565f6..c9eaa79e 100644
--- a/panther_manager/include/panther_manager/plugins/action/shutdown_hosts_from_file_node.hpp
+++ b/panther_manager/include/panther_manager/plugins/action/shutdown_hosts_from_file_node.hpp
@@ -39,10 +39,8 @@ class ShutdownHostsFromFile : public ShutdownHosts
static BT::PortsList providedPorts()
{
- return {
- BT::InputPort(
- "shutdown_hosts_file", "global path to YAML file with hosts to shutdown"),
- };
+ return {BT::InputPort(
+ "shutdown_hosts_file", "Absolute path to a YAML file listing the hosts to shut down.")};
}
private:
diff --git a/panther_manager/include/panther_manager/plugins/action/shutdown_single_host_node.hpp b/panther_manager/include/panther_manager/plugins/action/shutdown_single_host_node.hpp
index 630db650..006ac53a 100644
--- a/panther_manager/include/panther_manager/plugins/action/shutdown_single_host_node.hpp
+++ b/panther_manager/include/panther_manager/plugins/action/shutdown_single_host_node.hpp
@@ -38,14 +38,17 @@ class ShutdownSingleHost : public ShutdownHosts
static BT::PortsList providedPorts()
{
return {
- BT::InputPort("ip", "ip of the host to shutdown"),
- BT::InputPort("username", "user to log into while executing shutdown command"),
- BT::InputPort("port", "SSH communication port"),
- BT::InputPort("command", "command to execute on shutdown"),
- BT::InputPort("timeout", "time in seconds to wait for host to shutdown"),
+ BT::InputPort("ip", "IP address of the host to shut down."),
+ BT::InputPort(
+ "username", "Username to use for logging in and executing the shutdown command."),
+ BT::InputPort("port", "SSH port used for communication (default is usually 22)."),
+ BT::InputPort("command", "A command to execute on the remote host."),
+ BT::InputPort(
+ "timeout", "Maximum time (in seconds) to wait for the host to shut down."),
BT::InputPort(
- "ping_for_success", "ping host until it is not available or timeout is reached"),
- };
+ "ping_for_success",
+ "Whether to continuously ping the host until it becomes unreachable or the timeout is "
+ "reached.")};
}
private:
diff --git a/panther_manager/include/panther_manager/plugins/action/signal_shutdown_node.hpp b/panther_manager/include/panther_manager/plugins/action/signal_shutdown_node.hpp
index 7bc9709d..5eedaf7c 100644
--- a/panther_manager/include/panther_manager/plugins/action/signal_shutdown_node.hpp
+++ b/panther_manager/include/panther_manager/plugins/action/signal_shutdown_node.hpp
@@ -35,7 +35,7 @@ class SignalShutdown : public BT::SyncActionNode
static BT::PortsList providedPorts()
{
return {
- BT::InputPort("reason", "", "reason to shutdown robot"),
+ BT::InputPort("reason", "", "A reason to shutdown a robot."),
};
}
diff --git a/panther_manager/include/panther_manager/plugins/action/undock_robot_node.hpp b/panther_manager/include/panther_manager/plugins/action/undock_robot_node.hpp
index ade7380f..24589118 100644
--- a/panther_manager/include/panther_manager/plugins/action/undock_robot_node.hpp
+++ b/panther_manager/include/panther_manager/plugins/action/undock_robot_node.hpp
@@ -49,16 +49,15 @@ class UndockRobot : public BT::RosActionNode(
- "dock_type", "The dock plugin type, if not previous instance used for docking"),
- BT::InputPort(
- "max_undocking_time", 30.0, "Maximum time to get back to the staging pose"),
+ return providedBasicPorts(
+ {BT::InputPort(
+ "dock_type", "Specifies the dock plugin type to use for undocking."),
+ BT::InputPort(
+ "max_undocking_time", 30.0,
+ "Maximum allowable time (in seconds) to undock and return to the staging pose."),
- BT::OutputPort(
- "success", "If the action was successful"),
- BT::OutputPort("error_code", "Error code"),
- });
+ BT::OutputPort(
+ "error_code", "Returns an error code if the undocking process fails.")});
}
};
diff --git a/panther_manager/include/panther_manager/plugins/condition/are_buttons_pressed.hpp b/panther_manager/include/panther_manager/plugins/condition/check_bool_msg.hpp
similarity index 61%
rename from panther_manager/include/panther_manager/plugins/condition/are_buttons_pressed.hpp
rename to panther_manager/include/panther_manager/plugins/condition/check_bool_msg.hpp
index 8b0534b5..fbe52a88 100644
--- a/panther_manager/include/panther_manager/plugins/condition/are_buttons_pressed.hpp
+++ b/panther_manager/include/panther_manager/plugins/condition/check_bool_msg.hpp
@@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#ifndef PANTHER_MANAGER_PLUGINS_CONDITION_ARE_BUTTONS_PRESSED_HPP_
-#define PANTHER_MANAGER_PLUGINS_CONDITION_ARE_BUTTONS_PRESSED_HPP_
+#ifndef PANTHER_MANAGER_PLUGINS_CONDITION_CHECK_BOOL_MSG_HPP_
+#define PANTHER_MANAGER_PLUGINS_CONDITION_CHECK_BOOL_MSG_HPP_
#include
#include
@@ -22,34 +22,34 @@
#include
#include
-#include
+#include
#include "panther_manager/behavior_tree_utils.hpp"
namespace panther_manager
{
-class AreButtonsPressed : public BT::RosTopicSubNode
+// FIXME: There is no possibility to set QoS profile. Add it in the future to subscribe e_stop.
+class CheckBoolMsg : public BT::RosTopicSubNode
{
+ using BoolMsg = std_msgs::msg::Bool;
+
public:
- AreButtonsPressed(
+ CheckBoolMsg(
const std::string & name, const BT::NodeConfig & conf, const BT::RosNodeParams & params)
- : BT::RosTopicSubNode(name, conf, params)
+ : BT::RosTopicSubNode(name, conf, params)
{
}
- BT::NodeStatus onTick(const std::shared_ptr & last_msg);
+ BT::NodeStatus onTick(const BoolMsg::SharedPtr & last_msg);
static BT::PortsList providedPorts()
{
return providedBasicPorts(
- {BT::InputPort>("buttons", "state of buttons to accept a condition")});
+ {BT::InputPort("data", "Specifies the expected state of the data field.")});
}
-
-private:
- std::vector buttons_;
};
} // namespace panther_manager
-#endif // PANTHER_MANAGER_PLUGINS_CONDITION_ARE_BUTTONS_PRESSED_HPP_
+#endif // PANTHER_MANAGER_PLUGINS_CONDITION_CHECK_BOOL_MSG_HPP_
diff --git a/panther_manager/include/panther_manager/plugins/condition/check_joy_msg.hpp b/panther_manager/include/panther_manager/plugins/condition/check_joy_msg.hpp
new file mode 100644
index 00000000..a7966482
--- /dev/null
+++ b/panther_manager/include/panther_manager/plugins/condition/check_joy_msg.hpp
@@ -0,0 +1,68 @@
+// Copyright 2024 Husarion sp. z o.o.
+//
+// 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.
+
+#ifndef PANTHER_MANAGER_PLUGINS_CONDITION_CHECK_JOY_MSG_HPP_
+#define PANTHER_MANAGER_PLUGINS_CONDITION_CHECK_JOY_MSG_HPP_
+
+#include
+#include
+#include
+
+#include
+#include
+
+#include
+
+#include "panther_manager/behavior_tree_utils.hpp"
+
+namespace panther_manager
+{
+
+class CheckJoyMsg : public BT::RosTopicSubNode
+{
+ using JoyMsg = sensor_msgs::msg::Joy;
+
+public:
+ CheckJoyMsg(
+ const std::string & name, const BT::NodeConfig & conf, const BT::RosNodeParams & params)
+ : BT::RosTopicSubNode(name, conf, params)
+ {
+ }
+
+ BT::NodeStatus onTick(const JoyMsg::SharedPtr & last_msg);
+
+ static BT::PortsList providedPorts()
+ {
+ return providedBasicPorts(
+ {BT::InputPort>(
+ "axes", "",
+ "Specifies the expected state of the axes field. An empty string (\"\") means the values "
+ "are ignored."),
+ BT::InputPort>(
+ "buttons", "",
+ "Specifies the expected state of the buttons field. An empty string (\"\") means values "
+ "are ignored."),
+ BT::InputPort(
+ "timeout", 0.0, "Maximum allowable time delay to accept the condition.")});
+ }
+
+private:
+ bool checkAxes(const JoyMsg::SharedPtr & last_msg);
+ bool checkButtons(const JoyMsg::SharedPtr & last_msg);
+ bool checkTimeout(const JoyMsg::SharedPtr & last_msg);
+};
+
+} // namespace panther_manager
+
+#endif // PANTHER_MANAGER_PLUGINS_CONDITION_CHECK_JOY_MSG_HPP_
diff --git a/panther_manager/include/panther_manager/plugins/decorator/tick_after_timeout_node.hpp b/panther_manager/include/panther_manager/plugins/decorator/tick_after_timeout_node.hpp
index 20a273e5..af58ec3b 100644
--- a/panther_manager/include/panther_manager/plugins/decorator/tick_after_timeout_node.hpp
+++ b/panther_manager/include/panther_manager/plugins/decorator/tick_after_timeout_node.hpp
@@ -31,7 +31,8 @@ class TickAfterTimeout : public BT::DecoratorNode
static BT::PortsList providedPorts()
{
- return {BT::InputPort("timeout", "time in s to wait before ticking child again")};
+ return {BT::InputPort(
+ "timeout", "Time in seconds to wait before ticking the child node again.")};
}
private:
diff --git a/panther_manager/launch/manager.launch.py b/panther_manager/launch/manager.launch.py
index 14f4c8c7..c2ab3b18 100644
--- a/panther_manager/launch/manager.launch.py
+++ b/panther_manager/launch/manager.launch.py
@@ -32,6 +32,15 @@ def generate_launch_description():
panther_version = EnvironmentVariable(name="PANTHER_ROBOT_VERSION", default_value="1.0")
panther_manager_dir = FindPackageShare("panther_manager")
+ docking_bt_project_path = LaunchConfiguration("docking_bt_project_path")
+ declare_docking_bt_project_path_arg = DeclareLaunchArgument(
+ "docking_bt_project_path",
+ default_value=PathJoinSubstitution(
+ [panther_manager_dir, "behavior_trees", "DockingBT.btproj"]
+ ),
+ description="Path to BehaviorTree project file, responsible for docking management.",
+ )
+
lights_bt_project_path = LaunchConfiguration("lights_bt_project_path")
declare_lights_bt_project_path_arg = DeclareLaunchArgument(
"lights_bt_project_path",
@@ -78,6 +87,18 @@ def generate_launch_description():
description="Whether simulation is used",
)
+ docking_manager_node = Node(
+ package="panther_manager",
+ executable="docking_manager_node",
+ name="docking_manager",
+ parameters=[
+ PathJoinSubstitution([panther_manager_dir, "config", "docking_manager.yaml"]),
+ {"bt_project_path": docking_bt_project_path},
+ ],
+ namespace=namespace,
+ emulate_tty=True,
+ )
+
lights_manager_node = Node(
package="panther_manager",
executable="lights_manager_node",
@@ -107,11 +128,13 @@ def generate_launch_description():
)
actions = [
+ declare_docking_bt_project_path_arg,
declare_lights_bt_project_path_arg,
declare_safety_bt_project_path_arg,
declare_namespace_arg,
declare_shutdown_hosts_config_path_arg,
declare_use_sim_arg,
+ docking_manager_node,
lights_manager_node,
safety_manager_node,
]
diff --git a/panther_manager/package.xml b/panther_manager/package.xml
index 6da23d97..a9cd5f76 100644
--- a/panther_manager/package.xml
+++ b/panther_manager/package.xml
@@ -19,15 +19,18 @@
behaviortree_cpp
behaviortree_ros2
+ geometry_msgs
iputils-ping
libboost-dev
libssh-dev
+ opennav_docking_msgs
panther_msgs
panther_utils
rclcpp
rclcpp_action
sensor_msgs
std_srvs
+ tf2_geometry_msgs
yaml-cpp
ament_lint_auto
diff --git a/panther_manager/src/docking_manager_node.cpp b/panther_manager/src/docking_manager_node.cpp
new file mode 100644
index 00000000..8cd485f3
--- /dev/null
+++ b/panther_manager/src/docking_manager_node.cpp
@@ -0,0 +1,122 @@
+// Copyright 2024 Husarion sp. z o.o.
+//
+// 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.
+
+#include "panther_manager/docking_manager_node.hpp"
+
+#include
+#include
+#include
+#include