#include "amr_control/amr_control.h" #include #include #include #include #include namespace amr_control { AmrController::AmrController(ros::NodeHandle &nh, std::shared_ptr buffer) : initalized_(false), vda_5050_client_api_ptr_(nullptr), opc_ua_server_api_ptr_(nullptr), move_base_initialized_(false), loc_base_initialized_(false), monitor_ptr_(nullptr), loc_base_ptr_(nullptr), move_base_ptr_(nullptr), amr_safety_ptr_(nullptr), tf_core_ptr_(nullptr), loc_base_loader_("loc_core", "loc_core::BaseLocalization") { this->init(nh, buffer); } AmrController::AmrController() : initalized_(false), move_base_initialized_(false), loc_base_initialized_(false), vda_5050_client_api_ptr_(nullptr), opc_ua_server_api_ptr_(nullptr), monitor_ptr_(nullptr), loc_base_ptr_(nullptr), move_base_ptr_(nullptr), amr_safety_ptr_(nullptr), tf_converter_ptr_(nullptr), tf_core_ptr_(nullptr), loc_base_loader_("loc_core", "loc_core::BaseLocalization") { } AmrController::~AmrController() { // Wait for both threads to complete if (move_base_thr_) { move_base_thr_->join(); move_base_thr_.reset(); } if (loc_base_thr_) { loc_base_thr_->join(); loc_base_thr_.reset(); } if (server_thr_) { server_thr_->join(); server_thr_.reset(); } if (monitor_thr_) { monitor_thr_->join(); monitor_thr_.reset(); } if (opc_ua_server_api_ptr_) { opc_ua_server_api_ptr_.reset(); } if (vda_5050_client_api_ptr_) { vda_5050_client_api_ptr_.reset(); } // Stop costmap, cmd_vel and plan publishing if (amr_publisher_ptr_) { amr_publisher_ptr_->stopPublishing(); amr_publisher_ptr_->stopCmdVelPublishing(); amr_publisher_ptr_->stopPlanPublishing(); amr_publisher_ptr_.reset(); } } void AmrController::init(ros::NodeHandle &nh, std::shared_ptr buffer) { if (!initalized_) { nh_ = nh; tf_ = buffer; monitor_thr_ = std::make_shared( [this]() { this->initalizingMonitorHandle(nh_); this->threadHandle(); }); move_base_initialized_ = false; // Setup base localization loc_base_thr_ = std::make_shared( [this]() { this->initalizingLocalizationHandle(nh_); { std::unique_lock lock(init_loc_base_mutex_); loc_base_initialized_ = true; } init_loc_base_cv_.notify_one(); }); // Setup base navigation move_base_thr_ = std::make_shared( [this]() { std::unique_lock lock(init_loc_base_mutex_); init_loc_base_cv_.wait(lock, [this]() { return loc_base_initialized_; }); this->initalizingMoveBaseHandle(nh_); { std::lock_guard lock(init_move_base_mutex_); move_base_initialized_ = true; } init_move_base_cv_.notify_one(); // this->controllerDotuff(); }); // Setup server server_thr_ = std::make_shared( [this]() { std::unique_lock lock(init_move_base_mutex_); init_move_base_cv_.wait(lock, [this]() { return move_base_initialized_; }); this->initalizingComunicationHandle(nh_); }); ros::NodeHandle nh_core; is_detected_maker_sub_ = nh_core.subscribe("/is_detected_maker", 1, &AmrController::isDetectedMakerCallback, this); initalized_ = true; } } void AmrController::stop() { } void AmrController::initalizingComunicationHandle(ros::NodeHandle &nh) { this->count_ng_max_ = this->count_ok_max_ = 1; std::shared_ptr opc_ua_server_thr = std::make_shared( [this, nh]() { opc_ua_server_api_ptr_ = std::make_shared(nh, move_base_ptr_, loc_base_ptr_, monitor_ptr_); OpcUAServerAPI::muted_value_ = &this->muted_; OpcUAServerAPI::unLoad_excuted_ = std::bind(&AmrController::unLoadCallBack, this); OpcUAServerAPI::load_excuted_ = std::bind(&AmrController::loadCallBack, this); OpcUAServerAPI::belt_thread_ptr_ = &this->belt_thread_; OpcUAServerAPI::cur_belt_state_en_ = &this->cur_belt_state_en_; OpcUAServerAPI::belt_cancel_ = &this->cancel_; OpcUAServerAPI::arm_function_ptr_ = std::bind(&AmrController::ArmCallBack, this); OpcUAServerAPI::arm_thread_ptr_ = &this->arm_thread_; OpcUAServerAPI::arm_cancel_ = &this->enable_; OpcUAServerAPI::arm_go_home_ = &this->arm_go_home_; OpcUAServerAPI::arm_continue_ = &this->arm_continue_; OpcUAServerAPI::arm_power_on_ = &this->arm_power_on_; OpcUAServerAPI::count_ng_max_ = &this->count_ng_max_; OpcUAServerAPI::count_ok_max_ = &this->count_ok_max_; OpcUAServerAPI::status_code_ptr_ = this->status_code_ptr_; opc_ua_server_api_ptr_->start(); }); std::shared_ptr vda_5050_client_thr = std::make_shared( [this, nh]() { vda_5050_client_api_ptr_ = std::make_shared(nh, move_base_ptr_, loc_base_ptr_, monitor_ptr_); vda_5050_client_api_ptr_->muted_value_ = &this->muted_; vda_5050_client_api_ptr_->unLoad_excuted_ = std::bind(&AmrController::unLoadCallBack, this); vda_5050_client_api_ptr_->load_excuted_ = std::bind(&AmrController::loadCallBack, this); vda_5050_client_api_ptr_->cur_belt_state_en_ = &this->cur_belt_state_en_; vda_5050_client_api_ptr_->belt_thread_ptr_ = &this->belt_thread_; vda_5050_client_api_ptr_->arm_function_ptr_ = std::bind(&AmrController::ArmCallBack, this); vda_5050_client_api_ptr_->arm_thread_ptr_ = &this->arm_thread_; vda_5050_client_api_ptr_->enable_action_ = &this->enable_; vda_5050_client_api_ptr_->cancel_action_ = &this->cancel_; vda_5050_client_api_ptr_->pause_action_ = &this->pause_; vda_5050_client_api_ptr_->count_ng_max_ = &this->count_ng_max_; vda_5050_client_api_ptr_->count_ok_max_ = &this->count_ok_max_; }); ROS_INFO("Initalizing comunication is done"); opc_ua_server_thr->join(); vda_5050_client_thr->join(); } void AmrController::initalizingLocalizationHandle(ros::NodeHandle &nh) { std::string obj_name("loc_base::LocBase"); if (tf_ == nullptr) throw std::runtime_error("tf2_ros::Buffer object is null"); try { loc_base_ptr_ = loc_base_loader_.createUniqueInstance(obj_name); ros::NodeHandle node = ros::NodeHandle(nh, "global_costmap"); loc_base_ptr_->initialize(node, tf_); } catch (pluginlib::PluginlibException &ex) { ROS_FATAL("Failed to create the %s, are you sure it is properly registered and that the containing library is built? Exception: %s", obj_name.c_str(), ex.what()); exit(1); } catch (std::exception &e) { ROS_FATAL("Failed to create the %s, are you sure it is properly registered and that the containing library is built? Exception: %s", obj_name.c_str(), e.what()); exit(1); } } void AmrController::initalizingMoveBaseHandle(ros::NodeHandle &nh) { std::string obj_name("move_base::MoveBase"); if (tf_ == nullptr) throw std::runtime_error("tf2_ros::Buffer object is null"); tf_converter_ptr_ = std::make_shared(tf_); tf_converter_ptr_->TfBuffer(tf_core_ptr_); try { robot::log_info("[%s:%d]\n Loading MoveBase library...", __FILE__, __LINE__); robot::PluginLoaderHelper loader; std::string path_file_so = loader.findLibraryPath("MoveBase"); move_base_loader_ = boost::dll::import_alias( path_file_so, "MoveBase", boost::dll::load_mode::append_decorations); robot::log_info("[%s:%d]\n Creating MoveBase instance...", __FILE__, __LINE__); move_base_ptr_ = move_base_loader_(); sensor_converter_ptr_ = std::make_shared(nh, move_base_ptr_); move_base_ptr_->initialize(tf_core_ptr_); // Initialize costmap publisher for RViz visualization robot::log_info("[%s:%d]\n Initializing costmap publisher...", __FILE__, __LINE__); amr_publisher_ptr_ = std::make_shared(nh, move_base_ptr_); amr_subscriber_ptr_ = std::make_shared(nh, move_base_ptr_); ros::Rate r(3); do { r.sleep(); ros::spinOnce(); } while (ros::ok() && !move_base_ptr_->getFeedback()->is_ready); if (move_base_ptr_ != nullptr && move_base_ptr_->getFeedback() != nullptr && move_base_ptr_->getFeedback()->is_ready) { // Read footprint from parameter server and set it std::vector footprint; if (nh.hasParam("footprint")) { XmlRpc::XmlRpcValue footprint_xmlrpc; nh.getParam("footprint", footprint_xmlrpc); if (footprint_xmlrpc.getType() == XmlRpc::XmlRpcValue::TypeArray && footprint_xmlrpc.size() >= 3) { footprint.reserve(footprint_xmlrpc.size()); for (int i = 0; i < footprint_xmlrpc.size(); ++i) { if (footprint_xmlrpc[i].getType() == XmlRpc::XmlRpcValue::TypeArray && footprint_xmlrpc[i].size() == 2) { robot_geometry_msgs::Point pt; pt.x = static_cast(footprint_xmlrpc[i][0]); pt.y = static_cast(footprint_xmlrpc[i][1]); pt.z = 0.0; footprint.push_back(pt); } } if (footprint.size() >= 3) { move_base_ptr_->setRobotFootprint(footprint); robot::log_info("[%s:%d] Set robot footprint with %zu points", __FILE__, __LINE__, footprint.size()); for (size_t i = 0; i < footprint.size(); ++i) { robot::log_info("[%s:%d] Footprint point[%zu]: (%.3f, %.3f)", __FILE__, __LINE__, i, footprint[i].x, footprint[i].y); } } else { robot::log_warning("[%s:%d] Footprint must have at least 3 points, got %zu. Using default footprint.", __FILE__, __LINE__, footprint.size()); } } else { robot::log_warning("[%s:%d] Invalid footprint parameter format. Expected array of arrays with at least 3 points.", __FILE__, __LINE__); } } else { robot::log_info("[%s:%d] No footprint parameter found, using default footprint from move_base config.", __FILE__, __LINE__); } robot_geometry_msgs::Vector3 linear; linear.x = 0.3; move_base_ptr_->setTwistLinear(linear); linear.x = -0.3; move_base_ptr_->setTwistLinear(linear); // Request map từ service hoặc đợi message (vì map server chỉ publish một lần) robot::log_info("[%s:%d]\n Requesting map from server...", __FILE__, __LINE__); sensor_converter_ptr_->requestMapFromServer(nh); // Start publishing costmaps to RViz // Global costmap: 1 Hz, Local costmap: 5 Hz if (amr_publisher_ptr_) { robot::log_info("[%s:%d]\n Starting costmap publishing to RViz...", __FILE__, __LINE__); amr_publisher_ptr_->startPublishing(1.0, 5.0); // Start publishing cmd_vel using WallTimer double cmd_vel_rate = 20.0; // Default: 20 Hz nh.param("cmd_vel_publish_rate", cmd_vel_rate, cmd_vel_rate); robot::log_info("[%s:%d]\n Starting cmd_vel publishing at %.2f Hz...", __FILE__, __LINE__, cmd_vel_rate); amr_publisher_ptr_->startCmdVelPublishing(cmd_vel_rate); // Start publishing plans using Timer double global_plan_rate = 10.0; // Default: 10 Hz double local_plan_rate = 10.0; // Default: 10 Hz nh.param("global_plan_publish_rate", global_plan_rate, global_plan_rate); nh.param("local_plan_publish_rate", local_plan_rate, local_plan_rate); robot::log_info("[%s:%d]\n Starting plan publishing - Global: %.2f Hz, Local: %.2f Hz...", __FILE__, __LINE__, global_plan_rate, local_plan_rate); amr_publisher_ptr_->startPlanPublishing(global_plan_rate, local_plan_rate); } } } catch (const std::exception &e) { robot::log_error("[%s:%d]\n Exception: %s", __FILE__, __LINE__, e.what()); exit(1); } catch (...) { robot::log_error("[%s:%d]\n Unknown exception occurred", __FILE__, __LINE__); exit(1); } } void AmrController::initalizingMonitorHandle(ros::NodeHandle &nh) { this->monitor_ptr_ = std::make_shared(nh); } void AmrController::ArmCallBack() { ROS_INFO("Arm Calling"); std::lock_guard lock(this->arm_mutex_); { arm_joined_ = true; this->arm_thread_ = std::thread(&AmrController::ArmDotuff, this); } } void AmrController::ArmDotuff() { std::shared_ptr arm_control_ptr; arm_control_ptr = std::make_shared(); arm_control_ptr->enable_ = &this->enable_; arm_control_ptr->go_home_flag_ = &this->arm_go_home_; arm_control_ptr->continue_flag_ = &this->arm_continue_; arm_control_ptr->power_on_flag_ = &this->arm_power_on_; OpcUAServerAPI::mode_ptr_ = &arm_control_ptr->mode_; this->status_code_ptr_ = reinterpret_cast(&arm_control_ptr->statusCode_); if (!this->arm_go_home_) { arm_control_ptr->ok_count_max_ = &this->count_ok_max_; arm_control_ptr->ng_count_max_ = &this->count_ng_max_; arm_control_ptr->startModeThread(); } else { arm_control_ptr->startHomeThread(); } arm_control_ptr.reset(); ROS_INFO("Arm Finished"); std::lock_guard lock(this->arm_mutex_); this->arm_joined_ = false; } void AmrController::unLoadCallBack() { std::lock_guard lock(this->arm_mutex_); { ROS_INFO("Shiping call"); this->belt_joined_ = true; this->cancel_ = false; this->cur_belt_state_en_ = amr_control::State::WAITING; this->belt_thread_ = std::thread(&AmrController::conveyorBeltsShipping, this, std::ref(this->cur_belt_state_en_)); } } void AmrController::conveyorBeltsShipping(amr_control::State &state) { state = amr_control::State::INITIALIZING; std::shared_ptr plc_controller_ptr_; plc_controller_ptr_ = std::make_shared("192.168.2.5", 502); plc_controller_ptr_->connect(); if (!plc_controller_ptr_->checkConnected()) { state = amr_control::State::FAILED; return; } if (plc_controller_ptr_ && plc_controller_ptr_->checkConnected()) { int shipping_regsister = 119, receiving_regsister = 116; plc_controller_ptr_->setM(shipping_regsister); plc_controller_ptr_->resetM(receiving_regsister); ros::Duration(0.1).sleep(); plc_controller_ptr_->resetM(shipping_regsister); plc_controller_ptr_->resetM(receiving_regsister); ros::Rate r(5); while (ros::ok() && plc_controller_ptr_->checkConnected() && !this->cancel_) { state = amr_control::State::RUNNING; bool output_belt[2]; plc_controller_ptr_->mulGetM(124, 125, output_belt); bool enable_shipping = output_belt[0]; bool enable_receiving = output_belt[1]; if (!enable_shipping && !enable_receiving) { state = amr_control::State::FINISHED; break; } r.sleep(); } if (cancel_ || !plc_controller_ptr_->checkConnected()) state = amr_control::State::FAILED; plc_controller_ptr_->close(); } if (plc_controller_ptr_) plc_controller_ptr_.reset(); std::lock_guard lock(this->arm_mutex_); this->belt_joined_ = false; } void AmrController::loadCallBack() { std::lock_guard lock(this->arm_mutex_); { ROS_INFO("Receiving call"); this->belt_joined_ = true; this->cancel_ = false; this->cur_belt_state_en_ = amr_control::State::WAITING; this->belt_thread_ = std::thread(&AmrController::conveyorBeltsReceiving, this, std::ref(this->cur_belt_state_en_)); } } void AmrController::conveyorBeltsReceiving(amr_control::State &state) { state = amr_control::State::INITIALIZING; std::shared_ptr plc_controller_ptr_; plc_controller_ptr_ = std::make_shared("192.168.2.5", 502); plc_controller_ptr_->connect(); if (!plc_controller_ptr_->checkConnected()) { state = amr_control::State::FAILED; return; } if (plc_controller_ptr_ && plc_controller_ptr_->checkConnected()) { int shipping_regsister = 119, receiving_regsister = 116; plc_controller_ptr_->resetM(shipping_regsister); plc_controller_ptr_->setM(receiving_regsister); ros::Duration(0.1).sleep(); plc_controller_ptr_->resetM(shipping_regsister); plc_controller_ptr_->resetM(receiving_regsister); ros::Rate r(5); while (ros::ok() && plc_controller_ptr_->checkConnected() && !this->cancel_) { state = amr_control::State::RUNNING; bool output_belt[2]; plc_controller_ptr_->mulGetM(124, 125, output_belt); bool enable_shipping = output_belt[0]; bool enable_receiving = output_belt[1]; if (!enable_shipping && !enable_receiving) { state = amr_control::State::FINISHED; break; } r.sleep(); } if (this->cancel_ || !plc_controller_ptr_->checkConnected()) state = amr_control::State::FAILED; plc_controller_ptr_->close(); } if (plc_controller_ptr_) plc_controller_ptr_.reset(); std::lock_guard lock(this->arm_mutex_); this->belt_joined_ = false; } void AmrController::controllerDotuff() { ros::Rate r(10); while (ros::ok()) { std::shared_ptr plc_controller_ptr_; plc_controller_ptr_ = std::make_shared("192.168.2.5", 502); plc_controller_ptr_->connect(); r.sleep(); ros::spinOnce(); if (plc_controller_ptr_ == nullptr) continue; if (!plc_controller_ptr_->checkConnected()) continue; this->amr_safety_ptr_ = std::make_shared(); this->amr_safety_ptr_->getController(plc_controller_ptr_); while (ros::ok() && plc_controller_ptr_ && plc_controller_ptr_->checkConnected()) { if (!this->monitor_ptr_) continue; nav_2d_msgs::Twist2D velocity; if (this->monitor_ptr_->getVelocity(velocity)) { cmd_vel_mtx.lock(); this->amr_safety_ptr_->safetyHandle(velocity, cmd_vel_max_, cmd_vel_recommended_); cmd_vel_mtx.unlock(); } if (move_base_ptr_ != nullptr && move_base_ptr_->getFeedback() != nullptr && move_base_ptr_->getFeedback()->is_ready) { if (velocity.x <= -0.01) this->amr_safety_ptr_->writeMutesSafety(true); else { this->amr_safety_ptr_->writeMutesSafety(this->muted_); } } bool have_goods; int have_goods_regsister = 142; if (!OpcUAServerAPI::belt_cancel_) { plc_controller_ptr_->getM(have_goods_regsister, have_goods); OpcUAServerAPI::have_goods_ = have_goods; } else { plc_controller_ptr_->resetM(have_goods_regsister); } if (vda_5050_client_api_ptr_) { amr_control::OperatingMode mode_; bool operating_mode[4]; plc_controller_ptr_->mulGetM(14, 17, operating_mode); if (operating_mode[0]) mode_ = amr_control::OperatingMode::AUTOMATIC; else if (operating_mode[2]) mode_ = amr_control::OperatingMode::SERVICE; else if (operating_mode[3]) mode_ = amr_control::OperatingMode::MANUAL; switch (mode_) { case amr_control::OperatingMode::AUTOMATIC: vda_5050_client_api_ptr_->mode_ = vda_5050::State::OperatingMode::AUTOMATIC; break; case amr_control::OperatingMode::MANUAL: vda_5050_client_api_ptr_->mode_ = vda_5050::State::OperatingMode::MANUAL; break; case amr_control::OperatingMode::SERVICE: vda_5050_client_api_ptr_->mode_ = vda_5050::State::OperatingMode::SERVICE; break; default: vda_5050_client_api_ptr_->mode_ = vda_5050::State::OperatingMode::SERVICE; // Default break; } } r.sleep(); ros::spinOnce(); } if (plc_controller_ptr_) plc_controller_ptr_.reset(); if (amr_safety_ptr_) amr_safety_ptr_.reset(); } } void AmrController::isDetectedMakerCallback(const std_msgs::Bool::ConstPtr &msg) { this->muted_ = msg->data; } void AmrController::threadHandle() { ros::Rate r(5); while (ros::ok()) { if (this->arm_thread_.joinable()) { std::lock_guard lock(this->arm_mutex_); { if (!this->arm_joined_) { this->arm_thread_.join(); } } } if (this->belt_thread_.joinable()) { std::lock_guard lock(this->belt_mutex_); { if (!this->belt_joined_) { this->belt_thread_.join(); } } } if (move_base_ptr_ != nullptr && move_base_ptr_->getFeedback() != nullptr && move_base_ptr_->getFeedback()->is_ready) { nav_2d_msgs::Twist2D velocity; if (this->monitor_ptr_ && this->monitor_ptr_->getVelocity(velocity)) { this->cmd_vel_max_.x = !vda_5050_client_api_ptr_ ? 0.3 : std::max(0.3, std::max(OpcUAServerAPI::cmd_vel_max_.x, vda_5050_client_api_ptr_->cmd_vel_max_.x)); this->cmd_vel_max_.theta = !vda_5050_client_api_ptr_ ? 0.3 : std::max(0.3, vda_5050_client_api_ptr_->cmd_vel_max_.theta); robot_geometry_msgs::Vector3 linear; robot_geometry_msgs::Vector3 angular; cmd_vel_mtx.lock(); linear.x = this->amr_safety_ptr_ != nullptr ? this->cmd_vel_recommended_.x : this->cmd_vel_max_.x; angular.z = this->cmd_vel_max_.theta; cmd_vel_mtx.unlock(); this->move_base_ptr_->setTwistLinear(linear); linear.x *= -1.0; this->move_base_ptr_->setTwistLinear(linear); this->move_base_ptr_->setTwistAngular(angular); } } r.sleep(); ros::spinOnce(); } } }