feat: 已添加win_ubuntu_bridge
This commit is contained in:
@@ -5,19 +5,47 @@ if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
add_compile_options(-Wall -Wextra -Wpedantic)
|
||||
endif()
|
||||
|
||||
# 1. 寻找 ROS 2 和 行为树 核心依赖
|
||||
find_package(ament_cmake REQUIRED)
|
||||
find_package(rclcpp REQUIRED)
|
||||
find_package(behaviortree_cpp_v3 REQUIRED)
|
||||
find_package(rclcpp_action REQUIRED)
|
||||
find_package(rclcpp_components REQUIRED) # 🚨 核心依赖:寻找组件库
|
||||
find_package(behaviortree_cpp_v3 REQUIRED)
|
||||
find_package(ament_index_cpp REQUIRED)
|
||||
find_package(win_ubuntu_bridge REQUIRED)
|
||||
|
||||
# 2. 编译主节点
|
||||
add_executable(master_node src/master_node.cpp)
|
||||
target_include_directories(master_node PUBLIC src)
|
||||
ament_target_dependencies(master_node rclcpp behaviortree_cpp_v3 ament_index_cpp)
|
||||
# 1. 编译大脑为动态链接库 (SHARED) 组件
|
||||
add_library(brain_node SHARED src/brain_node.cpp)
|
||||
|
||||
# 3. 安装规则 (极其重要:把剧本和程序装到系统目录,让 ROS 2 能找到它)
|
||||
install(TARGETS master_node DESTINATION lib/${PROJECT_NAME})
|
||||
# 2. 将 include 暴露给编译器
|
||||
target_include_directories(brain_node PUBLIC
|
||||
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
|
||||
)
|
||||
|
||||
ament_target_dependencies(brain_node
|
||||
rclcpp
|
||||
rclcpp_action
|
||||
rclcpp_components
|
||||
behaviortree_cpp_v3
|
||||
ament_index_cpp
|
||||
win_ubuntu_bridge
|
||||
)
|
||||
|
||||
# 3. 注册插件
|
||||
rclcpp_components_register_node(brain_node
|
||||
PLUGIN "agv_calib_core::BrainNode"
|
||||
EXECUTABLE brain_node_exe
|
||||
)
|
||||
|
||||
# 4. 安装工程中所有的核心文件夹 (不可遗漏!)
|
||||
install(TARGETS brain_node brain_node_exe
|
||||
ARCHIVE DESTINATION lib
|
||||
LIBRARY DESTINATION lib/${PROJECT_NAME}
|
||||
RUNTIME DESTINATION lib/${PROJECT_NAME}
|
||||
)
|
||||
|
||||
install(DIRECTORY include/ DESTINATION include)
|
||||
install(DIRECTORY config/ DESTINATION share/${PROJECT_NAME}/config)
|
||||
install(DIRECTORY launch/ DESTINATION share/${PROJECT_NAME}/launch)
|
||||
install(DIRECTORY behavior_trees/ DESTINATION share/${PROJECT_NAME}/behavior_trees)
|
||||
|
||||
ament_package()
|
||||
@@ -0,0 +1,9 @@
|
||||
<root main_tree_to_execute="MainTree">
|
||||
<BehaviorTree ID="MainTree">
|
||||
<Sequence name="全自动标定总流程">
|
||||
<SetChassisMode target_mode="1" />
|
||||
<TriggerCapture sensor_id="cam_front" capture_code_out="{shared_code}" />
|
||||
<DownloadData sensor_id="cam_front" capture_code_in="{shared_code}" save_dir="/tmp/calib_data" saved_path_out="{saved_image_path}" />
|
||||
</Sequence>
|
||||
</BehaviorTree>
|
||||
</root>
|
||||
@@ -0,0 +1,6 @@
|
||||
/**:
|
||||
ros__parameters:
|
||||
# 动态指定要加载的 XML 剧本文件名
|
||||
tree_xml_filename: "main_tree.xml"
|
||||
# 行为树的 Tick 循环频率 (毫秒)
|
||||
tick_rate_ms: 50
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <rclcpp/rclcpp.hpp>
|
||||
#include <behaviortree_cpp_v3/bt_factory.h>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
|
||||
namespace agv_calib_core {
|
||||
|
||||
// 继承 Node,化身为标准的 ROS 2 Component
|
||||
class BrainNode : public rclcpp::Node {
|
||||
public:
|
||||
explicit BrainNode(const rclcpp::NodeOptions & options);
|
||||
~BrainNode() override;
|
||||
|
||||
private:
|
||||
// 行为树专属的后台执行线程 (极其重要!绝不能阻塞 ROS 2 容器主线程)
|
||||
void execute_behavior_tree();
|
||||
|
||||
std::thread bt_thread_;
|
||||
std::atomic<bool> is_running_;
|
||||
};
|
||||
|
||||
} // namespace agv_calib_core
|
||||
@@ -0,0 +1,109 @@
|
||||
#pragma once
|
||||
|
||||
#include <behaviortree_cpp_v3/action_node.h>
|
||||
#include <rclcpp/rclcpp.hpp>
|
||||
#include <rclcpp_action/rclcpp_action.hpp>
|
||||
|
||||
// 引入底层 win_ubuntu_bridge 接口
|
||||
#include "win_ubuntu_bridge/srv/set_diagnostic_mode.hpp"
|
||||
#include "win_ubuntu_bridge/srv/trigger_sync_capture.hpp"
|
||||
#include "win_ubuntu_bridge/action/download_sensor_data.hpp"
|
||||
|
||||
namespace agv_calib_core {
|
||||
|
||||
class SetChassisModeNode : public BT::SyncActionNode {
|
||||
public:
|
||||
SetChassisModeNode(const std::string& name, const BT::NodeConfiguration& config, rclcpp::Node* node)
|
||||
: BT::SyncActionNode(name, config), node_(node) {
|
||||
client_ = node_->create_client<win_ubuntu_bridge::srv::SetDiagnosticMode>("/chassis_gateway/set_diagnostic_mode");
|
||||
}
|
||||
|
||||
static BT::PortsList providedPorts() { return { BT::InputPort<int>("target_mode") }; }
|
||||
|
||||
BT::NodeStatus tick() override {
|
||||
int mode; if (!getInput("target_mode", mode)) return BT::NodeStatus::FAILURE;
|
||||
RCLCPP_INFO(node_->get_logger(), "🌲 [BT] 下发底盘夺权指令,模式: %d", mode);
|
||||
if (!client_->wait_for_service(std::chrono::seconds(2))) return BT::NodeStatus::FAILURE;
|
||||
|
||||
auto req = std::make_shared<win_ubuntu_bridge::srv::SetDiagnosticMode::Request>(); req->target_mode = mode;
|
||||
auto future = client_->async_send_request(req);
|
||||
|
||||
// 🚨 这里阻塞等待完全没问题!因为外层 BT 跑在独立线程,根本不影响 ROS 2 Executor 的回调!
|
||||
if (future.wait_for(std::chrono::seconds(3)) == std::future_status::ready) {
|
||||
auto res = future.get();
|
||||
if (res->success) return BT::NodeStatus::SUCCESS;
|
||||
}
|
||||
return BT::NodeStatus::FAILURE;
|
||||
}
|
||||
private:
|
||||
rclcpp::Node* node_; rclcpp::Client<win_ubuntu_bridge::srv::SetDiagnosticMode>::SharedPtr client_;
|
||||
};
|
||||
|
||||
class TriggerCaptureNode : public BT::SyncActionNode {
|
||||
public:
|
||||
TriggerCaptureNode(const std::string& name, const BT::NodeConfiguration& config, rclcpp::Node* node)
|
||||
: BT::SyncActionNode(name, config), node_(node) {
|
||||
client_ = node_->create_client<win_ubuntu_bridge::srv::TriggerSyncCapture>("/sensor_gateway/trigger_sync_capture");
|
||||
}
|
||||
|
||||
static BT::PortsList providedPorts() {
|
||||
return { BT::InputPort<std::string>("sensor_id"), BT::OutputPort<int64_t>("capture_code_out") };
|
||||
}
|
||||
|
||||
BT::NodeStatus tick() override {
|
||||
std::string sensor_id; getInput("sensor_id", sensor_id);
|
||||
RCLCPP_INFO(node_->get_logger(), "📷 [BT] 冻结 %s 数据...", sensor_id.c_str());
|
||||
if (!client_->wait_for_service(std::chrono::seconds(2))) return BT::NodeStatus::FAILURE;
|
||||
|
||||
auto req = std::make_shared<win_ubuntu_bridge::srv::TriggerSyncCapture::Request>(); req->sensor_ids.push_back(sensor_id);
|
||||
auto future = client_->async_send_request(req);
|
||||
if (future.wait_for(std::chrono::seconds(3)) == std::future_status::ready) {
|
||||
auto res = future.get();
|
||||
if (res->success) { setOutput("capture_code_out", res->capture_timestamp_us); return BT::NodeStatus::SUCCESS; }
|
||||
}
|
||||
return BT::NodeStatus::FAILURE;
|
||||
}
|
||||
private:
|
||||
rclcpp::Node* node_; rclcpp::Client<win_ubuntu_bridge::srv::TriggerSyncCapture>::SharedPtr client_;
|
||||
};
|
||||
|
||||
class DownloadDataNode : public BT::StatefulActionNode {
|
||||
public:
|
||||
DownloadDataNode(const std::string& name, const BT::NodeConfiguration& config, rclcpp::Node* node)
|
||||
: BT::StatefulActionNode(name, config), node_(node) {
|
||||
action_client_ = rclcpp_action::create_client<win_ubuntu_bridge::action::DownloadSensorData>(node_, "/sensor_gateway/download_sensor_data");
|
||||
}
|
||||
|
||||
static BT::PortsList providedPorts() {
|
||||
return { BT::InputPort<std::string>("sensor_id"), BT::InputPort<int64_t>("capture_code_in"),
|
||||
BT::InputPort<std::string>("save_dir"), BT::OutputPort<std::string>("saved_path_out") };
|
||||
}
|
||||
|
||||
BT::NodeStatus onStart() override {
|
||||
int64_t code; std::string sensor; std::string save_dir;
|
||||
if (!getInput("capture_code_in", code) || !getInput("sensor_id", sensor) || !getInput("save_dir", save_dir)) return BT::NodeStatus::FAILURE;
|
||||
|
||||
RCLCPP_INFO(node_->get_logger(), "📥 [BT] 挂起下载任务,取件码: %ld", code);
|
||||
if (!action_client_->wait_for_action_server(std::chrono::seconds(2))) return BT::NodeStatus::FAILURE;
|
||||
|
||||
auto goal_msg = win_ubuntu_bridge::action::DownloadSensorData::Goal();
|
||||
goal_msg.capture_timestamp_us = code; goal_msg.sensor_id = sensor;
|
||||
goal_msg.data_type = win_ubuntu_bridge::action::DownloadSensorData::Goal::DATA_TYPE_IMAGE; goal_msg.save_directory = save_dir;
|
||||
|
||||
auto send_goal_options = rclcpp_action::Client<win_ubuntu_bridge::action::DownloadSensorData>::SendGoalOptions();
|
||||
send_goal_options.result_callback = [this](const rclcpp_action::ClientGoalHandle<win_ubuntu_bridge::action::DownloadSensorData>::WrappedResult & result) {
|
||||
if (result.code == rclcpp_action::ResultCode::SUCCEEDED && result.result->success) {
|
||||
RCLCPP_INFO(node_->get_logger(), "✅ [BT] 落盘成功!路径: %s", result.result->saved_file_path.c_str());
|
||||
setOutput("saved_path_out", result.result->saved_file_path); done_ = true; success_ = true;
|
||||
} else { done_ = true; success_ = false; }
|
||||
};
|
||||
action_client_->async_send_goal(goal_msg, send_goal_options); done_ = false; return BT::NodeStatus::RUNNING;
|
||||
}
|
||||
BT::NodeStatus onRunning() override { if (done_) return success_ ? BT::NodeStatus::SUCCESS : BT::NodeStatus::FAILURE; return BT::NodeStatus::RUNNING; }
|
||||
void onHalted() override { }
|
||||
private:
|
||||
rclcpp::Node* node_; rclcpp_action::Client<win_ubuntu_bridge::action::DownloadSensorData>::SharedPtr action_client_;
|
||||
bool done_ = false; bool success_ = false;
|
||||
};
|
||||
|
||||
} // namespace agv_calib_core
|
||||
@@ -0,0 +1,31 @@
|
||||
import os
|
||||
from ament_index_python.packages import get_package_share_directory
|
||||
from launch import LaunchDescription
|
||||
from launch_ros.actions import ComposableNodeContainer
|
||||
from launch_ros.descriptions import ComposableNode
|
||||
|
||||
def generate_launch_description():
|
||||
# 获取 yaml 文件的绝对路径
|
||||
config_file = os.path.join(
|
||||
get_package_share_directory('agv_calib_core'),
|
||||
'config',
|
||||
'brain_params.yaml'
|
||||
)
|
||||
|
||||
# 建立多线程容器加载大脑组件 (MT 代表 Multi-Threaded Executor)
|
||||
container = ComposableNodeContainer(
|
||||
name='brain_container',
|
||||
namespace='',
|
||||
package='rclcpp_components',
|
||||
executable='component_container_mt',
|
||||
composable_node_descriptions=[
|
||||
ComposableNode(
|
||||
package='agv_calib_core',
|
||||
plugin='agv_calib_core::BrainNode',
|
||||
name='brain_node',
|
||||
parameters=[config_file] # 🚨 动态挂载 YAML 参数表!
|
||||
)
|
||||
],
|
||||
output='screen',
|
||||
)
|
||||
return LaunchDescription([container])
|
||||
@@ -2,21 +2,21 @@
|
||||
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
|
||||
<package format="3">
|
||||
<name>agv_calib_core</name>
|
||||
<version>0.0.0</version>
|
||||
<description>TODO: Package description</description>
|
||||
<version>1.0.0</version>
|
||||
<description>行为树总控大脑</description>
|
||||
<maintainer email="2469171725@qq.com">nvidia</maintainer>
|
||||
<license>TODO: License declaration</license>
|
||||
<license>Apache-2.0</license>
|
||||
|
||||
<buildtool_depend>ament_cmake</buildtool_depend>
|
||||
|
||||
<depend>rclcpp</depend>
|
||||
<depend>rclcpp_action</depend>
|
||||
<depend>rclcpp_components</depend>
|
||||
<depend>behaviortree_cpp_v3</depend>
|
||||
<depend>ament_index_cpp</depend>
|
||||
|
||||
<test_depend>ament_lint_auto</test_depend>
|
||||
<test_depend>ament_lint_common</test_depend>
|
||||
<depend>win_ubuntu_bridge</depend>
|
||||
|
||||
<export>
|
||||
<build_type>ament_cmake</build_type>
|
||||
</export>
|
||||
</package>
|
||||
</package>
|
||||
@@ -0,0 +1,87 @@
|
||||
#include "agv_calib_core/brain_node.hpp"
|
||||
#include "agv_calib_core/bt_ros2_nodes.hpp"
|
||||
|
||||
#include <behaviortree_cpp_v3/bt_factory.h>
|
||||
#include <behaviortree_cpp_v3/loggers/bt_cout_logger.h>
|
||||
#include <ament_index_cpp/get_package_share_directory.hpp>
|
||||
#include <rclcpp_components/register_node_macro.hpp>
|
||||
|
||||
namespace agv_calib_core {
|
||||
|
||||
BrainNode::BrainNode(const rclcpp::NodeOptions & options)
|
||||
: Node("brain_node", options), is_running_(false) {
|
||||
|
||||
RCLCPP_INFO(this->get_logger(), "👑 AGV 标定中央大脑 (Component 版) 正在挂载...");
|
||||
|
||||
// 1. 从 YAML 配置文件读取动态参数!绝不硬编码!
|
||||
this->declare_parameter<std::string>("tree_xml_filename", "main_tree.xml");
|
||||
this->declare_parameter<int>("tick_rate_ms", 50);
|
||||
|
||||
// 2. 启动专属的后台守护线程去运行行为树。将主线程交还给容器处理网络回调!
|
||||
is_running_ = true;
|
||||
bt_thread_ = std::thread(&BrainNode::execute_behavior_tree, this);
|
||||
}
|
||||
|
||||
BrainNode::~BrainNode() {
|
||||
is_running_ = false;
|
||||
if (bt_thread_.joinable()) {
|
||||
bt_thread_.join();
|
||||
}
|
||||
}
|
||||
|
||||
void BrainNode::execute_behavior_tree() {
|
||||
// 稍微延时 0.5 秒,确保节点完全被容器接管,再发起 Client 寻址
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
|
||||
BT::BehaviorTreeFactory factory;
|
||||
|
||||
// 注册业务积木,传递 this 裸指针给所有积木
|
||||
factory.registerBuilder<SetChassisModeNode>("SetChassisMode",
|
||||
[this](const std::string& name, const BT::NodeConfiguration& config) {
|
||||
return std::make_unique<SetChassisModeNode>(name, config, this);
|
||||
});
|
||||
|
||||
factory.registerBuilder<TriggerCaptureNode>("TriggerCapture",
|
||||
[this](const std::string& name, const BT::NodeConfiguration& config) {
|
||||
return std::make_unique<TriggerCaptureNode>(name, config, this);
|
||||
});
|
||||
|
||||
factory.registerBuilder<DownloadDataNode>("DownloadData",
|
||||
[this](const std::string& name, const BT::NodeConfiguration& config) {
|
||||
return std::make_unique<DownloadDataNode>(name, config, this);
|
||||
});
|
||||
|
||||
try {
|
||||
std::string xml_filename = this->get_parameter("tree_xml_filename").as_string();
|
||||
int tick_rate = this->get_parameter("tick_rate_ms").as_int();
|
||||
|
||||
std::string pkg_path = ament_index_cpp::get_package_share_directory("agv_calib_core");
|
||||
std::string xml_file = pkg_path + "/behavior_trees/" + xml_filename;
|
||||
|
||||
auto tree = factory.createTreeFromFile(xml_file);
|
||||
BT::StdCoutLogger logger_cout(tree);
|
||||
|
||||
RCLCPP_INFO(this->get_logger(), "📜 XML 剧本 [%s] 加载完毕,开始全自动流水线...", xml_filename.c_str());
|
||||
|
||||
// 按照 YAML 配置的频率持续 Tick
|
||||
BT::NodeStatus status = BT::NodeStatus::RUNNING;
|
||||
while (rclcpp::ok() && is_running_ && status == BT::NodeStatus::RUNNING) {
|
||||
status = tree.tickRoot();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(tick_rate));
|
||||
}
|
||||
|
||||
if (status == BT::NodeStatus::SUCCESS) {
|
||||
RCLCPP_INFO(this->get_logger(), "🎉 标定流水线全流程完美结束!");
|
||||
} else {
|
||||
RCLCPP_WARN(this->get_logger(), "⚠️ 流水线未成功完成 (可能被中止)。");
|
||||
}
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
RCLCPP_ERROR(this->get_logger(), "❌ 行为树崩溃: %s", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace agv_calib_core
|
||||
|
||||
// 🚨 终极一步:将该类注册为 ROS 2 Component (插件)!
|
||||
RCLCPP_COMPONENTS_REGISTER_NODE(agv_calib_core::BrainNode)
|
||||
@@ -1,82 +0,0 @@
|
||||
#pragma once
|
||||
#include <behaviortree_cpp_v3/action_node.h>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
|
||||
// 1. 假装连接车端并夺权
|
||||
class MockConnectAGV : public BT::SyncActionNode {
|
||||
public:
|
||||
MockConnectAGV(const std::string& name) : BT::SyncActionNode(name, {}) {}
|
||||
BT::NodeStatus tick() override {
|
||||
std::cout << "💻 [gRPC 对外] 📡 正在连接 Windows 车端... 夺权成功!" << std::endl;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500)); // 假装网络耗时 0.5 秒
|
||||
return BT::NodeStatus::SUCCESS;
|
||||
}
|
||||
};
|
||||
|
||||
// 2. 假装呼叫底盘算法
|
||||
class MockCallChassisAlgo : public BT::SyncActionNode {
|
||||
public:
|
||||
MockCallChassisAlgo(const std::string& name) : BT::SyncActionNode(name, {}) {}
|
||||
BT::NodeStatus tick() override {
|
||||
std::cout << "🧮 [Action 对内] 🚙 丢给底盘算法团队... 算好了!左轮径 0.098m。" << std::endl;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(800));
|
||||
return BT::NodeStatus::SUCCESS;
|
||||
}
|
||||
};
|
||||
|
||||
// 3. 假装控制车子跑S型曲线
|
||||
class MockTuneControl : public BT::SyncActionNode {
|
||||
public:
|
||||
MockTuneControl(const std::string& name) : BT::SyncActionNode(name, {}) {}
|
||||
BT::NodeStatus tick() override {
|
||||
std::cout << "💻 [gRPC 对外] 📈 正在下发S型曲线测试考题... 车端已跑完。" << std::endl;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
return BT::NodeStatus::SUCCESS;
|
||||
}
|
||||
};
|
||||
|
||||
// 4. 假装呼叫AI寻优算法
|
||||
class MockCallControlAlgo : public BT::SyncActionNode {
|
||||
public:
|
||||
MockCallControlAlgo(const std::string& name) : BT::SyncActionNode(name, {}) {}
|
||||
BT::NodeStatus tick() override {
|
||||
std::cout << "🧮 [Action 对内] 🧠 AI贝叶斯打分完毕... PID 最优参数已锁定!" << std::endl;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(800));
|
||||
return BT::NodeStatus::SUCCESS;
|
||||
}
|
||||
};
|
||||
|
||||
// 5. 假装走停拍并下载大文件
|
||||
class MockMoveAndCapture : public BT::SyncActionNode {
|
||||
public:
|
||||
MockMoveAndCapture(const std::string& name) : BT::SyncActionNode(name, {}) {}
|
||||
BT::NodeStatus tick() override {
|
||||
std::cout << "💻 [gRPC 对外] 🛑 刹车静止...咔嚓!5MB大文件已下载至 /tmp/cam.png" << std::endl;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(800));
|
||||
return BT::NodeStatus::SUCCESS;
|
||||
}
|
||||
};
|
||||
|
||||
// 6. 假装调用外参标定视觉算法
|
||||
class MockCallSensorAlgo : public BT::SyncActionNode {
|
||||
public:
|
||||
MockCallSensorAlgo(const std::string& name) : BT::SyncActionNode(name, {}) {}
|
||||
BT::NodeStatus tick() override {
|
||||
std::cout << "🧮 [Action 对内] 📷 视觉团队正在算矩阵... 拿到 4x4 外参矩阵!" << std::endl;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(800));
|
||||
return BT::NodeStatus::SUCCESS;
|
||||
}
|
||||
};
|
||||
|
||||
// 7. 假装出厂固化
|
||||
class MockCommitAllParams : public BT::SyncActionNode {
|
||||
public:
|
||||
MockCommitAllParams(const std::string& name) : BT::SyncActionNode(name, {}) {}
|
||||
BT::NodeStatus tick() override {
|
||||
std::cout << "💻 [gRPC 对外] 💾 正在把所有完美参数烧录进 AGV... 标定闭环,可以出厂!\n" << std::endl;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(300));
|
||||
return BT::NodeStatus::SUCCESS;
|
||||
}
|
||||
};
|
||||
@@ -1,54 +0,0 @@
|
||||
#pragma once
|
||||
#include <behaviortree_cpp_v3/action_node.h>
|
||||
#include <grpcpp/grpcpp.h>
|
||||
// 引入 CMake 自动生成的 C++ 网络契约头文件!
|
||||
#include "agv_calib_control.grpc.pb.h"
|
||||
|
||||
using namespace agv::calibration::control;
|
||||
|
||||
// =========================================================
|
||||
// 🌟 真实网络积木:连接车端并夺取控制权
|
||||
// =========================================================
|
||||
class ConnectAGVNode : public BT::SyncActionNode {
|
||||
public:
|
||||
// 注意:构造函数这里多了一个 const BT::NodeConfiguration& config 参数
|
||||
ConnectAGVNode(const std::string& name, const BT::NodeConfiguration& config) : BT::SyncActionNode(name, config) {
|
||||
// 1. 初始化时,拨号连接到车端 (因为我们要自己测,所以先连本机 127.0.0.1 端口)
|
||||
channel_ = grpc::CreateChannel("127.0.0.1:50051", grpc::InsecureChannelCredentials());
|
||||
stub_ = AgvCalibControlService::NewStub(channel_);
|
||||
}
|
||||
|
||||
// 行为树必须的静态函数 (定义端口)
|
||||
static BT::PortsList providedPorts() { return {}; }
|
||||
|
||||
BT::NodeStatus tick() override {
|
||||
std::cout << "\n💻 [行为树真节点] 正在通过 gRPC 向车端发起夺权请求..." << std::endl;
|
||||
|
||||
// 2. 准备发送载荷:要求进入调优模式
|
||||
ModeRequest request;
|
||||
request.set_target_mode(ModeRequest::TUNING_MODE);
|
||||
|
||||
StandardResponse response;
|
||||
grpc::ClientContext context;
|
||||
|
||||
// 🚨 架构师防线:设置 2 秒网络超时!如果网络断了,绝不让主线程死锁卡住!
|
||||
context.set_deadline(std::chrono::system_clock::now() + std::chrono::seconds(2));
|
||||
|
||||
// 3. 发射真正的网络脉冲!
|
||||
grpc::Status status = stub_->SetControlMode(&context, request, &response);
|
||||
|
||||
// 4. 根据网络回复,决定行为树这根树枝是亮绿灯还是红灯
|
||||
if (status.ok() && response.success()) {
|
||||
std::cout << "✅ [网络通信成功] 车端回执: " << response.message() << std::endl;
|
||||
return BT::NodeStatus::SUCCESS; // 绿灯,允许行为树执行下一步
|
||||
} else {
|
||||
std::cerr << "❌ [网络通信失败] 错误码: " << status.error_code()
|
||||
<< " 详情: " << status.error_message() << std::endl;
|
||||
return BT::NodeStatus::FAILURE; // 红灯,触发行为树重试或报警
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<grpc::Channel> channel_;
|
||||
std::unique_ptr<AgvCalibControlService::Stub> stub_;
|
||||
};
|
||||
@@ -1,44 +0,0 @@
|
||||
#include <rclcpp/rclcpp.hpp>
|
||||
#include <behaviortree_cpp_v3/bt_factory.h>
|
||||
#include <ament_index_cpp/get_package_share_directory.hpp>
|
||||
|
||||
// 引入刚才写的假节点
|
||||
#include "bt_nodes/dummy_nodes.hpp"
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
rclcpp::init(argc, argv);
|
||||
|
||||
std::cout << "\n=========================================" << std::endl;
|
||||
std::cout << "🚀 AGV 标定车间中央大脑 [空转测试版] 启动!" << std::endl;
|
||||
std::cout << "=========================================\n" << std::endl;
|
||||
|
||||
BT::BehaviorTreeFactory factory;
|
||||
|
||||
// 1. 把 C++ 类注册到工厂,名字必须和 XML 里的一模一样!
|
||||
factory.registerNodeType<MockConnectAGV>("MockConnectAGV");
|
||||
factory.registerNodeType<MockCallChassisAlgo>("MockCallChassisAlgo");
|
||||
factory.registerNodeType<MockTuneControl>("MockTuneControl");
|
||||
factory.registerNodeType<MockCallControlAlgo>("MockCallControlAlgo");
|
||||
factory.registerNodeType<MockMoveAndCapture>("MockMoveAndCapture");
|
||||
factory.registerNodeType<MockCallSensorAlgo>("MockCallSensorAlgo");
|
||||
factory.registerNodeType<MockCommitAllParams>("MockCommitAllParams");
|
||||
|
||||
try {
|
||||
// 2. 动态获取 XML 剧本的绝对路径 (防止你运行程序时路径不对找不到文件)
|
||||
std::string pkg_path = ament_index_cpp::get_package_share_directory("agv_calib_core");
|
||||
std::string xml_file = pkg_path + "/behavior_trees/main_pipeline.xml";
|
||||
|
||||
auto tree = factory.createTreeFromFile(xml_file);
|
||||
std::cout << "📜 行为树剧本加载完毕,开始全自动流水线...\n" << std::endl;
|
||||
|
||||
// 3. 开始执行总控流!
|
||||
tree.tickRoot();
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "❌ 加载 XML 失败: " << e.what() << std::endl;
|
||||
}
|
||||
|
||||
std::cout << "🎉 全流程执行完毕,完美收工!\n" << std::endl;
|
||||
rclcpp::shutdown();
|
||||
return 0;
|
||||
}
|
||||
@@ -154,6 +154,6 @@ install(TARGETS
|
||||
|
||||
install(DIRECTORY proto/ DESTINATION share/${PROJECT_NAME}/proto)
|
||||
# 若有 launch 或 behavior_trees 文件,随时解开下面这行的注释
|
||||
# install(DIRECTORY launch/ DESTINATION share/${PROJECT_NAME}/launch)
|
||||
install(DIRECTORY launch/ config/ DESTINATION share/${PROJECT_NAME}/launch)
|
||||
|
||||
ament_package()
|
||||
@@ -0,0 +1,47 @@
|
||||
from launch import LaunchDescription
|
||||
from launch.actions import DeclareLaunchArgument
|
||||
from launch.substitutions import LaunchConfiguration
|
||||
from launch_ros.actions import ComposableNodeContainer
|
||||
from launch_ros.descriptions import ComposableNode
|
||||
|
||||
def generate_launch_description():
|
||||
# 1. 声明一个外部参数 agv_ip,默认值是本地
|
||||
agv_ip_arg = DeclareLaunchArgument(
|
||||
'agv_ip',
|
||||
default_value='127.0.0.1:50051',
|
||||
description='Windows 车端在 Wi-Fi 下的 IP 和端口'
|
||||
)
|
||||
|
||||
agv_ip = LaunchConfiguration('agv_ip')
|
||||
|
||||
# 2. 把参数动态注入给三个网关组件
|
||||
container = ComposableNodeContainer(
|
||||
name='agv_gateway_container',
|
||||
namespace='',
|
||||
package='rclcpp_components',
|
||||
executable='component_container_mt',
|
||||
composable_node_descriptions=[
|
||||
ComposableNode(
|
||||
package='win_ubuntu_bridge',
|
||||
plugin='win_ubuntu_bridge::ChassisGatewayNode',
|
||||
name='chassis_gateway',
|
||||
parameters=[{'agv_ip': agv_ip}] # 👈 动态注入真实 IP
|
||||
),
|
||||
|
||||
ComposableNode(
|
||||
package='win_ubuntu_bridge',
|
||||
plugin='win_ubuntu_bridge::ControlGatewayNode',
|
||||
name='control_gateway',
|
||||
parameters=[{'agv_ip': agv_ip}] # 👈 动态注入真实 IP
|
||||
),
|
||||
|
||||
ComposableNode(
|
||||
package='win_ubuntu_bridge',
|
||||
plugin='win_ubuntu_bridge::SensorGatewayNode',
|
||||
name='sensor_gateway',
|
||||
parameters=[{'agv_ip': agv_ip}] # 👈 动态注入真实 IP
|
||||
),
|
||||
],
|
||||
output='screen',
|
||||
)
|
||||
return LaunchDescription([agv_ip_arg, container])
|
||||
Reference in New Issue
Block a user