From e59ec5152832304461fb82f712db077bfc408501 Mon Sep 17 00:00:00 2001 From: Kalana Ratnayake Date: Fri, 22 May 2026 07:29:25 +0000 Subject: [PATCH 1/2] integrating libaria --- .gitmodules | 3 + README.md | 2 +- amr_core/CMakeLists.txt | 15 +- amr_core/include/amr_core/client_node.hpp | 150 ----- amr_core/include/amr_core/core_node.hpp | 433 ++----------- .../amr_core/interfaces/arcl_interface.hpp | 260 -------- .../amr_core/interfaces/drive_interface.hpp | 591 +++++++++-------- .../amr_core/interfaces/laser_interface.hpp | 525 ++++++++++++++-- .../interfaces/map_file_interface.hpp | 593 ------------------ .../amr_core/interfaces/status_interface.hpp | 219 +++++++ .../include/amr_core/socket/socket_common.hpp | 111 ---- .../include/amr_core/socket/socket_driver.hpp | 541 ---------------- .../amr_core/socket/socket_listener.hpp | 383 ----------- .../amr_core/socket/socket_taskmaster.hpp | 410 ------------ .../amr_core/utils/libaria_runtime.hpp | 71 +++ amr_core/include/amr_core/utils/parser.hpp | 96 --- amr_core/package.xml | 1 + amr_core/src/client_node.cpp | 11 - amr_ros/config/parameters.yaml | 80 +-- libaria | 1 + 20 files changed, 1198 insertions(+), 3298 deletions(-) create mode 100644 .gitmodules delete mode 100644 amr_core/include/amr_core/client_node.hpp delete mode 100644 amr_core/include/amr_core/interfaces/arcl_interface.hpp delete mode 100644 amr_core/include/amr_core/interfaces/map_file_interface.hpp create mode 100644 amr_core/include/amr_core/interfaces/status_interface.hpp delete mode 100644 amr_core/include/amr_core/socket/socket_common.hpp delete mode 100644 amr_core/include/amr_core/socket/socket_driver.hpp delete mode 100644 amr_core/include/amr_core/socket/socket_listener.hpp delete mode 100644 amr_core/include/amr_core/socket/socket_taskmaster.hpp create mode 100644 amr_core/include/amr_core/utils/libaria_runtime.hpp delete mode 100644 amr_core/include/amr_core/utils/parser.hpp delete mode 100644 amr_core/src/client_node.cpp create mode 160000 libaria diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..3b2227c5 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "libaria"] + path = libaria + url = https://github.com/CollaborativeRoboticsLab/libaria.git diff --git a/README.md b/README.md index cd4bbbff..ca411d2a 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ sudo apt install ros-humble-navigation2 ros-humble-nav2-bringup ros-humble-slam- Clone the repositories into the `src` folder by ```sh -git clone https://github.com/CollaborativeRoboticsLab/omron_amr.git +git clone --recursive https://github.com/CollaborativeRoboticsLab/omron_amr.git ``` Build by diff --git a/amr_core/CMakeLists.txt b/amr_core/CMakeLists.txt index 83c2ab99..407e41c5 100644 --- a/amr_core/CMakeLists.txt +++ b/amr_core/CMakeLists.txt @@ -22,12 +22,16 @@ find_package(control_msgs REQUIRED) find_package(visualization_msgs REQUIRED) find_package(ament_index_cpp REQUIRED) find_package(tf2_ros REQUIRED) +find_package(libaria CONFIG REQUIRED) + +set(AMR_CORE_ARIA_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../libaria") include_directories( include ) add_executable(${PROJECT_NAME} src/core_node.cpp) +target_compile_definitions(${PROJECT_NAME} PRIVATE AMR_CORE_ARIA_DIR="${AMR_CORE_ARIA_DIR}") ament_target_dependencies(${PROJECT_NAME} rclcpp rclcpp_action @@ -42,19 +46,12 @@ ament_target_dependencies(${PROJECT_NAME} ament_index_cpp ) - -add_executable(client src/client_node.cpp) -ament_target_dependencies(client - rclcpp - rclcpp_action - std_msgs - amr_msgs - geometry_msgs +target_link_libraries(${PROJECT_NAME} + libaria::ArNetworking ) install(TARGETS ${PROJECT_NAME} - client DESTINATION lib/${PROJECT_NAME} ) diff --git a/amr_core/include/amr_core/client_node.hpp b/amr_core/include/amr_core/client_node.hpp deleted file mode 100644 index 54021d3e..00000000 --- a/amr_core/include/amr_core/client_node.hpp +++ /dev/null @@ -1,150 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include - -class ClientNode : public rclcpp::Node { -public: - using ActionT = amr_msgs::action::Action; - using ClientT = rclcpp_action::Client; - using GoalHandle = rclcpp_action::ClientGoalHandle; - - /** - * @brief constructor - */ - ClientNode() - : rclcpp::Node("amr_client_node") - {} - - /** - * - */ - void initialize() - { - // Parameters - this->declare_parameter("command_type", "none"); // none|goto_goal|execute_macro|dock|undock - this->declare_parameter("goal_name", "Goal1"); - this->declare_parameter("macro_name", "Macro1"); - this->declare_parameter("exit_on_result", false); - - // Action client - ac_ = rclcpp_action::create_client(shared_from_this(), "action_server"); - - // Defer optional one-shot command to allow executor to start - using namespace std::chrono_literals; - start_timer_ = this->create_wall_timer(0ms, [this]() { - start_timer_->cancel(); - auto cmd = get_param("command_type"); - if (cmd == "goto_goal") { - goto_goal(get_param("goal_name")); - } else if (cmd == "execute_macro") { - execute_macro(get_param("macro_name")); - } else if (cmd == "dock") { - dock(); - } else if (cmd == "undock") { - undock(); - } - }); - } - -private: - // ========== High-level commands ========== - void goto_goal(const std::string& name) { - ActionT::Goal goal; - goal.command = "goto " + name; - goal.identifier = { "Arrived at " + name }; - send_goal(goal); - } - - void execute_macro(const std::string& name) { - ActionT::Goal goal; - goal.command = "executeMacro " + name; - goal.identifier = { "Completed macro " + name }; - send_goal(goal); - } - - void dock() { - ActionT::Goal goal; - goal.command = "dock"; - goal.identifier = { "Arrived at dock" }; - send_goal(goal); - } - - void undock() { - ActionT::Goal goal; - goal.command = "undock"; - goal.identifier = { "Stopped" }; - send_goal(goal); - } - - - // ========== Action helpers ========== - void send_goal(const ActionT::Goal& goal) { - // Wait briefly for server - using namespace std::chrono_literals; - if (!ac_->wait_for_action_server(2s)) { - RCLCPP_ERROR(this->get_logger(), "Action server not available"); - return; - } - - auto options = typename ClientT::SendGoalOptions(); - - options.feedback_callback = - [this](GoalHandle::SharedPtr, const std::shared_ptr feedback) { - RCLCPP_INFO(this->get_logger(), "%s", feedback->feed_msg.c_str()); - }; - - options.result_callback = - [this](const GoalHandle::WrappedResult& result) { - switch (result.code) { - case rclcpp_action::ResultCode::SUCCEEDED: - RCLCPP_INFO(this->get_logger(), "%s", result.result->res_msg.c_str()); - break; - case rclcpp_action::ResultCode::ABORTED: - RCLCPP_WARN(this->get_logger(), "Aborted: %s", result.result ? result.result->res_msg.c_str() : ""); - break; - case rclcpp_action::ResultCode::CANCELED: - RCLCPP_WARN(this->get_logger(), "Canceled"); - break; - default: - RCLCPP_ERROR(this->get_logger(), "Unknown result code"); - break; - } - if (get_param("exit_on_result")) { - rclcpp::shutdown(); - } - }; - - options.goal_response_callback = - [this](const GoalHandle::SharedPtr& goal_handle) { - if (!goal_handle) { - RCLCPP_ERROR(this->get_logger(), "Goal was rejected by server"); - } else { - RCLCPP_INFO(this->get_logger(), "Goal accepted by server, waiting for result"); - } - }; - - ac_->async_send_goal(goal, options); - } - - // ========== Math ========== - template - T get_param(const std::string& name) const { - return this->get_parameter(name).get_parameter_value().template get(); - } - -private: - rclcpp_action::Client::SharedPtr ac_; - - rclcpp::TimerBase::SharedPtr start_timer_; -}; \ No newline at end of file diff --git a/amr_core/include/amr_core/core_node.hpp b/amr_core/include/amr_core/core_node.hpp index df55da71..86a5fd27 100644 --- a/amr_core/include/amr_core/core_node.hpp +++ b/amr_core/include/amr_core/core_node.hpp @@ -1,28 +1,12 @@ #pragma once #include -#include -#include #include #include -#include -#include -#include -#include -#include -#include - -#include "amr_core/socket/socket_listener.hpp" -#include "amr_core/socket/socket_driver.hpp" -#include "amr_core/socket/socket_taskmaster.hpp" -#include "amr_core/utils/amr_exception.hpp" -#include "amr_core/interfaces/map_file_interface.hpp" +#include "amr_core/interfaces/status_interface.hpp" #include "amr_core/interfaces/laser_interface.hpp" #include "amr_core/interfaces/drive_interface.hpp" -#include "amr_core/interfaces/arcl_interface.hpp" - -using namespace std::chrono_literals; class CoreNode : public rclcpp::Node { @@ -32,389 +16,110 @@ class CoreNode : public rclcpp::Node } /** - * @brief This function initializes the core node by reading parameters, setting up publishers, services, and action - * servers, and establishing connections to the robot. + * @brief Initializes and wires the libaria-backed interfaces used by the core node. */ void initialize() { - // Shared connection params (service + action) - ip_ = this->declare_parameter("robot.ip", "127.0.0.1"); - port_ = this->declare_parameter("robot.port", 0); - passwd_ = this->declare_parameter("robot.password", ""); - - // listener endpoint (defaults to same ip/port if not set) - host_ip_ = this->declare_parameter("host.ip", ip_); - host_port_ = this->declare_parameter("host.port", port_); - - // Publish source data from listener - publish_source_ = this->declare_parameter("source_data.publish", false); - publish_frequency_ = this->declare_parameter("source_data.frequency", 5); - pub_interval_ms_ = int(1000 / publish_frequency_); - reset_odometer_on_startup_ = this->declare_parameter("driver.reset_odometer_on_startup", true); - - // SocketDriver (low-level) - RCLCPP_INFO(this->get_logger(), "Initializing SocketDriver.."); - try - { - driver_ = std::make_shared(this->shared_from_this()); - driver_->connect(ip_, static_cast(port_)); - driver_->login(passwd_); - - RCLCPP_INFO(this->get_logger(), "SocketDriver connected to %s:%d", ip_.c_str(), port_); - } - catch (const amr_exception& e) - { - RCLCPP_ERROR(this->get_logger(), "SocketDriver initialization failed: %s", e.what()); - } - - // SocketTaskmaster (high-level) - RCLCPP_INFO(this->get_logger(), "Initializing SocketTaskmaster.."); - try - { - task_master_ = std::make_shared(this->shared_from_this()); - task_master_->connect(ip_, port_); - task_master_->login(passwd_); - - RCLCPP_INFO(this->get_logger(), "ScoketTaskmaster is up @ %s:%d", ip_.c_str(), port_); - } - catch (const amr_exception& ex) - { - RCLCPP_FATAL(this->get_logger(), "SocketTaskmaster initialization failed: %s", ex.what()); - } - - // SocketListener - RCLCPP_INFO(this->get_logger(), "Initializing SocketListener.."); - try - { - listener_ = std::make_shared(this->shared_from_this(), host_ip_, host_port_); - if (!listener_->begin()) - RCLCPP_ERROR(this->get_logger(), "SocketListener begin() failed"); - else - RCLCPP_INFO(this->get_logger(), "SocketListener started on %s:%d", host_ip_.c_str(), host_port_); - } - catch (const amr_exception& ex) - { - RCLCPP_ERROR(this->get_logger(), "SocketListener error: %s", ex.what()); - } - - // Publish source data from listener - if (publish_source_) - { - RCLCPP_INFO(this->get_logger(), "Publishing source data from listener enabled"); - - status_pub_ = this->create_publisher("amr/source/status", 10); - laser_pub_ = this->create_publisher("amr/source/laser", 10); - odom_pub_ = this->create_publisher("amr/source/odom", 10); - app_fault_query_pub_ = this->create_publisher("amr/source/application_fault_query", 10); - faults_get_pub_ = this->create_publisher("amr/source/faults_get", 10); - query_faults_pub_ = this->create_publisher("amr/source/query_faults", 10); - } - else - { - RCLCPP_INFO(this->get_logger(), "Publishing source data from listener is disabled"); - } - - // Publisher timer - main_timer = - this->create_wall_timer(std::chrono::milliseconds(pub_interval_ms_), std::bind(&CoreNode::main_loop, this)); - - // Publish map data from file - publish_map_ = this->declare_parameter("map_data.publish", false); - if (publish_map_) - { - data_point_marker_ = std::make_shared(this->shared_from_this()); - data_point_marker_->initialize(); - RCLCPP_INFO(this->get_logger(), "Publishing map data from file enabled"); - } - - // Publish laser scans - publish_laser_scans_ = this->declare_parameter("laser_scans.publish", true); - if (publish_laser_scans_) - { - laser_scans_ = std::make_shared(this->shared_from_this()); - laser_scans_->initialize(); - RCLCPP_INFO(this->get_logger(), "Publishing laser scans enabled"); - } - - // DriverInterface - odom_driver_ = std::make_shared(this->shared_from_this(), driver_); - odom_driver_->initialize(); - - // ARCL service + action - enable_arcl_access_ = this->declare_parameter("arcl.enable", false); - timeout_ms_ = this->declare_parameter("arcl.timeout_ms", 15000); - if (enable_arcl_access_) - { - RCLCPP_INFO(this->get_logger(), "ARCL interface is enabled"); - arcl_ros = std::make_shared(this->shared_from_this(), driver_, task_master_, timeout_ms_); - arcl_ros->initialize(); - } - - if (reset_odometer_on_startup_) - { - RCLCPP_INFO(this->get_logger(), "Resetting odometer.."); - std::string response; - int req_id = driver_->queue_command("odometerReset", ""); - bool got_response = driver_->wait_for_response(req_id, response, timeout_ms_); - if (!got_response) - RCLCPP_INFO(this->get_logger(), "Reset result: %s", response.c_str()); - } - else - { - RCLCPP_INFO(this->get_logger(), "Skipping odometer reset on startup"); - } - - RCLCPP_INFO(this->get_logger(), "CoreNode initialized"); - } - -private: - // -------- Publisher (SocketListener -> topics) -------- - /** - * @brief This function is called periodically by the publisher timer to poll the listener for new data and publish it - * to topics. - */ - void main_loop() - { - // Poll listener for new data - try - { - while (listener_ && listener_->poll_once(0)) + host_ = getOrDeclareParameter("robot.ip", "127.0.0.1"); + port_ = getOrDeclareParameter("robot.port", 7272); + user_ = getOrDeclareParameter("robot.user", "admin"); + password_ = getOrDeclareParameter("robot.password", ""); + protocol_ = getOrDeclareParameter("robot.protocol", "6MTX"); + publish_status_ = getOrDeclareParameter("status.publish", true); + publish_main_laser_ = getOrDeclareParameter("laser.main_laser.enabled", true); + publish_low_laser_ = getOrDeclareParameter("laser.low_laser.enabled", false); + + if (publish_status_) + { + try { + status_interface_ = std::make_shared(this->shared_from_this()); + status_interface_->initialize(host_, port_, user_, password_, protocol_); + RCLCPP_INFO(this->get_logger(), "Publishing status information enabled"); } - } - catch (const amr_exception& ex) - { - RCLCPP_ERROR(this->get_logger(), "Listener error: %s", ex.what()); - } - - pub_status(); - pub_laser(); - pub_odometer(); - pub_app_fault_query(); - pub_faults_get(); - pub_query_faults(); - } - - /** - * @brief This function publishes the robot's status information to the status topic. As an example. - * This include parsing the following fields from the listener: - * - * ExtendedStatusForHumans: Robot lost - * Status: Teleop driving - * StateOfCharge: 79.4 - * Location: 79031 -81597 20 - * LocalizationScore: 0.170543 - * Temperature: 36 - */ - void pub_status() - { - amr_msgs::msg::Status status_msg; - amr_msgs::msg::Location loc_msg; - - try - { - const auto& status_status = listener_->get_response("Status"); - const auto& status_batt = listener_->get_response("StateOfCharge"); - const auto& status_loc = listener_->get_response("Location"); - const auto& status_loc_sc = listener_->get_response("LocalizationScore"); - const auto& status_temp = listener_->get_response("Temperature"); - const auto& status_ext = listener_->get_response("ExtendedStatusForHumans"); - - status_msg.status = status_status.front(); - status_msg.extended_status = status_ext.front(); - status_msg.state_of_charge = std::stof(status_batt.front()); - status_msg.localization_score = std::stof(status_loc_sc.front()); - status_msg.temperature = std::stof(status_temp.front()); - - std::istringstream iss(status_loc.front()); - double x = 0.0, y = 0.0, th = 0.0; - if (iss >> x >> y >> th) + catch (const std::exception& ex) { - laser_x_ = x; - laser_y_ = y; - laser_th_ = th; - - loc_msg.x = x; - loc_msg.y = y; - loc_msg.theta = th; - status_msg.location = loc_msg; + status_interface_.reset(); + RCLCPP_ERROR(this->get_logger(), "StatusInterface initialization failed: %s", ex.what()); } - else - { - RCLCPP_WARN(this->get_logger(), "Value error with location coordinates. Using zeros."); - } - } - catch (const std::out_of_range&) - { - RCLCPP_DEBUG(this->get_logger(), "Status data not available yet"); - } - catch (const std::exception& ex) - { - RCLCPP_ERROR(this->get_logger(), "Status parse error: %s", ex.what()); } - - if (publish_source_ && status_pub_) - status_pub_->publish(status_msg); - } - - void pub_laser() - { - try - { - const auto& scans = listener_->get_response("RangeDeviceGetCurrent"); - std_msgs::msg::String msg; - msg.data = scans.front(); - - if (publish_source_ && laser_pub_) - laser_pub_->publish(msg); - - if (publish_laser_scans_) - laser_scans_->update(msg, laser_x_, laser_y_, laser_th_); - } - catch (const std::out_of_range&) + else { + RCLCPP_INFO(this->get_logger(), "Publishing status information disabled"); } - } - void pub_odometer() - { - try + if (publish_main_laser_ || publish_low_laser_) { - const auto& odom = listener_->get_response("Odometer"); - std::string joined; - for (size_t i = 0; i < odom.size(); ++i) + try { - if (i) - joined += ' '; - joined += odom[i]; + laser_scans_ = std::make_shared(this->shared_from_this()); + laser_scans_->initialize(host_, port_, user_, password_, protocol_); + RCLCPP_INFO(this->get_logger(), "Publishing laser scans enabled"); } - std_msgs::msg::String msg; - msg.data = joined; - if (publish_source_ && odom_pub_) - odom_pub_->publish(msg); - - if (odom_driver_) - odom_driver_->update(msg); - } - catch (const std::out_of_range&) - { - } - } - - void pub_app_fault_query() - { - try - { - const auto& query = listener_->get_response("applicationFaultQuery"); - std::string joined; - for (size_t i = 0; i < query.size(); ++i) + catch (const std::exception& ex) { - if (i) - joined += ' '; - joined += query[i]; + laser_scans_.reset(); + RCLCPP_ERROR(this->get_logger(), "LaserInterface initialization failed: %s", ex.what()); } - std_msgs::msg::String msg; - msg.data = joined; - if (publish_source_ && app_fault_query_pub_) - app_fault_query_pub_->publish(msg); } - catch (const std::out_of_range&) + else { + RCLCPP_INFO(this->get_logger(), "Publishing laser scans disabled"); } - } - void pub_faults_get() - { + // DriverInterface try { - const auto& faults = listener_->get_response("FaultList"); - std::string joined; - for (size_t i = 0; i < faults.size(); ++i) - { - if (i) - joined += ' '; - joined += faults[i]; - } - std_msgs::msg::String msg; - msg.data = joined; - if (publish_source_ && faults_get_pub_) - faults_get_pub_->publish(msg); + driver_ = std::make_shared(this->shared_from_this()); + driver_->initialize(host_, port_, user_, password_, protocol_); } - catch (const std::out_of_range&) + catch (const std::exception& ex) { + driver_.reset(); + RCLCPP_ERROR(this->get_logger(), "DriverInterface initialization failed: %s", ex.what()); } + + RCLCPP_INFO(this->get_logger(), "CoreNode initialized"); } - void pub_query_faults() +private: + /** + * @brief Helper function to get a parameter value or declare it with a default if it doesn't exist. + * @tparam T Parameter type. + * @param name Parameter name. + * @param default_value Default value to declare if parameter doesn't exist. + * @return Parameter value. + */ + template + T getOrDeclareParameter(const std::string& name, const T& default_value) { - try - { - const auto& faults = listener_->get_response("RobotFaultQuery"); - std::string joined; - for (size_t i = 0; i < faults.size(); ++i) - { - if (i) - joined += ' '; - joined += faults[i]; - } - std_msgs::msg::String msg; - msg.data = joined; - if (publish_source_ && query_faults_pub_) - query_faults_pub_->publish(msg); - } - catch (const std::out_of_range&) + if (!this->has_parameter(name)) { + return this->declare_parameter(name, default_value); } + + T value = default_value; + this->get_parameter(name, value); + return value; } private: - // Params - std::string ip_; - int port_{ 0 }; - std::string passwd_; - int publish_frequency_{ 5 }; - int pub_interval_ms_{ 200 }; - - std::string host_ip_; - int host_port_{ 0 }; - - double laser_x_{ 0.0 }, laser_y_{ 0.0 }, laser_th_{ 0.0 }; - - bool publish_source_{ false }; - bool reset_odometer_on_startup_{ true }; - - bool enable_arcl_access_{ false }; - - // Service (driver) - std::shared_ptr driver_; - - // Action (taskmaster) - std::shared_ptr task_master_; - - // Publisher (listener) - std::shared_ptr listener_; - rclcpp::TimerBase::SharedPtr main_timer; - - // Source Pubs - rclcpp::Publisher::SharedPtr status_pub_; - rclcpp::Publisher::SharedPtr laser_pub_; - rclcpp::Publisher::SharedPtr odom_pub_; - rclcpp::Publisher::SharedPtr app_fault_query_pub_; - rclcpp::Publisher::SharedPtr faults_get_pub_; - rclcpp::Publisher::SharedPtr query_faults_pub_; - - // Map pubs - bool publish_map_{ false }; - std::shared_ptr data_point_marker_; ///< Data point marker instance + // Status information + std::shared_ptr status_interface_; // Laser scans - bool publish_laser_scans_{ false }; std::shared_ptr laser_scans_; // DriverInterface - std::shared_ptr odom_driver_; - - // ARCL ROS interface - std::shared_ptr arcl_ros; - int timeout_ms_{ 15000 }; + std::shared_ptr driver_; + + // Parameters + std::string host_; + int port_{ 7272 }; + std::string user_; + std::string password_; + std::string protocol_; + bool publish_status_{ true }; + bool publish_main_laser_{ true }; + bool publish_low_laser_{ false }; }; \ No newline at end of file diff --git a/amr_core/include/amr_core/interfaces/arcl_interface.hpp b/amr_core/include/amr_core/interfaces/arcl_interface.hpp deleted file mode 100644 index b57cb32e..00000000 --- a/amr_core/include/amr_core/interfaces/arcl_interface.hpp +++ /dev/null @@ -1,260 +0,0 @@ -#include -#include -#include - -#include "rclcpp/rclcpp.hpp" - -#include -#include - -#include "amr_core/socket/socket_driver.hpp" -#include "amr_core/socket/socket_taskmaster.hpp" -#include "amr_core/utils/parser.hpp" -#include "amr_core/utils/amr_exception.hpp" - -/** - * @brief ROS 2 ARCL driver node (without direct socket code). - * Uses SocketDriver for ARCL communication. - */ -class ARCL_Interface -{ -public: - using ServiceARCL = amr_msgs::srv::ArclApi; - using ActionARCL = amr_msgs::action::Action; - using GoalHandle = rclcpp_action::ServerGoalHandle; - - /** - * @brief Constructor. Initializes publishers, subscribers, and parameters. - * @param node Shared pointer to rclcpp::Node. - */ - ARCL_Interface(rclcpp::Node::SharedPtr node, std::shared_ptr driver, - std::shared_ptr taskmaster, int service_timeout_ms = 15000) - { - node_ = node; - socket_driver_ = driver; - socket_taskmaster_ = taskmaster; - service_timeout_ms_ = service_timeout_ms; - } - - void initialize() - { - // Service server - if (!socket_driver_) - service_ = node_->create_service( - "arcl_api_service", std::bind(&ARCL_Interface::handle_service, this, std::placeholders::_1, std::placeholders::_2)); - else - RCLCPP_ERROR(node_->get_logger(), "Service server not created: driver unavailable"); - - // Action server - if (socket_taskmaster_) - action_server_ = rclcpp_action::create_server( - node_, "arcl_api_action", - std::bind(&ARCL_Interface::handle_goal, this, std::placeholders::_1, std::placeholders::_2), - std::bind(&ARCL_Interface::handle_cancel, this, std::placeholders::_1), - std::bind(&ARCL_Interface::handle_accepted, this, std::placeholders::_1)); - else - RCLCPP_ERROR(node_->get_logger(), "Action server not created: taskmaster unavailable"); - } - -private: - /** - * @brief This function handles incoming service requests to send ARCL commands to the robot. - */ - void handle_service(const std::shared_ptr req, std::shared_ptr resp) - { - std::string response; - bool got_response; - - // Check driver availability - if (!socket_driver_) - { - response = "ERROR: driver not available"; - got_response = true; - } - // Send command to robot - else - { - int req_id = socket_driver_->queue_command(req->command, req->line_identifier); - got_response = socket_driver_->wait_for_response(req_id, response, service_timeout_ms_); - } - - // Delegate queueing and response handling to SocketDriver - if (got_response) - resp->response = response; - else - resp->response = "TIMEOUT"; - } - - // -------- Action server (SocketTaskmaster) -------- - /** - * @brief This function is called when a new goal is received by the action server. - * - * @param uuid The unique identifier for the goal. - * @param goal The goal message containing the command and identifiers. - * @return rclcpp_action::GoalResponse Indicates whether the goal is accepted or rejected - */ - rclcpp_action::GoalResponse handle_goal(const rclcpp_action::GoalUUID&, std::shared_ptr) - { - return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE; - } - - /** - * @brief This function is called when a goal is requested to be canceled by the action client. - * - * @param goal_handle The handle of the goal to be canceled. - * @return rclcpp_action::CancelResponse Indicates whether the cancel request is accepted or rejected. - */ - rclcpp_action::CancelResponse handle_cancel(const std::shared_ptr) - { - return rclcpp_action::CancelResponse::ACCEPT; - } - - /** - * @brief This function is called when a new goal is accepted by the action server. - * - * @param goal_handle The handle of the accepted goal. - */ - void handle_accepted(const std::shared_ptr goal_handle) - { - action_thread_ = std::thread(&ARCL_Interface::execute, this, goal_handle); - action_thread_.detach(); - } - - /** - * @brief This function executes the action goal by sending commands to the robot and processing feedback. - * - * @param goal_handle The handle of the accepted goal. - */ - void execute(const std::shared_ptr goal_handle) - { - // Check taskmaster availability - if (!socket_taskmaster_) - { - auto result = std::make_shared(); - result->res_msg = "Taskmaster unavailable"; - goal_handle->abort(result); - return; - } - - Parser parser; - - // Get goal details - auto goal = goal_handle->get_goal(); - - std::vector end_lines; - - // Use first identifier as end line if available - if (!goal->identifier.empty()) - end_lines.emplace_back(goal->identifier.front()); - - try - { - // Send command to robot - socket_taskmaster_->push_command(goal->command, true, end_lines); - } - catch (const amr_exception& ex) - { - // Failed to send command - RCLCPP_ERROR(node_->get_logger(), "push_command error: %s", ex.what()); - - auto result = std::make_shared(); - result->res_msg = std::string("push_command failed: ") + ex.what(); - goal_handle->abort(result); - return; - } - - auto feedback = std::make_shared(); - auto result = std::make_shared(); - auto start = std::chrono::steady_clock::now(); - - while (rclcpp::ok()) - { - // Check for cancelation - if (goal_handle->is_canceling()) - { - result->res_msg = "Canceled"; - goal_handle->canceled(result); - return; - } - - // Wait for feedback or completion - SocketTaskmaster::WaitResult wr{ false, "", "" }; - try - { - wr = socket_taskmaster_->wait_command(100); - } - catch (const amr_exception& ex) - { - // Failed to get feedback - RCLCPP_ERROR(node_->get_logger(), "wait_command error: %s", ex.what()); - - result->res_msg = std::string("wait_command error: ") + ex.what(); - goal_handle->abort(result); - return; - } - - // Handle lack of feedback or no goal - if (!wr.feedback.empty() && wr.feedback.find("No goal") != std::string::npos) - { - result->res_msg = "No such goal"; - goal_handle->abort(result); - return; - } - - // Process feedback or completion - if (wr.complete) - { - feedback->feed_msg = wr.feedback; - goal_handle->publish_feedback(feedback); - result->res_msg = wr.result; - goal_handle->succeed(result); - RCLCPP_INFO(node_->get_logger(), "action_server: %s", result->res_msg.c_str()); - return; - } - else if (!wr.feedback.empty()) - { - auto pr = parser.process_arcl_server(wr.feedback); - feedback->feed_msg = pr.message; - goal_handle->publish_feedback(feedback); - - if (pr.code == Parser::Code::PASS) - { - result->res_msg = pr.message; - goal_handle->succeed(result); - return; - } - else if (pr.code == Parser::Code::FAIL) - { - result->res_msg = pr.message; - goal_handle->abort(result); - return; - } - } - // Check for timeout - if (std::chrono::steady_clock::now() - start > std::chrono::minutes(10)) - { - result->res_msg = "Timeout"; - goal_handle->abort(result); - return; - } - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } - - if (goal_handle->is_active()) - { - result->res_msg = "Node shutdown"; - goal_handle->abort(result); - } - } - - rclcpp::Node::SharedPtr node_; - std::shared_ptr socket_driver_; - std::shared_ptr socket_taskmaster_; - - rclcpp::Service::SharedPtr service_; - rclcpp_action::Server::SharedPtr action_server_; - - int service_timeout_ms_{ 15000 }; - - std::thread action_thread_; -}; \ No newline at end of file diff --git a/amr_core/include/amr_core/interfaces/drive_interface.hpp b/amr_core/include/amr_core/interfaces/drive_interface.hpp index 6bd9f63a..9bddbf82 100644 --- a/amr_core/include/amr_core/interfaces/drive_interface.hpp +++ b/amr_core/include/amr_core/interfaces/drive_interface.hpp @@ -1,9 +1,17 @@ #pragma once +#include +#include + #include #include #include #include +#include +#include +#include +#include + #include "rclcpp/rclcpp.hpp" #include #include "std_msgs/msg/empty.hpp" @@ -13,370 +21,412 @@ #include #include #include -#include "amr_core/socket/socket_driver.hpp" +#include "amr_core/utils/libaria_runtime.hpp" /** - * @brief ROS 2 ARCL driver node (without direct socket code). - * Uses SocketDriver for ARCL communication. + * @brief ROS 2 drive interface backed by libaria/ArNetworking. */ class DriverInterface { public: /** - * @brief Constructor. Initializes publishers, subscribers, and parameters. + * @brief Constructor. Initializes publishers, subscribers, and libaria callbacks. * @param node Shared pointer to rclcpp::Node. */ - DriverInterface(rclcpp::Node::SharedPtr node, std::shared_ptr driver) + DriverInterface(rclcpp::Node::SharedPtr node) + : node_(std::move(node)), ratio_drive_(&client_), update_callback_(this, &DriverInterface::handleUpdateNumbers) { - node_ = node; - socket_driver_ = driver; - tf_broadcaster_ = std::make_unique(node_); } + + ~DriverInterface() + { + client_.disconnect(); + } + /** * @brief Initializes parameters, publishers, and subscribers. + * + * This should be called after the node is fully constructed. + * @param host Robot server IP address. + * @param port Robot server port. + * @param user Username for robot server authentication. + * @param password Password for robot server authentication. + * @param protocol Optional protocol version to enforce on the robot server connection. + * + * Throws std::runtime_error if connection to the robot server fails or is rejected by the robot. */ - void initialize() + void initialize(const std::string& host, int port, const std::string& user, const std::string& password, + const std::string& protocol) { - // Parameters - publish_odom_ = node_->declare_parameter("driver.publish_odom", true); - publish_robot_tf_ = node_->declare_parameter("driver.publish_robot_tf", true); - subscribe_cmd_vel_ = node_->declare_parameter("driver.subscribe_cmd_vel", true); - subscribe_goal_pose_ = node_->declare_parameter("driver.subscribe_goal_pose", true); - subscribe_initial_pose_ = node_->declare_parameter("driver.subscribe_initial_pose", true); - subscribe_local_plan_ = node_->declare_parameter("driver.subscribe_localplan", false); - - odom_topic_ = node_->declare_parameter("driver.odom_topic", "amr/odom"); - cmd_vel_topic_ = node_->declare_parameter("driver.cmd_vel_topic", "amr/cmd_vel"); - odom_reset_topic = node_->declare_parameter("driver.odom_reset_topic", "amr/odomReset"); - goal_pose_topic_ = node_->declare_parameter("driver.goal_pose_topic", "goal_pose"); - initial_pose_topic_ = node_->declare_parameter("driver.initial_pose_topic", "initialpose"); - - odom_frame_ = node_->declare_parameter("driver.odom_frame", "odom"); - base_frame_ = node_->declare_parameter("driver.base_frame", "base_link"); - timeout_ms = node_->declare_parameter("driver.command_timeout_ms", 15000); - - expected_cmd_vel_freq_ = node_->declare_parameter("driver.expected_cmd_vel_freq", 20.0); - min_ang_speed_ = node_->declare_parameter("driver.min_angular_speed", -30); // deg/s - max_ang_speed_ = node_->declare_parameter("driver.max_angular_speed", 30); // deg/s - min_lin_speed_ = node_->declare_parameter("driver.min_linear_speed", -1000); // mm/s - max_lin_speed_ = node_->declare_parameter("driver.max_linear_speed", 1000); // mm/s - - unit_move_distance_ = node_->declare_parameter("driver.unit_move_distance", 100.0); // mm - unit_turn_angle_ = node_->declare_parameter("driver.unit_turn_angle", 10.0); // deg + host_ = host; + port_ = port; + user_ = user; + password_ = password; + protocol_ = protocol; + + odom_topic_ = getOrDeclareParameter("driver.odom_topic", "amr/odom"); + cmd_vel_topic_ = getOrDeclareParameter("driver.cmd_vel_topic", "amr/cmd_vel"); + stop_topic_ = getOrDeclareParameter("driver.stop_topic", "amr/stop"); + publish_odom_ = getOrDeclareParameter("driver.publish_odom", true); + publish_robot_tf_ = getOrDeclareParameter("driver.publish_robot_tf", true); + + odom_frame_ = getOrDeclareParameter("driver.odom_frame", "amr/odom"); + base_frame_ = getOrDeclareParameter("driver.base_frame", "amr/base_link"); + + expected_cmd_vel_freq_ = getOrDeclareParameter("driver.expected_cmd_vel_freq", 20.0); + min_ang_speed_ = getOrDeclareParameter("driver.min_angular_speed", -60.0); // deg/s + max_ang_speed_ = getOrDeclareParameter("driver.max_angular_speed", 60.0); // deg/s + min_lin_speed_ = getOrDeclareParameter("driver.min_linear_speed", -200.0); // mm/s + max_lin_speed_ = getOrDeclareParameter("driver.max_linear_speed", 1200.0); // mm/s + drive_throttle_pct_ = getOrDeclareParameter("driver.drive_throttle_pct", 100.0); + unsafe_drive_ = getOrDeclareParameter("driver.unsafe_drive", true); + cmd_vel_timeout_sec_ = getOrDeclareParameter("driver.cmd_vel_timeout_sec", 0.2); // Publishers if (publish_odom_) + { odom_pub_ = node_->create_publisher(odom_topic_, 10); + } // Subscribers stop_sub_ = node_->create_subscription( - "amr/stop", 10, std::bind(&DriverInterface::stopCB, this, std::placeholders::_1)); + stop_topic_, 10, std::bind(&DriverInterface::stopCB, this, std::placeholders::_1)); - odom_reset_sub_ = node_->create_subscription( - odom_reset_topic, 10, std::bind(&DriverInterface::odomResetCB, this, std::placeholders::_1)); - - if (subscribe_cmd_vel_ && !subscribe_local_plan_) - cmd_vel_sub_ = node_->create_subscription( - cmd_vel_topic_, 10, std::bind(&DriverInterface::cmdVelCB, this, std::placeholders::_1)); - else if (!subscribe_cmd_vel_ && subscribe_local_plan_) - local_plan_sub_ = node_->create_subscription( - "/local_plan", 10, std::bind(&DriverInterface::localPlanCB, this, std::placeholders::_1)); - else - RCLCPP_ERROR(node_->get_logger(), "DriverInterface: Cannot subscribe to both cmd_vel and local_plan. Please " - "choose one."); + cmd_vel_sub_ = node_->create_subscription( + cmd_vel_topic_, 10, std::bind(&DriverInterface::cmdVelCB, this, std::placeholders::_1)); - if (subscribe_goal_pose_) - goal_pose_sub_ = node_->create_subscription( - goal_pose_topic_, 10, std::bind(&DriverInterface::goalPoseCB, this, std::placeholders::_1)); + connectClient(); + configureHandlers(); + ratio_drive_.setThrottle(drive_throttle_pct_); + configureDriveMode(); + client_.runAsync(); - if (subscribe_initial_pose_) - initial_pose_sub_ = node_->create_subscription( - initial_pose_topic_, 10, std::bind(&DriverInterface::initialPoseCB, this, std::placeholders::_1)); + const int watchdog_ms = std::max(50, static_cast(1000.0 / std::max(expected_cmd_vel_freq_, 1.0))); + cmd_vel_watchdog_timer_ = node_->create_wall_timer(std::chrono::milliseconds(watchdog_ms), + std::bind(&DriverInterface::handleCmdVelWatchdog, this)); } +private: /** - * @brief Update odometry based on incoming string message. - * @param msg String message containing odometry data. - * - * Example input: "Odometer: mm deg