feat: 已添加win_ubuntu_bridge

This commit is contained in:
li-shihao-code
2026-03-06 17:16:50 +08:00
parent 1ea480eccd
commit 18d6500d04
21 changed files with 1673 additions and 196 deletions
@@ -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])