6.2 C++接口使用示例
本章节将带您实现索引所示若干功能
构建 & 运行说明
进入到解压后的 sdk 顶层目录,执行以下命令
source /opt/ros/humble/setup.bash colcon build source install/setup.bash ros2 run examples '对应功能名称如: get_mc_action'
📝 代码说明
完整的代码实现包含完整的错误处理、信号处理、超时处理等机制,确保程序的健壮性。请您在 examples 目录下查看/修改
6.2.1 获取机器人模式
通过调用GetMcAction服务获取机器人当前的运行模式,包括模式ID、描述和状态信息。
1#include "aimdk_msgs/srv/get_mc_action.hpp"
2#include "aimdk_msgs/msg/common_request.hpp"
3#include "aimdk_msgs/msg/response_header.hpp"
4#include "rclcpp/rclcpp.hpp"
5#include <chrono>
6#include <memory>
7#include <signal.h>
8
9// Global variable used for signal handling
10std::shared_ptr<rclcpp::Node> g_node = nullptr;
11
12// Signal handler function
13void signal_handler(int signal) {
14 if (g_node) {
15 RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
16 signal);
17 rclcpp::shutdown();
18 }
19}
20
21class GetMcActionClient : public rclcpp::Node {
22public:
23 GetMcActionClient() : Node("get_mc_action_client") {
24 // Set service call timeout
25 const std::chrono::seconds timeout(8);
26
27 client_ = this->create_client<aimdk_msgs::srv::GetMcAction>(
28 "/aimdk_5Fmsgs/srv/GetMcAction"); // correct the service path
29 RCLCPP_INFO(this->get_logger(), "Get MC Action Client created");
30
31 // Add timeout handling
32 auto start_time = std::chrono::steady_clock::now();
33 while (!client_->wait_for_service(std::chrono::seconds(2))) {
34 if (std::chrono::steady_clock::now() - start_time > timeout) {
35 RCLCPP_ERROR(this->get_logger(), "Service wait timed out");
36 rclcpp::shutdown();
37 return;
38 }
39 RCLCPP_INFO(this->get_logger(), "Service unavailable, waiting...");
40 }
41 }
42
43 void send_request() {
44 try {
45 auto request = std::make_shared<aimdk_msgs::srv::GetMcAction::Request>();
46 request->request = aimdk_msgs::msg::CommonRequest();
47
48 RCLCPP_INFO(this->get_logger(), "Sending request to get robot mode");
49
50 // Set a service call timeout
51 const std::chrono::milliseconds timeout(250);
52 for (int i = 0; i < 8; i++) {
53 auto future = client_->async_send_request(request);
54 auto retcode = rclcpp::spin_until_future_complete(shared_from_this(),
55 future, timeout);
56 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
57 // retry as remote peer: mc module can be under heavy load
58 RCLCPP_INFO(this->get_logger(), "trying to send request... [%d]", i);
59 continue;
60 }
61 // future.done
62 auto response = future.get();
63 RCLCPP_INFO(this->get_logger(), "Current robot mode: %d",
64 response->info.current_action.value);
65 RCLCPP_INFO(this->get_logger(), "Mode description: %s",
66 response->info.action_desc.c_str());
67 RCLCPP_INFO(this->get_logger(), "Mode status: %d",
68 response->info.status.value);
69 return;
70 }
71 RCLCPP_ERROR(this->get_logger(), "Service call failed or timed out");
72 } catch (const std::exception &e) {
73 RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
74 }
75 }
76
77private:
78 rclcpp::Client<aimdk_msgs::srv::GetMcAction>::SharedPtr client_;
79};
80
81int main(int argc, char *argv[]) {
82 try {
83 rclcpp::init(argc, argv);
84
85 // Set up signal handlers
86 signal(SIGINT, signal_handler);
87 signal(SIGTERM, signal_handler);
88
89 // Create node
90 g_node = std::make_shared<GetMcActionClient>();
91 auto client = std::dynamic_pointer_cast<GetMcActionClient>(g_node);
92 std::cout << "Robot modes: (1: default, 100: position-control stand, 200: "
93 "stable stand, 300: walk):"
94 << std::endl;
95 if (client) {
96 client->send_request();
97 }
98
99 // Clean up resources
100 g_node.reset();
101 rclcpp::shutdown();
102
103 return 0;
104 } catch (const std::exception &e) {
105 RCLCPP_ERROR(rclcpp::get_logger("main"),
106 "Program exited with exception: %s", e.what());
107 return 1;
108 }
109}
使用说明
ros2 run examples get_mc_action
输出示例
[INFO] [get_mc_action_client]: 当前机器人模式: 100
[INFO] [get_mc_action_client]: 模式描述: 站姿预备(位控站立)模式
[INFO] [get_mc_action_client]: 模式状态: 1
接口参考
服务:
/aimdk_5Fmsgs/srv/GetMcAction消息:
aimdk_msgs/srv/GetMcAction
6.2.2 设置机器人模式
该示例中用到了SetMcAction服务,运行节点程序后终端输入模式对应的字段值,机器人会立即进入相应的运动模式。
切入稳定站立(STAND_DEFAULT)模式前,确保机器人脚已经着地。
1#include "aimdk_msgs/srv/set_mc_action.hpp"
2#include "aimdk_msgs/msg/common_response.hpp"
3#include "aimdk_msgs/msg/common_state.hpp"
4#include "aimdk_msgs/msg/mc_action.hpp"
5#include "aimdk_msgs/msg/mc_action_command.hpp"
6#include "aimdk_msgs/msg/request_header.hpp"
7#include "rclcpp/rclcpp.hpp"
8#include <chrono>
9#include <memory>
10#include <signal.h>
11
12// Global variable used for signal handling
13std::shared_ptr<rclcpp::Node> g_node = nullptr;
14
15// Signal handler function
16void signal_handler(int signal) {
17 if (g_node) {
18 RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
19 signal);
20 rclcpp::shutdown();
21 }
22}
23
24class SetMcActionClient : public rclcpp::Node {
25public:
26 SetMcActionClient() : Node("set_mc_action_client") {
27 // Set service call timeout
28 const std::chrono::seconds timeout(8);
29
30 client_ = this->create_client<aimdk_msgs::srv::SetMcAction>(
31 "/aimdk_5Fmsgs/srv/SetMcAction");
32 RCLCPP_INFO(this->get_logger(), "Set MC Action Client created");
33
34 // Add timeout handling
35 auto start_time = std::chrono::steady_clock::now();
36 while (!client_->wait_for_service(std::chrono::seconds(2))) {
37 if (std::chrono::steady_clock::now() - start_time > timeout) {
38 RCLCPP_ERROR(this->get_logger(), "Waiting for service timed out");
39 rclcpp::shutdown();
40 return;
41 }
42 RCLCPP_INFO(this->get_logger(), "Service not available, waiting...");
43 }
44 }
45
46 bool send_request(int action_value) {
47 try {
48 auto request = std::make_shared<aimdk_msgs::srv::SetMcAction::Request>();
49 request->header = aimdk_msgs::msg::RequestHeader();
50
51 // Set robot mode
52 aimdk_msgs::msg::McActionCommand command;
53 aimdk_msgs::msg::McAction action;
54 action.value = action_value;
55 command.action = action;
56 command.action_desc =
57 "Set robot mode to: " + std::to_string(action_value);
58 request->command = command;
59
60 RCLCPP_INFO(this->get_logger(), "Sending request to set robot mode: %d",
61 action_value);
62
63 // Set Service Call Timeout
64 const std::chrono::milliseconds timeout(250);
65 for (int i = 0; i < 8; i++) {
66 auto future = client_->async_send_request(request);
67 auto retcode = rclcpp::spin_until_future_complete(shared_from_this(),
68 future, timeout);
69 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
70 // retry as remote peer: mc module can be under heavy load
71 RCLCPP_INFO(this->get_logger(), "trying to send request... [%d]", i);
72 continue;
73 }
74 // future.done
75 auto response = future.get();
76 if (response->response.status.value ==
77 aimdk_msgs::msg::CommonState::SUCCESS) {
78 RCLCPP_INFO(this->get_logger(), "Set robot mode succeeded");
79 return true;
80 } else {
81 RCLCPP_ERROR(this->get_logger(), "Set robot mode failed: %s",
82 response->response.message.c_str());
83 return false;
84 }
85 }
86 RCLCPP_ERROR(this->get_logger(), "Service call failed or timed out");
87 return false;
88 } catch (const std::exception &e) {
89 RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
90 return false;
91 }
92 }
93
94private:
95 rclcpp::Client<aimdk_msgs::srv::SetMcAction>::SharedPtr client_;
96};
97
98int main(int argc, char *argv[]) {
99 try {
100 rclcpp::init(argc, argv);
101
102 // Set up signal handlers
103 signal(SIGINT, signal_handler);
104 signal(SIGTERM, signal_handler);
105
106 // Create node
107 g_node = std::make_shared<SetMcActionClient>();
108 auto client = std::dynamic_pointer_cast<SetMcActionClient>(g_node);
109
110 if (client) {
111 int action_value = 100; // JOINT_DEFAULT Mode
112
113 // Prefer command-line argument; otherwise prompt user
114 if (argc > 1) {
115 action_value = std::atoi(argv[1]);
116 RCLCPP_INFO(g_node->get_logger(), "Using command-line argument: %d",
117 action_value);
118 } else {
119 std::cout << "Enter robot mode (1: default, 100: position-control "
120 "stand, 200: "
121 "stable stand, 300: walk): ";
122 std::cin >> action_value;
123 }
124
125 if (!client->send_request(action_value)) {
126 RCLCPP_ERROR(g_node->get_logger(), "Failed to set robot mode");
127 return 1;
128 }
129 }
130
131 // Clean up resources
132 g_node.reset();
133 rclcpp::shutdown();
134
135 return 0;
136 } catch (const std::exception &e) {
137 RCLCPP_ERROR(rclcpp::get_logger("main"),
138 "Program exited with exception: %s", e.what());
139 return 1;
140 }
141}
使用说明
# 使用命令行参数设置模式(推荐)
ros2 run examples set_mc_action 100 # 站姿预备(位控站立)模式
ros2 run examples set_mc_action 300 # 走跑模式
# 或者不带参数运行,程序会提示用户输入
ros2 run examples set_mc_action
输出示例
[INFO] [set_mc_action_client]: 设置机器人模式成功
注意事项
切入
stand_default模式前,确保机器人脚已经着地模式切换可能需要几秒钟时间完成
接口参考
服务:
/aimdk_5Fmsgs/srv/SetMcAction消息:
aimdk_msgs/srv/SetMcAction
6.2.3 设置机器人动作
该示例中用到了preset_motion_client,启动节点程序后,输入相应的字段值可实现左手(右手)的握手(举手、挥手、飞吻)动作。
可供调用的参数见预设动作表
1#include "aimdk_msgs/msg/common_response.hpp"
2#include "aimdk_msgs/msg/common_state.hpp"
3#include "aimdk_msgs/msg/common_task_response.hpp"
4#include "aimdk_msgs/msg/mc_control_area.hpp"
5#include "aimdk_msgs/msg/mc_preset_motion.hpp"
6#include "aimdk_msgs/msg/request_header.hpp"
7#include "aimdk_msgs/srv/set_mc_preset_motion.hpp"
8#include "rclcpp/rclcpp.hpp"
9#include <chrono>
10#include <memory>
11#include <signal.h>
12
13std::shared_ptr<rclcpp::Node> g_node = nullptr;
14
15void signal_handler(int signal) {
16 if (g_node) {
17 RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
18 signal);
19 rclcpp::shutdown();
20 }
21}
22
23class PresetMotionClient : public rclcpp::Node {
24public:
25 PresetMotionClient() : Node("preset_motion_client") {
26 const std::chrono::seconds timeout(8);
27
28 client_ = this->create_client<aimdk_msgs::srv::SetMcPresetMotion>(
29 "/aimdk_5Fmsgs/srv/SetMcPresetMotion");
30
31 RCLCPP_INFO(this->get_logger(), "Preset motion service client created");
32
33 // Add timeout handling
34 auto start_time = std::chrono::steady_clock::now();
35 while (!client_->wait_for_service(std::chrono::seconds(2))) {
36 if (std::chrono::steady_clock::now() - start_time > timeout) {
37 RCLCPP_ERROR(this->get_logger(), "Waiting for service timed out");
38 rclcpp::shutdown();
39 return;
40 }
41 RCLCPP_INFO(this->get_logger(), "Waiting for service to start...");
42 }
43 }
44
45 bool send_motion_request(int area_id, int motion_id) {
46 try {
47 auto request =
48 std::make_shared<aimdk_msgs::srv::SetMcPresetMotion::Request>();
49 request->header = aimdk_msgs::msg::RequestHeader();
50
51 aimdk_msgs::msg::McPresetMotion motion;
52 aimdk_msgs::msg::McControlArea area;
53
54 motion.value = motion_id; // Preset motion ID
55 area.value = area_id; // Control area ID
56 request->motion = motion;
57 request->area = area;
58 request->interrupt = false; // Not interrupt current motion
59
60 RCLCPP_INFO(this->get_logger(),
61 "Arm:(ID=%d); sending preset motion request: (ID=%d)",
62 area_id, motion_id);
63
64 const std::chrono::milliseconds timeout(250);
65
66 for (int i = 0; i < 8; i++) {
67 request->header.stamp = rclcpp::Clock().now();
68 auto future = client_->async_send_request(request);
69 auto retcode = rclcpp::spin_until_future_complete(shared_from_this(),
70 future, timeout);
71 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
72 // retry as remote peer: mc module can be under heavy load
73 RCLCPP_INFO(this->get_logger(),
74 "trying to sned preset motion request... [%d]", i);
75 continue;
76 }
77 // future.done
78 auto response = future.get();
79 if (response->response.state.value ==
80 aimdk_msgs::msg::CommonState::SUCCESS) {
81 RCLCPP_INFO(this->get_logger(), "Motion executed successfully: %lu",
82 response->response.task_id);
83 return true;
84 } else if (response->response.state.value ==
85 aimdk_msgs::msg::CommonState::RUNNING) {
86 RCLCPP_INFO(this->get_logger(), "Motion is running: %lu",
87 response->response.task_id);
88 return true;
89 } else {
90 RCLCPP_WARN(this->get_logger(), "Motion execution failed: %lu",
91 response->response.task_id);
92 return false;
93 }
94 }
95 RCLCPP_ERROR(this->get_logger(), "Service call failed or timed out");
96 return false;
97 } catch (const std::exception &e) {
98 RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
99 return false;
100 }
101 }
102
103private:
104 rclcpp::Client<aimdk_msgs::srv::SetMcPresetMotion>::SharedPtr client_;
105};
106
107int main(int argc, char *argv[]) {
108 rclcpp::init(argc, argv);
109 signal(SIGINT, signal_handler);
110 signal(SIGTERM, signal_handler);
111
112 g_node = std::make_shared<PresetMotionClient>();
113 // Cast g_node (std::shared_ptr<rclcpp::Node>) to a derived
114 // PresetMotionClient pointer (std::shared_ptr<PresetMotionClient>)
115 auto client = std::dynamic_pointer_cast<PresetMotionClient>(g_node);
116
117 int HAND_area = 1;
118 int HAND_motion = 1003;
119 std::cout << "Define the arm area for the preset motion: left=1, right=2"
120 << std::endl;
121 std::cout << "Enter arm area ID (1-left, 2-right): ";
122 std::cin >> HAND_area;
123 std::cout << "Define preset motions: raise hand=1001, wave=1002, "
124 "handshake=1003, air-kiss=1004"
125 << std::endl;
126 std::cout << "Enter preset motion ID (1001-raise, 1002-wave, 1003-handshake, "
127 "1004-airkiss): ";
128 std::cin >> HAND_motion;
129 if (client) {
130 if (!client->send_motion_request(HAND_area, HAND_motion)) {
131 RCLCPP_ERROR(g_node->get_logger(), "Failed to send motion request");
132 return 1;
133 }
134 }
135 g_node.reset();
136 rclcpp::shutdown();
137 return 0;
138}
6.2.4 夹爪控制
该示例中用到了hand_control,通过往话题/aima/hal/joint/hand/command发布消息来控制夹爪的运动
注意
注意⚠️ :运行本示例前需要在机器人运控计算单元(PC1)使用aima em stop-app mc关闭原生运控模块,获取机器人控制权。注意确保机器人安全
1#include "aimdk_msgs/msg/hand_command.hpp"
2#include "aimdk_msgs/msg/hand_command_array.hpp"
3#include "aimdk_msgs/msg/hand_type.hpp"
4#include "aimdk_msgs/msg/message_header.hpp"
5#include "rclcpp/rclcpp.hpp"
6#include <chrono>
7#include <vector>
8
9class HandControl : public rclcpp::Node {
10public:
11 HandControl()
12 : Node("hand_control"), position_pairs_({
13 {1.0, 1.0},
14 {0.0, 0.0},
15 {0.5, 0.5},
16 {0.2, 0.8},
17 {0.7, 0.3},
18 }),
19 current_index_(0) {
20 publisher_ = this->create_publisher<aimdk_msgs::msg::HandCommandArray>(
21 "/aima/hal/joint/hand/command", 10);
22
23 timer_ = this->create_wall_timer(
24 std::chrono::milliseconds(20), // 50Hz
25 std::bind(&HandControl::publish_hand_commands, this));
26
27 last_switch_time_ = now();
28 RCLCPP_INFO(this->get_logger(), "The hand control node has been started!");
29 }
30
31 void publish_hand_commands() {
32 // 1. Determine if it's time to switch parameters.
33 auto now_time = this->now();
34 if ((now_time - last_switch_time_).seconds() >= 2.0) {
35 current_index_ = (current_index_ + 1) % position_pairs_.size();
36 last_switch_time_ = now_time;
37 RCLCPP_INFO(this->get_logger(),
38 "已切换到下一个参数组,索引=%zu (left=%.2f, right=%.2f)",
39 current_index_, position_pairs_[current_index_].first,
40 position_pairs_[current_index_].second);
41 }
42
43 auto msg = std::make_unique<aimdk_msgs::msg::HandCommandArray>();
44 msg->header = aimdk_msgs::msg::MessageHeader();
45
46 float left_position = position_pairs_[current_index_].first;
47 float right_position = position_pairs_[current_index_].second;
48
49 aimdk_msgs::msg::HandCommand left_hands;
50 left_hands.name = "left_hand";
51 left_hands.position = left_position;
52 left_hands.velocity = 1.0;
53 left_hands.acceleration = 1.0;
54 left_hands.deceleration = 1.0;
55 left_hands.effort = 1.0;
56
57 aimdk_msgs::msg::HandCommand right_hands;
58 right_hands.name = "right_hand";
59 right_hands.position = right_position;
60 right_hands.velocity = 1.0;
61 right_hands.acceleration = 1.0;
62 right_hands.deceleration = 1.0;
63 right_hands.effort = 1.0;
64
65 msg->left_hands.push_back(left_hands);
66 msg->right_hands.push_back(right_hands);
67 msg->left_hand_type.value = 2;
68 msg->right_hand_type.value = 2;
69
70 publisher_->publish(std::move(msg));
71 }
72
73private:
74 rclcpp::Publisher<aimdk_msgs::msg::HandCommandArray>::SharedPtr publisher_;
75 rclcpp::TimerBase::SharedPtr timer_;
76
77 std::vector<std::pair<float, float>> position_pairs_;
78 size_t current_index_;
79
80 rclcpp::Time last_switch_time_;
81};
82
83int main(int argc, char *argv[]) {
84 rclcpp::init(argc, argv);
85 auto hand_control_node = std::make_shared<HandControl>();
86 rclcpp::spin(hand_control_node);
87 rclcpp::shutdown();
88 return 0;
89}
6.2.5 灵巧手控制 (升级中暂不开放)
该示例中用到了omnihand_control,通过往话题/aima/hal/joint/hand/command发布消息来控制夹爪的运动
注意
注意⚠️ :运行本示例前需要在机器人运控计算单元(PC1)使用aima em stop-app mc关闭原生运控模块,获取机器人控制权。注意确保机器人安全
1#include "aimdk_msgs/msg/hand_command.hpp"
2#include "aimdk_msgs/msg/hand_command_array.hpp"
3#include "aimdk_msgs/msg/hand_type.hpp"
4#include "aimdk_msgs/msg/message_header.hpp"
5#include "rclcpp/rclcpp.hpp"
6#include <chrono>
7#include <vector>
8
9/**
10 * @brief Omnihand control node
11 */
12class OmnihandControl : public rclcpp::Node {
13public:
14 OmnihandControl()
15 : Node("omnihand_control"), position_pairs_({
16 {1.0, 1.0}, // Hands fully open
17 {0.0, 0.0}, // Hands fully closed
18 {0.5, 0.5}, // Hands half open
19 {0.3, 0.7}, // Left closed right open
20 {0.8, 0.2} // Left open right closed
21 }),
22 current_index_(0) {
23 publisher_ = this->create_publisher<aimdk_msgs::msg::HandCommandArray>(
24 "/aima/hal/joint/hand/command", 10);
25
26 timer_ = this->create_wall_timer(
27 std::chrono::milliseconds(20), // 50Hz
28 std::bind(&OmnihandControl::publish_hand_commands, this));
29
30 last_switch_time_ = now();
31 RCLCPP_INFO(this->get_logger(), "Omnihand control node has been started!");
32 }
33
34 /**
35 * @brief Publish hand commands
36 */
37 void publish_hand_commands() {
38 // 1. Switches parameter set every 5 seconds
39 auto now_time = this->now();
40 if ((now_time - last_switch_time_).seconds() >= 5.0) {
41 current_index_ = (current_index_ + 1) % position_pairs_.size();
42 last_switch_time_ = now_time;
43 RCLCPP_INFO(
44 this->get_logger(),
45 "Switched to parameter set %zu, left hand = %.2f, right hand = %.2f",
46 current_index_, position_pairs_[current_index_].first,
47 position_pairs_[current_index_].second);
48 }
49
50 auto msg = std::make_unique<aimdk_msgs::msg::HandCommandArray>();
51 msg->header = aimdk_msgs::msg::MessageHeader();
52
53 float left_position = position_pairs_[current_index_].first;
54 float right_position = position_pairs_[current_index_].second;
55
56 // Left hand 10 joints
57 for (int i = 0; i < 10; ++i) {
58 aimdk_msgs::msg::HandCommand left_hands;
59 left_hands.name = "left_hand_joint" + std::to_string(i);
60 left_hands.position = left_position;
61 left_hands.velocity = 1.0;
62 left_hands.acceleration = 1.0;
63 left_hands.deceleration = 1.0;
64 left_hands.effort = 1.0;
65 msg->left_hands.push_back(left_hands);
66 }
67 // Right hand 10 joints
68 for (int i = 0; i < 10; ++i) {
69 aimdk_msgs::msg::HandCommand right_hands;
70 right_hands.name = "right_hand_joint" + std::to_string(i);
71 right_hands.position = right_position;
72 right_hands.velocity = 1.0;
73 right_hands.acceleration = 1.0;
74 right_hands.deceleration = 1.0;
75 right_hands.effort = 1.0;
76 msg->right_hands.push_back(right_hands);
77 }
78
79 // Type 1: Dexterous hand mode
80 msg->left_hand_type.value = 1;
81 msg->right_hand_type.value = 1;
82
83 publisher_->publish(std::move(msg));
84 }
85
86private:
87 // Member variables
88 rclcpp::Publisher<aimdk_msgs::msg::HandCommandArray>::SharedPtr publisher_;
89 rclcpp::TimerBase::SharedPtr timer_;
90
91 std::vector<std::pair<float, float>> position_pairs_;
92 size_t current_index_;
93 rclcpp::Time last_switch_time_;
94};
95
96/**
97 * @brief Main function
98 */
99// Initializes ROS, creates node, and starts spinning
100int main(int argc, char *argv[]) {
101 rclcpp::init(argc, argv);
102 auto omnihand_control_node = std::make_shared<OmnihandControl>();
103 rclcpp::spin(omnihand_control_node);
104 rclcpp::shutdown();
105 return 0;
106}
6.2.6 注册二开输入源
对于v0.7之后的版本,在实现对MC的控制前,需要先注册输入源。该示例中通过/aimdk_5Fmsgs/srv/SetMcInputSource服务来注册二开的输入源,让MC感知到,只有注册过输入源后才能实现机器人的速度控制
1#include "aimdk_msgs/srv/set_mc_input_source.hpp"
2#include "aimdk_msgs/msg/common_request.hpp"
3#include "aimdk_msgs/msg/common_response.hpp"
4#include "aimdk_msgs/msg/common_state.hpp"
5#include "aimdk_msgs/msg/common_task_response.hpp"
6#include "aimdk_msgs/msg/mc_input_action.hpp"
7#include "rclcpp/rclcpp.hpp"
8#include <chrono>
9#include <memory>
10#include <signal.h>
11
12std::shared_ptr<rclcpp::Node> g_node = nullptr;
13
14void signal_handler(int signal) {
15 if (g_node) {
16 RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
17 signal);
18 rclcpp::shutdown();
19 }
20}
21
22class McInputClient : public rclcpp::Node {
23public:
24 McInputClient() : Node("set_mc_input_source_client") {
25 client_ = this->create_client<aimdk_msgs::srv::SetMcInputSource>(
26 "/aimdk_5Fmsgs/srv/SetMcInputSource");
27
28 RCLCPP_INFO(this->get_logger(),
29 "Set MC input source service client created");
30
31 // Wait for the service to be available (with timeout)
32 const std::chrono::seconds timeout(8);
33 auto start_time = std::chrono::steady_clock::now();
34 while (!client_->wait_for_service(std::chrono::seconds(2))) {
35 if (std::chrono::steady_clock::now() - start_time > timeout) {
36 RCLCPP_ERROR(this->get_logger(), "Timed out waiting for service");
37 throw std::runtime_error("service unavailable");
38 }
39 RCLCPP_INFO(this->get_logger(), "Waiting for service to start...");
40 }
41 }
42
43 bool send_input_request() {
44 try {
45 auto request =
46 std::make_shared<aimdk_msgs::srv::SetMcInputSource::Request>();
47
48 // Set request data
49 request->action.value = 1001; // Add new input source
50 request->input_source.name = "node"; // Set message source
51 request->input_source.priority = 40; // Set priority
52 request->input_source.timeout = 1000; // Set timeout (ms)
53
54 RCLCPP_INFO(this->get_logger(), "Sending input source request: (ID=%d)",
55 request->action.value);
56
57 auto timeout = std::chrono::milliseconds(250);
58
59 for (int i = 0; i < 8; i++) {
60 // Set header timestamp
61 request->request.header.stamp = this->now(); // use Node::now()
62 auto future = client_->async_send_request(request);
63 auto retcode = rclcpp::spin_until_future_complete(
64 this->shared_from_this(), future, timeout);
65 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
66 // retry as remote peer: mc module can be under heavy load
67 RCLCPP_INFO(this->get_logger(),
68 "trying to register input source... [%d]", i);
69 continue;
70 }
71 // future.done
72 auto response = future.get();
73 auto code = response->response.header.code;
74 RCLCPP_INFO(
75 this->get_logger(), "Input source set %s: code=%ld, task_id=%lu",
76 code ? "failed" : "succeeded", code, response->response.task_id);
77 return true;
78 }
79 RCLCPP_ERROR(this->get_logger(), "Service call failed or timed out");
80 return false;
81 } catch (const std::exception &e) {
82 RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
83 return false;
84 }
85 }
86
87private:
88 rclcpp::Client<aimdk_msgs::srv::SetMcInputSource>::SharedPtr client_;
89};
90
91int main(int argc, char *argv[]) {
92 try {
93 rclcpp::init(argc, argv);
94 signal(SIGINT, signal_handler);
95 signal(SIGTERM, signal_handler);
96
97 auto client_node = std::make_shared<McInputClient>();
98 g_node = client_node;
99
100 if (!client_node->send_input_request()) {
101 RCLCPP_ERROR(client_node->get_logger(),
102 "Set input source request failed");
103 }
104
105 // Shutdown ROS first, then release resources
106 rclcpp::shutdown();
107 client_node.reset();
108 g_node.reset();
109
110 return 0;
111 } catch (const std::exception &e) {
112 RCLCPP_ERROR(rclcpp::get_logger("main"),
113 "Program terminated with exception: %s", e.what());
114 rclcpp::shutdown();
115 return 1;
116 }
117}
6.2.7 获取当前输入源
该示例中用到了GetCurrentInputSource服务,用于获取当前已注册的输入源信息,包括输入源名称、优先级和超时时间等信息。
1#include "aimdk_msgs/srv/get_current_input_source.hpp"
2#include "aimdk_msgs/msg/common_request.hpp"
3#include "aimdk_msgs/msg/response_header.hpp"
4#include "rclcpp/rclcpp.hpp"
5#include <chrono>
6#include <memory>
7#include <signal.h>
8
9// Global node object
10std::shared_ptr<rclcpp::Node> g_node = nullptr;
11
12// Signal handler
13void signal_handler(int signal) {
14 if (g_node) {
15 RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
16 signal);
17 rclcpp::shutdown();
18 }
19}
20
21// Client Class
22class GetCurrentInputSourceClient : public rclcpp::Node {
23public:
24 GetCurrentInputSourceClient() : Node("get_current_input_source_client") {
25 const std::chrono::seconds timeout(8);
26
27 client_ = this->create_client<aimdk_msgs::srv::GetCurrentInputSource>(
28 "/aimdk_5Fmsgs/srv/GetCurrentInputSource");
29
30 RCLCPP_INFO(this->get_logger(), "GetCurrentInputSource client created");
31
32 auto start_time = std::chrono::steady_clock::now();
33 while (!client_->wait_for_service(std::chrono::seconds(2))) {
34 if (std::chrono::steady_clock::now() - start_time > timeout) {
35 RCLCPP_ERROR(this->get_logger(), "Waiting for service timed out");
36 rclcpp::shutdown();
37 return;
38 }
39 RCLCPP_INFO(this->get_logger(),
40 "Service not available yet, continuing to wait...");
41 }
42 }
43
44 void send_request() {
45 try {
46 auto request =
47 std::make_shared<aimdk_msgs::srv::GetCurrentInputSource::Request>();
48 request->request = aimdk_msgs::msg::CommonRequest();
49
50 RCLCPP_INFO(this->get_logger(),
51 "Sending request to get current input source");
52
53 auto timeout = std::chrono::milliseconds(250);
54
55 for (int i = 0; i < 8; i++) {
56 request->request.header.stamp = this->now();
57 auto future = client_->async_send_request(request);
58 auto retcode = rclcpp::spin_until_future_complete(
59 this->shared_from_this(), future, timeout);
60 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
61 // retry as remote peer: mc module can be under heavy load
62 RCLCPP_INFO(this->get_logger(),
63 "trying to register input source... [%d]", i);
64 continue;
65 }
66 // future.done
67 auto response = future.get();
68 if (response->response.header.code == 0) {
69 RCLCPP_INFO(this->get_logger(), "Current input source: %s",
70 response->input_source.name.c_str());
71 RCLCPP_INFO(this->get_logger(), "Priority: %d",
72 response->input_source.priority);
73 RCLCPP_INFO(this->get_logger(), "Timeout: %d",
74 response->input_source.timeout);
75 } else {
76 RCLCPP_WARN(this->get_logger(),
77 "Service returned failure, return code: %ld",
78 response->response.header.code);
79 }
80 return;
81 }
82 RCLCPP_ERROR(this->get_logger(), "Service call failed or timed out");
83 } catch (const std::exception &e) {
84 RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
85 }
86 }
87
88private:
89 rclcpp::Client<aimdk_msgs::srv::GetCurrentInputSource>::SharedPtr client_;
90};
91
92int main(int argc, char *argv[]) {
93 try {
94 rclcpp::init(argc, argv);
95
96 signal(SIGINT, signal_handler);
97 signal(SIGTERM, signal_handler);
98
99 g_node = std::make_shared<GetCurrentInputSourceClient>();
100 auto client =
101 std::dynamic_pointer_cast<GetCurrentInputSourceClient>(g_node);
102
103 if (client) {
104 client->send_request();
105 }
106
107 g_node.reset();
108 rclcpp::shutdown();
109 return 0;
110 } catch (const std::exception &e) {
111 RCLCPP_ERROR(rclcpp::get_logger("main"),
112 "Program exited with exception: %s", e.what());
113 return 1;
114 }
115}
使用说明
# 获取当前输入源信息
ros2 run examples get_current_input_source
输出示例
[INFO] [get_current_input_source_client]: 当前输入源: node
[INFO] [get_current_input_source_client]: 优先级: 40
[INFO] [get_current_input_source_client]: 超时时间: 1000
注意事项
确保GetCurrentInputSource服务正常运行
需要在注册输入源之后才能获取到有效信息
状态码为0表示查询成功
6.2.8 机器人走跑控制
该示例中用到了mc_locomotion_velocity,以下示例通过发布/aima/mc/locomotion/velocity话题控制机器人的行走,对于v0.7之后的版本,实现速度控制前需要注册输入源(该示例已实现注册输入源),具体注册步骤可参考代码。
1#include "aimdk_msgs/msg/mc_locomotion_velocity.hpp"
2#include "aimdk_msgs/msg/common_request.hpp"
3#include "aimdk_msgs/msg/common_response.hpp"
4#include "aimdk_msgs/msg/common_state.hpp"
5#include "aimdk_msgs/msg/common_task_response.hpp"
6#include "aimdk_msgs/msg/mc_input_action.hpp"
7#include "aimdk_msgs/msg/message_header.hpp"
8#include "aimdk_msgs/srv/set_mc_input_source.hpp"
9
10#include "rclcpp/rclcpp.hpp"
11#include <chrono>
12#include <memory>
13#include <signal.h>
14#include <thread>
15
16class DirectVelocityControl : public rclcpp::Node {
17public:
18 DirectVelocityControl() : Node("direct_velocity_control") {
19 // Create publisher
20 publisher_ = this->create_publisher<aimdk_msgs::msg::McLocomotionVelocity>(
21 "/aima/mc/locomotion/velocity", 10);
22 // Create service client
23 client_ = this->create_client<aimdk_msgs::srv::SetMcInputSource>(
24 "/aimdk_5Fmsgs/srv/SetMcInputSource");
25
26 // Maximum speed limits
27 max_forward_speed_ = 1.0; // m/s
28 max_lateral_speed_ = 0.5; // m/s
29 max_angular_speed_ = 1.0; // rad/s
30 // Minimum speed limits (0 is also OK)
31 min_forward_speed_ = 0.2; // m/s
32 min_lateral_speed_ = 0.2; // m/s
33 min_angular_speed_ = 0.1; // rad/s
34
35 RCLCPP_INFO(this->get_logger(), "Direct velocity control node started.");
36 }
37
38 void start_publish() {
39 if (timer_ != nullptr) {
40 return;
41 }
42 // Set timer to periodically publish velocity messages (50Hz)
43 timer_ = this->create_wall_timer(
44 std::chrono::milliseconds(20),
45 std::bind(&DirectVelocityControl::publish_velocity, this));
46 }
47
48 bool register_input_source() {
49 const std::chrono::seconds timeout(8);
50 auto start_time = std::chrono::steady_clock::now();
51 while (!client_->wait_for_service(std::chrono::seconds(2))) {
52 if (std::chrono::steady_clock::now() - start_time > timeout) {
53 RCLCPP_ERROR(this->get_logger(), "Waiting for service timed out");
54 return false;
55 }
56 RCLCPP_INFO(this->get_logger(), "Waiting for input source service...");
57 }
58
59 auto request =
60 std::make_shared<aimdk_msgs::srv::SetMcInputSource::Request>();
61 request->action.value = 1001;
62 request->input_source.name = "node";
63 request->input_source.priority = 40;
64 request->input_source.timeout = 1000;
65
66 auto timeout2 = std::chrono::milliseconds(250);
67
68 for (int i = 0; i < 8; i++) {
69 request->request.header.stamp = this->now();
70 auto future = client_->async_send_request(request);
71 auto retcode = rclcpp::spin_until_future_complete(
72 this->shared_from_this(), future, timeout2);
73 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
74 // retry as remote peer: mc module can be under heavy load
75 RCLCPP_INFO(this->get_logger(),
76 "trying to register input source... [%d]", i);
77 continue;
78 }
79 // future.done
80 auto response = future.get();
81 int state = response->response.state.value;
82 RCLCPP_INFO(this->get_logger(),
83 "Set input source succeeded: state=%d, task_id=%lu", state,
84 response->response.task_id);
85 return true;
86 }
87 RCLCPP_ERROR(this->get_logger(), "Service call failed or timed out");
88 return false;
89 }
90
91 void publish_velocity() {
92 auto msg = std::make_unique<aimdk_msgs::msg::McLocomotionVelocity>();
93 msg->header = aimdk_msgs::msg::MessageHeader();
94 msg->header.stamp = this->now();
95 msg->source = "node"; // Set message source
96 msg->forward_velocity = forward_velocity_;
97 msg->lateral_velocity = lateral_velocity_;
98 msg->angular_velocity = angular_velocity_;
99
100 publisher_->publish(std::move(msg));
101 RCLCPP_INFO(this->get_logger(),
102 "Published velocity: Forward %.2f m/s, Lateral %.2f m/s, "
103 "Angular %.2f rad/s",
104 forward_velocity_, lateral_velocity_, angular_velocity_);
105 }
106
107 void clear_velocity() {
108 forward_velocity_ = 0.0;
109 lateral_velocity_ = 0.0;
110 angular_velocity_ = 0.0;
111 }
112
113 bool set_forward(double forward) {
114 if (abs(forward) < 0.005) {
115 forward_velocity_ = 0.0;
116 return true;
117 } else if ((abs(forward) > max_forward_speed_) ||
118 (abs(forward) < min_forward_speed_)) {
119 RCLCPP_ERROR(this->get_logger(), "input value out of range, exiting");
120 return false;
121 } else {
122 forward_velocity_ = forward;
123 return true;
124 }
125 }
126
127 bool set_lateral(double lateral) {
128 if (abs(lateral) < 0.005) {
129 lateral_velocity_ = 0.0;
130 return true;
131 } else if ((abs(lateral) > max_lateral_speed_) ||
132 (abs(lateral) < min_lateral_speed_)) {
133 RCLCPP_ERROR(this->get_logger(), "input value out of range, exiting");
134 return false;
135 } else {
136 lateral_velocity_ = lateral;
137 return true;
138 }
139 }
140
141 bool set_angular(double angular) {
142 if (abs(angular) < 0.005) {
143 angular_velocity_ = 0.0;
144 return true;
145 } else if ((abs(angular) > max_angular_speed_) ||
146 (abs(angular) < min_angular_speed_)) {
147 RCLCPP_ERROR(this->get_logger(), "input value out of range, exiting");
148 return false;
149 } else {
150 angular_velocity_ = angular;
151 return true;
152 }
153 }
154
155private:
156 rclcpp::Publisher<aimdk_msgs::msg::McLocomotionVelocity>::SharedPtr
157 publisher_;
158 rclcpp::Client<aimdk_msgs::srv::SetMcInputSource>::SharedPtr client_;
159 rclcpp::TimerBase::SharedPtr timer_;
160
161 double forward_velocity_;
162 double lateral_velocity_;
163 double angular_velocity_;
164
165 double max_forward_speed_;
166 double max_lateral_speed_;
167 double max_angular_speed_;
168
169 double min_forward_speed_;
170 double min_lateral_speed_;
171 double min_angular_speed_;
172};
173
174// Signal Processing
175std::shared_ptr<DirectVelocityControl> global_node = nullptr;
176void signal_handler(int sig) {
177 if (global_node) {
178 global_node->clear_velocity();
179 RCLCPP_INFO(global_node->get_logger(),
180 "Received signal %d: clearing velocity and shutting down", sig);
181 }
182 rclcpp::shutdown();
183}
184
185int main(int argc, char *argv[]) {
186 rclcpp::init(argc, argv);
187 auto node = std::make_shared<DirectVelocityControl>();
188 global_node = node;
189 signal(SIGINT, signal_handler);
190 signal(SIGTERM, signal_handler);
191
192 if (!node->register_input_source()) {
193 RCLCPP_ERROR(node->get_logger(),
194 "Input source registration failed, exiting");
195 rclcpp::shutdown();
196 return 1;
197 }
198
199 // get and check control values
200 // notice that mc has thresholds to start movement
201 double forward, lateral, angular;
202 std::cout << "Enter forward speed 0 or ±(0.2 ~ 1.0) m/s: ";
203 std::cin >> forward;
204 if (!node->set_forward(forward)) {
205 return 2;
206 }
207 std::cout << "Enter lateral speed 0 or ±(0.2 ~ 0.5) m/s: ";
208 std::cin >> lateral;
209 if (!node->set_lateral(lateral)) {
210 return 2;
211 }
212 std::cout << "Enter angular speed 0 or ±(0.1 ~ 1.0) rad/s: ";
213 std::cin >> angular;
214 if (!node->set_angular(angular)) {
215 return 2;
216 }
217
218 RCLCPP_INFO(node->get_logger(), "Setting velocity; moving for 5 seconds");
219
220 node->start_publish();
221
222 auto start_time = node->now();
223 while ((node->now() - start_time).seconds() < 5.0) {
224 rclcpp::spin_some(node);
225 std::this_thread::sleep_for(std::chrono::milliseconds(1));
226 }
227
228 node->clear_velocity();
229 RCLCPP_INFO(node->get_logger(), "5 seconds elapsed; robot stopped");
230
231 rclcpp::spin(node);
232 rclcpp::shutdown();
233 return 0;
234}
6.2.9 关节电机控制
本示例展示了如何使用ROS2和Ruckig库来控制机器人的关节运动。
注意
注意⚠️ :运行本示例前需要在机器人运控计算单元(PC1)使用aima em stop-app mc关闭原生运控模块,获取机器人控制权。注意确保机器人安全
!该示例代码直接对底层电机也就是HAL层进行控制,在运行程序前请检查代码中对关节的安全限位是否与所用机器人相符, 并确保安全!
机器人关节控制示例
这个示例展示了如何使用ROS2和Ruckig库来控制机器人的关节运动。示例实现了以下功能:
机器人关节模型定义
基于Ruckig的轨迹插补
多关节协同控制
实时位置、速度和加速度控制
依赖项
ROS2
Ruckig库
aimdk_msgs包
构建说明
将代码放入ROS2工作空间的src目录
在CMakeLists.txt中添加:
find_package(rclcpp REQUIRED)
find_package(aimdk_msgs REQUIRED)
find_package(ruckig REQUIRED)
add_executable(joint_control_example joint_control_example.cpp)
ament_target_dependencies(joint_control_example
rclcpp
aimdk_msgs
ruckig
)
在package.xml中添加依赖:
<depend>rclcpp</depend>
<depend>aimdk_msgs</depend>
<depend>ruckig</depend>
示例功能说明
创建了四个控制器节点,分别控制:
腿部x2(12个关节)
腰部x1(3个关节)
手臂x2(14个关节)
头部x1(2个关节)
演示功能:
每10秒让指定关节在正负0.5弧度之间摆动
使用Ruckig库实现平滑的运动轨迹
实时发布关节控制命令
自定义使用
添加新的控制逻辑:
修改
SetTargetPosition函数添加新的控制回调函数
调整控制频率:
修改
control_timer_的周期(当前为2ms)
1#include <aimdk_msgs/msg/joint_command_array.hpp>
2#include <aimdk_msgs/msg/joint_state_array.hpp>
3#include <atomic>
4#include <cstdlib>
5#include <memory>
6#include <rclcpp/rclcpp.hpp>
7#include <ruckig/ruckig.hpp>
8#include <signal.h>
9#include <string>
10#include <unordered_map>
11#include <vector>
12
13/**
14 * @brief Global variables and signal handling
15 */
16// Global variables to control program state
17std::atomic<bool> g_running(true);
18std::atomic<bool> g_emergency_stop(false);
19
20// Signal handler function
21void signal_handler(int) {
22 g_running = false;
23 RCLCPP_INFO(rclcpp::get_logger("main"),
24 "Received termination signal, shutting down...");
25}
26
27/**
28 * @brief Robot model definition
29 */
30enum class JointArea {
31 HEAD, // Head joints
32 ARM, // Arm joints
33 WAIST, // Waist joints
34 LEG, // Leg joints
35};
36
37/**
38 * @brief Joint information structure
39 */
40struct JointInfo {
41 std::string name; // Joint name
42 double lower_limit; // Joint angle lower limit
43 double upper_limit; // Joint angle upper limit
44 double kp; // Position control gain
45 double kd; // Velocity control gain
46};
47
48/**
49 * @brief Robot model configuration
50 * Contains parameters for all joints, enabling or disabling specific joints as
51 * needed
52 */
53std::map<JointArea, std::vector<JointInfo>> robot_model = {
54 {JointArea::LEG,
55 {
56 // Left leg joint configuration
57 {"left_hip_pitch_joint", -2.4871, 2.4871, 40.0, 4.0}, // Left hip pitch
58 {"left_hip_roll_joint", -0.12217, 2.9059, 40.0, 4.0}, // Left hip roll
59 {"left_hip_yaw_joint", -1.6842, 3.4296, 30.0, 3.0}, // Left hip yaw
60 {"left_knee_joint", 0.026179, 2.1206, 80.0, 8.0}, // Left knee
61 {"left_ankle_pitch_joint", -0.80285, 0.45378, 40.0,
62 4.0}, // Left ankle pitch
63 {"left_ankle_roll_joint", -0.2618, 0.2618, 20.0,
64 2.0}, // Left ankle roll
65 // Right leg joint configuration
66 {"right_hip_pitch_joint", -2.4871, 2.4871, 40.0,
67 4.0}, // Right hip pitch
68 {"right_hip_roll_joint", -2.9059, 0.12217, 40.0,
69 4.0}, // Right hip roll
70 {"right_hip_yaw_joint", -3.4296, 1.6842, 30.0, 3.0}, // Right hip yaw
71 {"right_knee_joint", 0.026179, 2.1206, 80.0, 8.0}, // Right knee
72 {"right_ankle_pitch_joint", -0.80285, 0.45378, 40.0,
73 4.0}, // Right ankle pitch
74 {"right_ankle_roll_joint", -0.2618, 0.2618, 20.0,
75 2.0}, // Right ankle roll
76 }},
77
78 {JointArea::WAIST,
79 {
80 // Waist joint configuration
81 {"waist_yaw_joint", -3.4296, 2.3824, 20.0, 4.0}, // Waist yaw
82 {"waist_pitch_joint", -0.17453, 0.17453, 20.0, 4.0}, // Waist pitch
83 {"waist_roll_joint", -0.48869, 0.48869, 20.0, 4.0}, // Waist roll
84 }},
85 {JointArea::ARM,
86 {
87 // Left arm joint configuration
88 {"left_shoulder_pitch_joint", -2.5569, 2.5569, 20.0,
89 2.0}, // Left shoulder pitch
90 {"left_shoulder_roll_joint", -0.06108, 3.3598, 20.0,
91 2.0}, // Left shoulder roll
92 {"left_shoulder_yaw_joint", -2.5569, 2.5569, 20.0,
93 2.0}, // Left shoulder yaw
94 {"left_elbow_pitch_joint", -2.4435, 0.0, 20.0,
95 2.0}, // Left elbow pitch
96 {"left_elbow_roll_joint", -2.5569, 2.5569, 20.0,
97 2.0}, // Left elbow roll
98 {"left_wrist_pitch_joint", -0.5585, 0.5585, 20.0,
99 2.0}, // Left wrist pitch
100 {"left_wrist_yaw_joint", -1.5446, 1.5446, 20.0,
101 2.0}, // Left wrist yaw
102 // Right arm joint configuration
103 {"right_shoulder_pitch_joint", -2.5569, 2.5569, 20.0,
104 2.0}, // Right shoulder pitch
105 {"right_shoulder_roll_joint", -3.3598, 0.06108, 20.0,
106 2.0}, // Right shoulder roll
107 {"right_shoulder_yaw_joint", -2.5569, 2.5569, 20.0,
108 2.0}, // Right shoulder yaw
109 {"right_elbow_pitch_joint", 0.0, 2.4435, 20.0,
110 2.0}, // Right elbow pitch
111 {"right_elbow_roll_joint", -2.5569, 2.5569, 20.0,
112 2.0}, // Right elbow roll
113 {"right_wrist_pitch_joint", -0.5585, 0.5585, 20.0,
114 2.0}, // Right wrist pitch
115 {"right_wrist_yaw_joint", -1.5446, 1.5446, 20.0,
116 2.0}, // Right wrist yaw
117 }},
118 {JointArea::HEAD,
119 {
120 // Head joint configuration
121 {"head_pitch_joint", -0.61087, 0.61087, 20.0, 2.0}, // Head pitch
122 {"head_yaw_joint", -0.61087, 0.61087, 20.0, 2.0}, // Head yaw
123 }},
124};
125
126/**
127 * @brief Joint controller node class
128 * @tparam DOFs Degrees of freedom
129 * @tparam Area Joint area
130 */
131template <int DOFs, JointArea Area>
132class JointControllerNode : public rclcpp::Node {
133public:
134 /**
135 * @brief Constructor
136 * @param node_name Node name
137 * @param sub_topic Subscription topic name
138 * @param pub_topic Publication topic name
139 * @param qos QoS configuration
140 */
141 JointControllerNode(std::string node_name, std::string sub_topic,
142 std::string pub_topic,
143 rclcpp::QoS qos = rclcpp::SensorDataQoS())
144 : Node(node_name), ruckig(0.002) {
145 joint_info_ = robot_model[Area];
146 if (joint_info_.size() != DOFs) {
147 RCLCPP_ERROR(this->get_logger(), "Joint count mismatch.");
148 exit(1);
149 }
150
151 // Set motion constraints for Ruckig trajectory planner
152 for (int i = 0; i < DOFs; ++i) {
153 input.max_velocity[i] = 1.0; // Max velocity limit
154 input.max_acceleration[i] = 1.0; // Max acceleration limit
155 input.max_jerk[i] = 25.0; // Max jerk (change of acceleration) limit
156 }
157
158 // Create joint state subscriber
159 sub_ = this->create_subscription<aimdk_msgs::msg::JointStateArray>(
160 sub_topic, qos,
161 std::bind(&JointControllerNode::JointStateCallback, this,
162 std::placeholders::_1));
163
164 // Create joint command publisher
165 pub_ = this->create_publisher<aimdk_msgs::msg::JointCommandArray>(pub_topic,
166 qos);
167 }
168
169private:
170 // Ruckig trajectory planner variables
171 ruckig::Ruckig<DOFs> ruckig; // Trajectory planner instance
172 ruckig::InputParameter<DOFs> input; // Input parameters
173 ruckig::OutputParameter<DOFs> output; // Output parameters
174 bool ruckig_initialized_ = false; // Trajectory planner initialization flag
175 std::vector<JointInfo> joint_info_; // Joint information list
176
177 // ROS communication variables
178 rclcpp::Subscription<aimdk_msgs::msg::JointStateArray>::SharedPtr
179 sub_; // State subscriber
180 rclcpp::Publisher<aimdk_msgs::msg::JointCommandArray>::SharedPtr
181 pub_; // Command publisher
182
183 /**
184 * @brief Joint state callback function
185 * @param msg Joint state message
186 */
187 void
188 JointStateCallback(const aimdk_msgs::msg::JointStateArray::SharedPtr msg) {
189 // Initialize trajectory planner on first state reception
190 if (!ruckig_initialized_) {
191 for (int i = 0; i < DOFs; ++i) {
192 input.current_position[i] = msg->joints[i].position;
193 input.current_velocity[i] = msg->joints[i].velocity;
194 input.current_acceleration[i] = 0.0;
195 }
196 ruckig_initialized_ = true;
197 RCLCPP_INFO(this->get_logger(),
198 "Ruckig trajectory planner initialization complete");
199 }
200 }
201
202public:
203 /**
204 * @brief Set target joint position
205 * @param joint_name Joint name
206 * @param target_position Target position
207 * @return Whether the target position was successfully set
208 */
209 bool SetTargetPosition(std::string joint_name, double target_position) {
210 if (!ruckig_initialized_) {
211 RCLCPP_WARN(this->get_logger(),
212 "Ruckig trajectory planner not initialized");
213 return false;
214 }
215
216 // Find target joint and set its position
217 int target_joint = -1;
218 for (int i = 0; i < DOFs; ++i) {
219 if (joint_info_[i].name == joint_name) {
220 // Check if target position is within limits
221 if (target_position < joint_info_[i].lower_limit ||
222 target_position > joint_info_[i].upper_limit) {
223 RCLCPP_ERROR(
224 this->get_logger(),
225 "Target position %.3f exceeds limit for joint %s [%.3f, %.3f]",
226 target_position, joint_name.c_str(), joint_info_[i].lower_limit,
227 joint_info_[i].upper_limit);
228 return false;
229 }
230 input.target_position[i] = target_position;
231 input.target_velocity[i] = 0.0;
232 input.target_acceleration[i] = 0.0;
233 target_joint = i;
234 } else {
235 input.target_position[i] = input.current_position[i];
236 input.target_velocity[i] = 0.0;
237 input.target_acceleration[i] = 0.0;
238 }
239 }
240
241 if (target_joint == -1) {
242 RCLCPP_ERROR(this->get_logger(), "Joint %s not found",
243 joint_name.c_str());
244 return false;
245 }
246
247 // Perform trajectory planning and send command using Ruckig
248 const double tolerance = 1e-6;
249 while (g_running && rclcpp::ok() && !g_emergency_stop) {
250 auto result = ruckig.update(input, output);
251 if (result != ruckig::Result::Working &&
252 result != ruckig::Result::Finished) {
253 RCLCPP_WARN(this->get_logger(), "Trajectory planning failed");
254 break;
255 }
256
257 // Update current state
258 for (int i = 0; i < DOFs; ++i) {
259 input.current_position[i] = output.new_position[i];
260 input.current_velocity[i] = output.new_velocity[i];
261 input.current_acceleration[i] = output.new_acceleration[i];
262 }
263
264 // Check if target position is reached
265 if (std::abs(output.new_position[target_joint] - target_position) <
266 tolerance) {
267 RCLCPP_INFO(this->get_logger(), "Joint %s reached target position",
268 joint_name.c_str());
269 break;
270 }
271
272 // Create and send joint command
273 aimdk_msgs::msg::JointCommandArray cmd;
274 cmd.joints.resize(DOFs);
275 for (int i = 0; i < DOFs; ++i) {
276 auto &joint = joint_info_[i];
277 cmd.joints[i].name = joint.name;
278 cmd.joints[i].position = output.new_position[i];
279 cmd.joints[i].velocity = output.new_velocity[i];
280 cmd.joints[i].stiffness = joint.kp;
281 cmd.joints[i].damping = joint.kd;
282 }
283 pub_->publish(cmd);
284
285 // Short delay to avoid excessive CPU usage
286 std::this_thread::sleep_for(std::chrono::milliseconds(2));
287 }
288
289 return true;
290 }
291
292 /**
293 * @brief Safely stop all joints
294 */
295 void safe_stop() {
296 if (!ruckig_initialized_) {
297 RCLCPP_WARN(this->get_logger(), "Ruckig trajectory planner not "
298 "initialized, cannot perform safe stop");
299 return;
300 }
301
302 RCLCPP_INFO(this->get_logger(), "Performing safe stop...");
303
304 // Set all joint target positions to current positions
305 for (int i = 0; i < DOFs; ++i) {
306 input.target_position[i] = input.current_position[i];
307 input.target_velocity[i] = 0.0;
308 input.target_acceleration[i] = 0.0;
309 }
310
311 // Send final command to ensure joints stop
312 aimdk_msgs::msg::JointCommandArray cmd;
313 cmd.joints.resize(DOFs);
314 for (int i = 0; i < DOFs; ++i) {
315 auto &joint = joint_info_[i];
316 cmd.joints[i].name = joint.name;
317 cmd.joints[i].position = input.current_position[i];
318 cmd.joints[i].velocity = 0.0;
319 cmd.joints[i].stiffness = joint.kp;
320 cmd.joints[i].damping = joint.kd;
321 }
322 pub_->publish(cmd);
323
324 RCLCPP_INFO(this->get_logger(), "Safe stop complete");
325 }
326
327 /**
328 * @brief Emergency stop for all joints
329 */
330 void emergency_stop() {
331 g_emergency_stop = true;
332 safe_stop();
333 RCLCPP_ERROR(this->get_logger(), "Emergency stop triggered");
334 }
335};
336
337/**
338 * @brief Main function
339 */
340int main(int argc, char *argv[]) {
341 rclcpp::init(argc, argv);
342
343 // Set up signal handling
344 signal(SIGINT, signal_handler);
345 signal(SIGTERM, signal_handler);
346
347 try {
348 // Create leg controller node
349 auto leg_node = std::make_shared<JointControllerNode<12, JointArea::LEG>>(
350 "leg_node", "/aima/hal/joint/leg/state", "/aima/hal/joint/leg/command");
351
352 // Create timer node
353 rclcpp::Node::SharedPtr timer_node =
354 rclcpp::Node::make_shared("timer_node");
355 double position = 0.8;
356
357 // Create timer callback function
358 auto timer = timer_node->create_wall_timer(std::chrono::seconds(3), [&]() {
359 if (!g_running || g_emergency_stop)
360 return; // If the program is shutting down or emergency stopped, do not
361 // execute new actions
362 position = -position;
363 position = 1.3 + position;
364 if (!leg_node->SetTargetPosition("left_knee_joint", position)) {
365 RCLCPP_ERROR(rclcpp::get_logger("main"),
366 "Failed to set target position");
367 }
368 });
369
370 // Create executor
371 rclcpp::executors::MultiThreadedExecutor executor;
372 executor.add_node(leg_node);
373 executor.add_node(timer_node);
374
375 // Main loop
376 while (g_running && rclcpp::ok() && !g_emergency_stop) {
377 executor.spin_once(std::chrono::milliseconds(100));
378 }
379
380 // Safely stop all joints
381 RCLCPP_INFO(rclcpp::get_logger("main"), "Safely stopping all joints...");
382 leg_node->safe_stop();
383
384 // Wait a short time to ensure command transmission is complete
385 std::this_thread::sleep_for(std::chrono::milliseconds(100));
386
387 // Clean up resources
388 RCLCPP_INFO(rclcpp::get_logger("main"), "Cleaning up resources...");
389 leg_node.reset();
390 timer_node.reset();
391
392 } catch (const std::exception &e) {
393 RCLCPP_ERROR(rclcpp::get_logger("main"), "Exception occurred: %s",
394 e.what());
395 g_emergency_stop = true;
396 } catch (...) {
397 RCLCPP_ERROR(rclcpp::get_logger("main"), "Unknown exception occurred");
398 g_emergency_stop = true;
399 }
400
401 RCLCPP_INFO(rclcpp::get_logger("main"), "Program exited safely");
402 rclcpp::shutdown();
403 return 0;
404}
6.2.10 键盘控制机器人
本示例实现了通过PC的键盘输入控制机器人的前进后退转弯的功能。
通过WASD控制机器人行走方向,增加/减少速度(±0.2 m/s),Q/E增加/减少角速度(±0.1 rad/s),ESC退出程序并释放终端资源,Space立即将速度归零,执行急停。
小心
注意:运行本示例前需要先使用手柄,将机器人切入稳定站立模式。(位控站立/走跑模式时按R2+X, 其他模式详见模式流转图进行操作),然后在机器人的终端界面使用:aima em stop-app rc关闭遥控器,防止通道占用。
实现键盘控制前需要注册输入源(该示例已实现注册输入源)
运行前需要安装curse模块:
sudo apt install libncurses-dev
1#include "aimdk_msgs/msg/common_request.hpp"
2#include "aimdk_msgs/msg/common_response.hpp"
3#include "aimdk_msgs/msg/common_state.hpp"
4#include "aimdk_msgs/msg/common_task_response.hpp"
5#include "aimdk_msgs/msg/mc_input_action.hpp"
6#include "aimdk_msgs/msg/mc_locomotion_velocity.hpp"
7#include "aimdk_msgs/msg/message_header.hpp"
8#include "aimdk_msgs/srv/set_mc_input_source.hpp"
9
10#include <algorithm>
11#include <chrono>
12#include <csignal>
13#include <curses.h>
14#include <rclcpp/rclcpp.hpp>
15
16using aimdk_msgs::msg::McLocomotionVelocity;
17using std::placeholders::_1;
18
19class KeyboardVelocityController : public rclcpp::Node {
20public:
21 KeyboardVelocityController()
22 : Node("keyboard_velocity_controller"), forward_velocity_(0.0),
23 lateral_velocity_(0.0), angular_velocity_(0.0), step_(0.2),
24 angular_step_(0.1) {
25 pub_ = this->create_publisher<McLocomotionVelocity>(
26 "/aima/mc/locomotion/velocity", 10);
27 client_ = this->create_client<aimdk_msgs::srv::SetMcInputSource>(
28 "/aimdk_5Fmsgs/srv/SetMcInputSource");
29 // Register input source
30 if (!register_input_source()) {
31 RCLCPP_ERROR(this->get_logger(),
32 "Input source registration failed, exiting");
33 throw std::runtime_error("Input source registration failed");
34 }
35 // Initialize ncurses
36 initscr();
37 cbreak();
38 noecho();
39 keypad(stdscr, TRUE);
40 nodelay(stdscr, TRUE);
41
42 timer_ = this->create_wall_timer(
43 std::chrono::milliseconds(50),
44 std::bind(&KeyboardVelocityController::checkKeyAndPublish, this));
45
46 RCLCPP_INFO(this->get_logger(),
47 "Control started: W/S Forward/Backward | A/D Strafe Left/Right "
48 "| Q/E Turn Left/Right | Space Stop | ESC Exit");
49 }
50
51 ~KeyboardVelocityController() {
52 endwin(); // Restore terminal
53 }
54
55private:
56 rclcpp::Publisher<McLocomotionVelocity>::SharedPtr pub_;
57 rclcpp::Client<aimdk_msgs::srv::SetMcInputSource>::SharedPtr client_;
58 rclcpp::TimerBase::SharedPtr timer_;
59
60 float forward_velocity_, lateral_velocity_, angular_velocity_;
61 const float step_, angular_step_;
62
63 bool register_input_source() {
64 const std::chrono::seconds srv_timeout(8);
65 auto start_time = std::chrono::steady_clock::now();
66 while (!client_->wait_for_service(std::chrono::seconds(2))) {
67 if (std::chrono::steady_clock::now() - start_time > srv_timeout) {
68 RCLCPP_ERROR(this->get_logger(), "Waiting for service timed out");
69 return false;
70 }
71 RCLCPP_INFO(this->get_logger(), "Waiting for input source service...");
72 }
73
74 auto request =
75 std::make_shared<aimdk_msgs::srv::SetMcInputSource::Request>();
76 request->action.value = 1001;
77 request->input_source.name = "node";
78 request->input_source.priority = 40;
79 request->input_source.timeout = 1000;
80
81 auto timeout = std::chrono::milliseconds(250);
82
83 for (int i = 0; i < 8; i++) {
84 request->request.header.stamp = this->now();
85 auto future = client_->async_send_request(request);
86 auto retcode = rclcpp::spin_until_future_complete(
87 this->get_node_base_interface(), future, timeout);
88 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
89 // retry as remote peer: mc module can be under heavy load
90 RCLCPP_INFO(this->get_logger(),
91 "trying to register input source... [%d]", i);
92 continue;
93 }
94 // future.done
95 auto response = future.get();
96 int state = response->response.state.value;
97 RCLCPP_INFO(this->get_logger(),
98 "Set input source succeeded: state=%d, task_id=%lu", state,
99 response->response.task_id);
100 return true;
101 }
102 RCLCPP_ERROR(this->get_logger(), "Service call failed or timed out");
103 return false;
104 }
105
106 void checkKeyAndPublish() {
107 int ch = getch(); // non-blocking read
108
109 switch (ch) {
110 case ' ': // Space key
111 forward_velocity_ = 0.0;
112 lateral_velocity_ = 0.0;
113 angular_velocity_ = 0.0;
114 break;
115 case 'w':
116 forward_velocity_ = std::min(forward_velocity_ + step_, 1.0f);
117 break;
118 case 's':
119 forward_velocity_ = std::max(forward_velocity_ - step_, -1.0f);
120 break;
121 case 'a':
122 lateral_velocity_ = std::min(lateral_velocity_ + step_, 1.0f);
123 break;
124 case 'd':
125 lateral_velocity_ = std::max(lateral_velocity_ - step_, -1.0f);
126 break;
127 case 'q':
128 angular_velocity_ = std::min(angular_velocity_ + angular_step_, 1.0f);
129 break;
130 case 'e':
131 angular_velocity_ = std::max(angular_velocity_ - angular_step_, -1.0f);
132 break;
133 case 27: // ESC Key
134 RCLCPP_INFO(this->get_logger(), "Exiting control");
135 rclcpp::shutdown();
136 return;
137 }
138
139 auto msg = std::make_unique<McLocomotionVelocity>();
140 msg->header = aimdk_msgs::msg::MessageHeader();
141 msg->header.stamp = this->now();
142 msg->source = "node";
143 msg->forward_velocity = forward_velocity_;
144 msg->lateral_velocity = lateral_velocity_;
145 msg->angular_velocity = angular_velocity_;
146
147 float fwd = forward_velocity_;
148 float lat = lateral_velocity_;
149 float ang = angular_velocity_;
150
151 pub_->publish(std::move(msg));
152
153 // Screen Output
154 clear();
155 mvprintw(0, 0,
156 "W/S: Forward/Backward | A/D: Left/Right Strafe | Q/E: Turn "
157 "Left/Right | Space: Stop | ESC: Exit");
158 mvprintw(2, 0,
159 "Speed Status: Forward: %.2f m/s | Lateral: %.2f m/s | Angular: "
160 "%.2f rad/s",
161 fwd, lat, ang);
162 refresh();
163 }
164};
165
166int main(int argc, char *argv[]) {
167 rclcpp::init(argc, argv);
168 try {
169 auto node = std::make_shared<KeyboardVelocityController>();
170 rclcpp::spin(node);
171 } catch (const std::exception &e) {
172 RCLCPP_FATAL(rclcpp::get_logger("main"),
173 "Program exited with exception: %s", e.what());
174 }
175 rclcpp::shutdown();
176 return 0;
177}
6.2.11 拍照
该示例中用到了take_photo,在运行节点程序前,修改需要拍照的相机话题,启动节点程序后,会创建/images/目录,将当前帧图像保存在这个目录下。
1#include <chrono>
2#include <cv_bridge/cv_bridge.h>
3#include <filesystem>
4#include <opencv2/opencv.hpp>
5#include <rclcpp/rclcpp.hpp>
6#include <sensor_msgs/msg/image.hpp>
7#include <string>
8
9class SaveOneRaw : public rclcpp::Node {
10public:
11 SaveOneRaw() : Node("save_one_image"), saved_(false) {
12 topic_ = declare_parameter<std::string>(
13 "image_topic", "/aima/hal/sensor/stereo_head_front_left/rgb_image");
14
15 std::filesystem::create_directories("images");
16
17 auto qos = rclcpp::SensorDataQoS(); // BestEffort/Volatile
18 sub_ = create_subscription<sensor_msgs::msg::Image>(
19 topic_, qos, std::bind(&SaveOneRaw::cb, this, std::placeholders::_1));
20
21 RCLCPP_INFO(get_logger(), "Subscribing (raw): %s", topic_.c_str());
22 }
23
24private:
25 void cb(const sensor_msgs::msg::Image::SharedPtr msg) {
26 if (saved_)
27 return;
28
29 try {
30 // Obtain the Mat without copying by not specifying encoding
31 cv_bridge::CvImageConstPtr cvp = cv_bridge::toCvShare(msg);
32 cv::Mat img = cvp->image;
33
34 // Convert to BGR for uniform saving
35 if (msg->encoding == "rgb8") {
36 cv::cvtColor(img, img, cv::COLOR_RGB2BGR);
37 } else if (msg->encoding == "mono8") {
38 cv::cvtColor(img, img, cv::COLOR_GRAY2BGR);
39 } // bgr8 直接用;其它编码可加更多分支
40
41 auto now = std::chrono::system_clock::now();
42 auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
43 now.time_since_epoch())
44 .count();
45 std::string path = "images/frame_" + std::to_string(ms) + ".png";
46
47 if (cv::imwrite(path, img)) {
48 RCLCPP_INFO(get_logger(), "Saved: %s (%dx%d)", path.c_str(), img.cols,
49 img.rows);
50 saved_ = true;
51 rclcpp::shutdown();
52 } else {
53 RCLCPP_ERROR(get_logger(), "cv::imwrite failed: %s", path.c_str());
54 }
55 } catch (const std::exception &e) {
56 RCLCPP_ERROR(get_logger(), "raw decode failed: %s", e.what());
57 // Do not set the saved flag; wait for the next frame
58 }
59 }
60
61 std::string topic_;
62 bool saved_;
63 rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr sub_;
64};
65
66int main(int argc, char **argv) {
67 rclcpp::init(argc, argv);
68 rclcpp::spin(std::make_shared<SaveOneRaw>());
69 return 0;
70}
6.2.12 相机推流示例集
该示例集提供了多种相机数据订阅和处理功能,支持深度相机、双目相机和单目相机的数据流订阅。
这些相机数据订阅 example 并没有实际的业务用途, 仅提供相机数据基础信息的打印;若您比较熟悉 ros2 使用,会发现 ros2 topic echo + ros2 topic hz 也能够实现 example 提供的功能。
您可以选择快速查阅 SDK 接口手册中话题列表直接快速进入自己模块的开发,也可以使用相机 example 作为脚手架加入自己的业务逻辑。
我们发布的传感器数据均为未经预处理(去畸变等)的原始数据,如果您需要查询传感器的详细信息(如分辨率、焦距等),请关注相应的内参(camera_info)话题。
深度相机数据订阅
该示例中用到了echo_camera_rgbd,通过订阅/aima/hal/sensor/rgbd_head_front/话题来接收机器人的深度相机数据,支持深度点云、深度图、RGB图、压缩RGB图和相机内参等多种数据类型。
功能特点:
支持多种数据类型订阅(深度点云、深度图、RGB图、压缩图、相机内参)
实时FPS统计和数据显示
支持RGB图视频录制功能
可配置的topic类型选择
支持的数据类型:
depth_pointcloud: 深度点云数据 (sensor_msgs/PointCloud2)depth_image: 深度图像 (sensor_msgs/Image)rgb_image: RGB图像 (sensor_msgs/Image)rgb_image_compressed: 压缩RGB图像 (sensor_msgs/CompressedImage)camera_info: 相机内参 (sensor_msgs/CameraInfo)
1#include <deque>
2#include <iomanip>
3#include <memory>
4#include <rclcpp/rclcpp.hpp>
5#include <sensor_msgs/msg/camera_info.hpp>
6#include <sensor_msgs/msg/compressed_image.hpp>
7#include <sensor_msgs/msg/image.hpp>
8#include <sensor_msgs/msg/point_cloud2.hpp>
9#include <sstream>
10#include <string>
11#include <vector>
12
13// OpenCV headers for image/video writing
14#include <cv_bridge/cv_bridge.h>
15#include <opencv2/opencv.hpp>
16
17/**
18 * @brief Example of subscribing to multiple topics for the head depth camera
19 *
20 * You can select which topic type to subscribe to via the startup argument
21 * --ros-args -p topic_type:=<type>:
22 * - depth_pointcloud: Depth point cloud (sensor_msgs/PointCloud2)
23 * - depth_image: Depth image (sensor_msgs/Image)
24 * - rgb_image: RGB image (sensor_msgs/Image)
25 * - rgb_image_compressed: RGB compressed image (sensor_msgs/CompressedImage)
26 * - rgb_camera_info: Camera intrinsic parameters (sensor_msgs/CameraInfo)
27 * - depth_camera_info: Camera intrinsic parameters (sensor_msgs/CameraInfo)
28 *
29 * Examples:
30 * ros2 run examples echo_camera_rgbd --ros-args -p
31 * topic_type:=depth_pointcloud ros2 run examples echo_camera_rgbd --ros-args -p
32 * topic_type:=rgb_image ros2 run examples echo_camera_rgbd --ros-args -p
33 * topic_type:=rgb_camera_info
34 *
35 * topic_type defaults to "rgb_image"
36 *
37 * See individual callbacks for more detailed comments
38 */
39class CameraTopicEcho : public rclcpp::Node {
40public:
41 CameraTopicEcho() : Node("camera_topic_echo") {
42 // Select which topic type to subscribe to
43 topic_type_ = declare_parameter<std::string>("topic_type", "rgb_image");
44 dump_video_path_ = declare_parameter<std::string>("dump_video_path", "");
45
46 // Subscribed topics and their message layouts
47 // 1. /aima/hal/sensor/rgbd_head_front/depth_pointcloud
48 // - topic_type: depth_pointcloud
49 // - message type: sensor_msgs::msg::PointCloud2
50 // - frame_id: rgbd_head_front
51 // - child_frame_id: /
52 // - contents: depth point cloud
53 // 2. /aima/hal/sensor/rgbd_head_front/depth_image
54 // - topic_type: depth_image
55 // - message type: sensor_msgs::msg::Image
56 // - frame_id: rgbd_head_front
57 // - contents: depth image
58 // 3. /aima/hal/sensor/rgbd_head_front/rgb_image
59 // - topic_type: rgb_image
60 // - message type: sensor_msgs::msg::Image
61 // - frame_id: rgbd_head_front
62 // - contents: RGB image
63 // 4. /aima/hal/sensor/rgbd_head_front/rgb_image/compressed
64 // - topic_type: rgb_image_compressed
65 // - message type: sensor_msgs::msg::CompressedImage
66 // - frame_id: rgbd_head_front
67 // - contents: RGB compressed image
68 // 5. /aima/hal/sensor/rgbd_head_front/rgb_camera_info
69 // - topic_type: camera_info
70 // - message type: sensor_msgs::msg::CameraInfo
71 // - frame_id: rgbd_head_front
72 // - contents: RGB camera intrinsic parameters
73 // 6. /aima/hal/sensor/rgbd_head_front/depth_camera_info
74 // - topic_type: camera_info
75 // - message type: sensor_msgs::msg::CameraInfo
76 // - frame_id: rgbd_head_front
77 // - contents: RGB camera intrinsic parameters
78
79 auto qos = rclcpp::SensorDataQoS();
80
81 // Enable depth pointcloud subscription
82 if (topic_type_ == "depth_pointcloud") {
83 topic_name_ = "/aima/hal/sensor/rgbd_head_front/depth_pointcloud";
84 sub_pointcloud_ = create_subscription<sensor_msgs::msg::PointCloud2>(
85 topic_name_, qos,
86 std::bind(&CameraTopicEcho::cb_pointcloud, this,
87 std::placeholders::_1));
88 RCLCPP_INFO(get_logger(), "✅ Subscribing PointCloud2: %s",
89 topic_name_.c_str());
90
91 // Enable depth image subscription
92 } else if (topic_type_ == "depth_image") {
93 topic_name_ = "/aima/hal/sensor/rgbd_head_front/depth_image";
94 sub_image_ = create_subscription<sensor_msgs::msg::Image>(
95 topic_name_, qos,
96 std::bind(&CameraTopicEcho::cb_image, this, std::placeholders::_1));
97 RCLCPP_INFO(get_logger(), "✅ Subscribing Depth Image: %s",
98 topic_name_.c_str());
99
100 // Enable RGB image subscription
101 } else if (topic_type_ == "rgb_image") {
102 topic_name_ = "/aima/hal/sensor/rgbd_head_front/rgb_image";
103 sub_image_ = create_subscription<sensor_msgs::msg::Image>(
104 topic_name_, qos,
105 std::bind(&CameraTopicEcho::cb_image, this, std::placeholders::_1));
106 RCLCPP_INFO(get_logger(), "✅ Subscribing RGB Image: %s",
107 topic_name_.c_str());
108 if (!dump_video_path_.empty()) {
109 RCLCPP_INFO(get_logger(), "📝 Will dump received images to video: %s",
110 dump_video_path_.c_str());
111 }
112
113 // Enable RGB compressed image subscription
114 } else if (topic_type_ == "rgb_image_compressed") {
115 topic_name_ = "/aima/hal/sensor/rgbd_head_front/rgb_image/compressed";
116 sub_compressed_ = create_subscription<sensor_msgs::msg::CompressedImage>(
117 topic_name_, qos,
118 std::bind(&CameraTopicEcho::cb_compressed, this,
119 std::placeholders::_1));
120 RCLCPP_INFO(get_logger(), "✅ Subscribing CompressedImage: %s",
121 topic_name_.c_str());
122
123 // Enable rgb camera info subscription
124 } else if (topic_type_ == "rgb_camera_info") {
125 topic_name_ = "/aima/hal/sensor/rgbd_head_front/rgb_camera_info";
126 // RGB-D CameraInfo subscriptions is different with other cameras.
127 // The messages arrive in about 10Hz and SensorDataQoS is enough.
128 sub_camerainfo_ = create_subscription<sensor_msgs::msg::CameraInfo>(
129 topic_name_, qos,
130 std::bind(&CameraTopicEcho::cb_camerainfo, this,
131 std::placeholders::_1));
132 RCLCPP_INFO(get_logger(), "✅ Subscribing RGB CameraInfo: %s",
133 topic_name_.c_str());
134
135 // Enable depth camera info subscription
136 } else if (topic_type_ == "depth_camera_info") {
137 topic_name_ = "/aima/hal/sensor/rgbd_head_front/depth_camera_info";
138 // RGB-D CameraInfo subscriptions is different with other cameras.
139 // The messages arrive in about 10Hz and SensorDataQoS is enough.
140 sub_camerainfo_ = create_subscription<sensor_msgs::msg::CameraInfo>(
141 topic_name_, qos,
142 std::bind(&CameraTopicEcho::cb_camerainfo, this,
143 std::placeholders::_1));
144 RCLCPP_INFO(get_logger(), "✅ Subscribing Depth CameraInfo: %s",
145 topic_name_.c_str());
146
147 // Unknown topic_type error
148 } else {
149 RCLCPP_ERROR(get_logger(), "Unknown topic_type: %s", topic_type_.c_str());
150 throw std::runtime_error("Unknown topic_type");
151 }
152 }
153
154 ~CameraTopicEcho() override {
155 if (video_writer_.isOpened()) {
156 video_writer_.release();
157 RCLCPP_INFO(get_logger(), "Video file closed.");
158 }
159 }
160
161private:
162 // PointCloud2 callback
163 void cb_pointcloud(const sensor_msgs::msg::PointCloud2::SharedPtr msg) {
164 // Update arrivals (for FPS calculation)
165 update_arrivals();
166
167 // Print point cloud basic info
168 if (should_print()) {
169 std::ostringstream oss;
170 oss << "🌫️ PointCloud2 received\n"
171 << " • frame_id: " << msg->header.frame_id << "\n"
172 << " • stamp (sec): "
173 << rclcpp::Time(msg->header.stamp).seconds() << "\n"
174 << " • width x height: " << msg->width << " x " << msg->height
175 << "\n"
176 << " • point_step: " << msg->point_step << "\n"
177 << " • row_step: " << msg->row_step << "\n"
178 << " • fields: ";
179 for (const auto &f : msg->fields)
180 oss << f.name << "(" << (int)f.datatype << ") ";
181 oss << "\n • is_bigendian: " << msg->is_bigendian
182 << "\n • is_dense: " << msg->is_dense
183 << "\n • data size: " << msg->data.size()
184 << "\n • recv FPS (1s): " << get_fps();
185 RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
186 }
187 }
188
189 // Image callback (depth/RGB image)
190 void cb_image(const sensor_msgs::msg::Image::SharedPtr msg) {
191 update_arrivals();
192
193 if (should_print()) {
194 RCLCPP_INFO(get_logger(),
195 "📸 %s received\n"
196 " • frame_id: %s\n"
197 " • stamp (sec): %.6f\n"
198 " • encoding: %s\n"
199 " • size (WxH): %u x %u\n"
200 " • step (bytes/row):%u\n"
201 " • is_bigendian: %u\n"
202 " • recv FPS (1s): %.1f",
203 topic_type_.c_str(), msg->header.frame_id.c_str(),
204 rclcpp::Time(msg->header.stamp).seconds(),
205 msg->encoding.c_str(), msg->width, msg->height, msg->step,
206 msg->is_bigendian, get_fps());
207 }
208
209 // Video dump is supported only for RGB images
210 if (topic_type_ == "rgb_image" && !dump_video_path_.empty()) {
211 dump_image_to_video(msg);
212 }
213 }
214
215 // CompressedImage callback
216 void cb_compressed(const sensor_msgs::msg::CompressedImage::SharedPtr msg) {
217 update_arrivals();
218
219 if (should_print()) {
220 RCLCPP_INFO(get_logger(),
221 "🗜️ CompressedImage received\n"
222 " • frame_id: %s\n"
223 " • stamp (sec): %.6f\n"
224 " • format: %s\n"
225 " • data size: %zu\n"
226 " • recv FPS (1s): %.1f",
227 msg->header.frame_id.c_str(),
228 rclcpp::Time(msg->header.stamp).seconds(),
229 msg->format.c_str(), msg->data.size(), get_fps());
230 }
231 }
232
233 // CameraInfo callback (camera intrinsic parameters)
234 void cb_camerainfo(const sensor_msgs::msg::CameraInfo::SharedPtr msg) {
235 // CameraInfo is typically published once; print it once
236 std::ostringstream oss;
237 oss << "📷 " << topic_type_ << " received\n"
238 << " • frame_id: " << msg->header.frame_id << "\n"
239 << " • stamp (sec): " << rclcpp::Time(msg->header.stamp).seconds()
240 << "\n"
241 << " • width x height: " << msg->width << " x " << msg->height << "\n"
242 << " • distortion_model:" << msg->distortion_model << "\n"
243 << " • D: [";
244 for (size_t i = 0; i < msg->d.size(); ++i) {
245 oss << msg->d[i];
246 if (i + 1 < msg->d.size())
247 oss << ", ";
248 }
249 oss << "]\n • K: [";
250 for (int i = 0; i < 9; ++i) {
251 oss << msg->k[i];
252 if (i + 1 < 9)
253 oss << ", ";
254 }
255 oss << "]\n • R: [";
256 for (int i = 0; i < 9; ++i) {
257 oss << msg->r[i];
258 if (i + 1 < 9)
259 oss << ", ";
260 }
261 oss << "]\n • P: [";
262 for (int i = 0; i < 12; ++i) {
263 oss << msg->p[i];
264 if (i + 1 < 12)
265 oss << ", ";
266 }
267 oss << "]\n"
268 << " • binning_x: " << msg->binning_x << "\n"
269 << " • binning_y: " << msg->binning_y << "\n"
270 << " • roi: { x_offset: " << msg->roi.x_offset
271 << ", y_offset: " << msg->roi.y_offset
272 << ", height: " << msg->roi.height << ", width: " << msg->roi.width
273 << ", do_rectify: " << (msg->roi.do_rectify ? "true" : "false") << " }";
274 RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
275 }
276
277 // Track arrival timestamps to compute FPS
278 void update_arrivals() {
279 const rclcpp::Time now = this->get_clock()->now();
280 arrivals_.push_back(now);
281 while (!arrivals_.empty() && (now - arrivals_.front()).seconds() > 1.0) {
282 arrivals_.pop_front();
283 }
284 }
285 double get_fps() const { return static_cast<double>(arrivals_.size()); }
286
287 // Control printing frequency
288 bool should_print() {
289 const rclcpp::Time now = this->get_clock()->now();
290 if ((now - last_print_).seconds() >= 1.0) {
291 last_print_ = now;
292 return true;
293 }
294 return false;
295 }
296
297 // Dump received images to a video file (RGB images only)
298 void dump_image_to_video(const sensor_msgs::msg::Image::SharedPtr &msg) {
299 cv::Mat image;
300 try {
301 // Obtain the Mat without copying by not specifying encoding
302 cv_bridge::CvImageConstPtr cvp = cv_bridge::toCvShare(msg);
303 image = cvp->image;
304 // Convert to BGR for uniform saving
305 if (msg->encoding == "rgb8") {
306 cv::cvtColor(image, image, cv::COLOR_RGB2BGR);
307 } else {
308 RCLCPP_WARN(get_logger(), "image encoding not expected: %s",
309 msg->encoding.c_str());
310 return;
311 }
312 } catch (const std::exception &e) {
313 RCLCPP_WARN(get_logger(), "cv_bridge exception: %s", e.what());
314 return;
315 }
316
317 // Initialize VideoWriter
318 if (!video_writer_.isOpened()) {
319 int fourcc = cv::VideoWriter::fourcc('M', 'J', 'P', 'G');
320 double fps = std::max(1.0, get_fps());
321 bool ok = video_writer_.open(dump_video_path_, fourcc, fps,
322 cv::Size(image.cols, image.rows), true);
323 if (!ok) {
324 RCLCPP_ERROR(get_logger(), "Failed to open video file: %s",
325 dump_video_path_.c_str());
326 dump_video_path_.clear(); // stop trying
327 return;
328 }
329 RCLCPP_INFO(get_logger(), "VideoWriter started: %s, size=%dx%d, fps=%.1f",
330 dump_video_path_.c_str(), image.cols, image.rows, fps);
331 }
332 video_writer_.write(image);
333 }
334
335 // Member variables
336 std::string topic_type_;
337 std::string topic_name_;
338 std::string dump_video_path_;
339
340 // Subscriptions
341 rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr sub_image_;
342 rclcpp::Subscription<sensor_msgs::msg::CompressedImage>::SharedPtr
343 sub_compressed_;
344 rclcpp::Subscription<sensor_msgs::msg::CameraInfo>::SharedPtr sub_camerainfo_;
345 rclcpp::Subscription<sensor_msgs::msg::PointCloud2>::SharedPtr
346 sub_pointcloud_;
347
348 // FPS statistics
349 rclcpp::Time last_print_{0, 0, RCL_ROS_TIME};
350 std::deque<rclcpp::Time> arrivals_;
351
352 // Video writer
353 cv::VideoWriter video_writer_;
354};
355
356int main(int argc, char **argv) {
357 rclcpp::init(argc, argv);
358 auto node = std::make_shared<CameraTopicEcho>();
359 rclcpp::spin(node);
360 rclcpp::shutdown();
361 return 0;
362}
使用说明:
订阅深度点云数据:
ros2 run examples echo_camera_rgbd --ros-args -p topic_type:=depth_pointcloud
订阅RGB图像数据:
ros2 run examples echo_camera_rgbd --ros-args -p topic_type:=rgb_image
订阅相机内参:
ros2 run examples echo_camera_rgbd --ros-args -p topic_type:=rgb_camera_info ros2 run examples echo_camera_rgbd --ros-args -p topic_type:=depth_camera_info
录制RGB视频:
# dump_video_path的值可改为其他路径, 注意提前创建该文件所在目录才能保存 ros2 run examples echo_camera_rgbd --ros-args -p topic_type:=rgb_image -p dump_video_path:=$PWD/output.avi
双目相机数据订阅
该示例中用到了echo_camera_stereo,通过订阅/aima/hal/sensor/stereo_head_front_*/话题来接收机器人的双目相机数据,支持左右相机的RGB图、压缩图和相机内参数据。
功能特点:
支持左右相机独立数据订阅
实时FPS统计和数据显示
支持RGB图视频录制功能
可配置的相机选择(左/右)
支持的数据类型:
left_rgb_image: 左相机RGB图像 (sensor_msgs/Image)left_rgb_image_compressed: 左相机压缩RGB图像 (sensor_msgs/CompressedImage)left_camera_info: 左相机内参 (sensor_msgs/CameraInfo)right_rgb_image: 右相机RGB图像 (sensor_msgs/Image)right_rgb_image_compressed: 右相机压缩RGB图像 (sensor_msgs/CompressedImage)right_camera_info: 右相机内参 (sensor_msgs/CameraInfo)
1#include <deque>
2#include <iomanip>
3#include <memory>
4#include <rclcpp/rclcpp.hpp>
5#include <sensor_msgs/msg/camera_info.hpp>
6#include <sensor_msgs/msg/compressed_image.hpp>
7#include <sensor_msgs/msg/image.hpp>
8#include <sstream>
9#include <string>
10#include <vector>
11
12// OpenCV headers for image/video writing
13#include <cv_bridge/cv_bridge.h>
14#include <opencv2/opencv.hpp>
15
16/**
17 * @brief Example of subscribing to multiple topics for the stereo head camera
18 *
19 * You can select which topic type to subscribe to via the startup argument
20 * --ros-args -p topic_type:=<type>:
21 * - left_rgb_image: left camera RGB image (sensor_msgs/Image)
22 * - left_rgb_image_compressed: left camera RGB compressed image
23 * (sensor_msgs/CompressedImage)
24 * - left_camera_info: left camera intrinsic parameters
25 * (sensor_msgs/CameraInfo)
26 * - right_rgb_image: right camera RGB image (sensor_msgs/Image)
27 * - right_rgb_image_compressed: right camera RGB compressed image
28 * (sensor_msgs/CompressedImage)
29 * - right_camera_info: right camera intrinsic parameters
30 * (sensor_msgs/CameraInfo)
31 *
32 * Examples:
33 * ros2 run examples echo_camera_stereo --ros-args -p
34 * topic_type:=left_rgb_image ros2 run examples echo_camera_stereo --ros-args -p
35 * topic_type:=right_rgb_image ros2 run examples echo_camera_stereo --ros-args
36 * -p topic_type:=left_camera_info
37 *
38 * topic_type defaults to "left_rgb_image"
39 *
40 * See individual callbacks for more detailed comments
41 */
42class StereoCameraTopicEcho : public rclcpp::Node {
43public:
44 StereoCameraTopicEcho() : Node("stereo_camera_topic_echo") {
45 // Select which topic type to subscribe to
46 topic_type_ =
47 declare_parameter<std::string>("topic_type", "left_rgb_image");
48 dump_video_path_ = declare_parameter<std::string>("dump_video_path", "");
49
50 // Subscribed topics and their message layouts
51 // 1. /aima/hal/sensor/stereo_head_front_left/rgb_image
52 // - topic_type: left_rgb_image
53 // - message type: sensor_msgs::msg::Image
54 // - frame_id: stereo_head_front
55 // - child_frame_id: /
56 // - contents: left camera raw image
57 // 2. /aima/hal/sensor/stereo_head_front_left/rgb_image/compressed
58 // - topic_type: left_rgb_image_compressed
59 // - message type: sensor_msgs::msg::CompressedImage
60 // - frame_id: stereo_head_front
61 // - contents: left camera compressed image
62 // 3. /aima/hal/sensor/stereo_head_front_left/camera_info
63 // - topic_type: left_camera_info
64 // - message type: sensor_msgs::msg::CameraInfo
65 // - frame_id: stereo_head_front
66 // - contents: left camera intrinsic parameters
67 // 4. /aima/hal/sensor/stereo_head_front_right/rgb_image
68 // - topic_type: right_rgb_image
69 // - message type: sensor_msgs::msg::Image
70 // - frame_id: stereo_head_front_right
71 // - child_frame_id: /
72 // - contents: right camera raw image
73 // 5. /aima/hal/sensor/stereo_head_front_right/rgb_image/compressed
74 // - topic_type: right_rgb_image_compressed
75 // - message type: sensor_msgs::msg::CompressedImage
76 // - frame_id: stereo_head_front_right
77 // - contents: right camera compressed image
78 // 6. /aima/hal/sensor/stereo_head_front_right/camera_info
79 // - topic_type: right_camera_info
80 // - message type: sensor_msgs::msg::CameraInfo
81 // - frame_id: stereo_head_front_right
82 // - contents: right camera intrinsic parameters
83
84 // Set QoS parameters - use SensorData QoS
85 auto qos = rclcpp::SensorDataQoS();
86
87 // Enable left camera RGB image subscription
88 if (topic_type_ == "left_rgb_image") {
89 topic_name_ = "/aima/hal/sensor/stereo_head_front_left/rgb_image";
90 sub_image_ = create_subscription<sensor_msgs::msg::Image>(
91 topic_name_, qos,
92 std::bind(&StereoCameraTopicEcho::cb_image, this,
93 std::placeholders::_1));
94 RCLCPP_INFO(get_logger(), "✅ Subscribing Left RGB Image: %s",
95 topic_name_.c_str());
96 if (!dump_video_path_.empty()) {
97 RCLCPP_INFO(get_logger(), "📝 Will dump received images to video: %s",
98 dump_video_path_.c_str());
99 }
100
101 // Enable left camera RGB compressed image subscription
102 } else if (topic_type_ == "left_rgb_image_compressed") {
103 topic_name_ =
104 "/aima/hal/sensor/stereo_head_front_left/rgb_image/compressed";
105 sub_compressed_ = create_subscription<sensor_msgs::msg::CompressedImage>(
106 topic_name_, qos,
107 std::bind(&StereoCameraTopicEcho::cb_compressed, this,
108 std::placeholders::_1));
109 RCLCPP_INFO(get_logger(), "✅ Subscribing Left CompressedImage: %s",
110 topic_name_.c_str());
111
112 // Enable left camera info subscription
113 } else if (topic_type_ == "left_camera_info") {
114
115 topic_name_ = "/aima/hal/sensor/stereo_head_front_left/camera_info";
116 // CameraInfo subscriptions must use reliable + transient_local
117 // QoS in order to receive latched/history messages (even if only one
118 // message was published). Here we use keep_last(1) + reliable
119 // + transient_local.
120 sub_camerainfo_ = create_subscription<sensor_msgs::msg::CameraInfo>(
121 topic_name_,
122 rclcpp::QoS(rclcpp::KeepLast(1)).reliable().transient_local(),
123 std::bind(&StereoCameraTopicEcho::cb_camerainfo, this,
124 std::placeholders::_1));
125 RCLCPP_INFO(get_logger(),
126 "✅ Subscribing Left CameraInfo (with transient_local): %s",
127 topic_name_.c_str());
128
129 // Enable right camera RGB image subscription
130 } else if (topic_type_ == "right_rgb_image") {
131 topic_name_ = "/aima/hal/sensor/stereo_head_front_right/rgb_image";
132 sub_image_ = create_subscription<sensor_msgs::msg::Image>(
133 topic_name_, qos,
134 std::bind(&StereoCameraTopicEcho::cb_image, this,
135 std::placeholders::_1));
136 RCLCPP_INFO(get_logger(), "✅ Subscribing Right RGB Image: %s",
137 topic_name_.c_str());
138 if (!dump_video_path_.empty()) {
139 RCLCPP_INFO(get_logger(), "📝 Will dump received images to video: %s",
140 dump_video_path_.c_str());
141 }
142
143 // Enable right camera RGB compressed image subscription
144 } else if (topic_type_ == "right_rgb_image_compressed") {
145 topic_name_ =
146 "/aima/hal/sensor/stereo_head_front_right/rgb_image/compressed";
147 sub_compressed_ = create_subscription<sensor_msgs::msg::CompressedImage>(
148 topic_name_, qos,
149 std::bind(&StereoCameraTopicEcho::cb_compressed, this,
150 std::placeholders::_1));
151 RCLCPP_INFO(get_logger(), "✅ Subscribing Right CompressedImage: %s",
152 topic_name_.c_str());
153
154 // Enable right camera info subscription
155 } else if (topic_type_ == "right_camera_info") {
156 topic_name_ = "/aima/hal/sensor/stereo_head_front_right/camera_info";
157 // CameraInfo subscriptions must use reliable + transient_local
158 // QoS in order to receive latched/history messages (even if only one
159 // message was published). Here we use keep_last(1) + reliable
160 // + transient_local.
161 sub_camerainfo_ = create_subscription<sensor_msgs::msg::CameraInfo>(
162 topic_name_,
163 rclcpp::QoS(rclcpp::KeepLast(1)).reliable().transient_local(),
164 std::bind(&StereoCameraTopicEcho::cb_camerainfo, this,
165 std::placeholders::_1));
166 RCLCPP_INFO(get_logger(),
167 "✅ Subscribing Right CameraInfo (with transient_local): %s",
168 topic_name_.c_str());
169
170 // Unknown topic_type error
171 } else {
172 RCLCPP_ERROR(get_logger(), "Unknown topic_type: %s", topic_type_.c_str());
173 throw std::runtime_error("Unknown topic_type");
174 }
175 }
176
177 ~StereoCameraTopicEcho() override {
178 if (video_writer_.isOpened()) {
179 video_writer_.release();
180 RCLCPP_INFO(get_logger(), "Video file closed.");
181 }
182 }
183
184private:
185 // Image callback (left/right RGB image)
186 void cb_image(const sensor_msgs::msg::Image::SharedPtr msg) {
187 update_arrivals();
188
189 if (should_print()) {
190 RCLCPP_INFO(get_logger(),
191 "📸 %s received\n"
192 " • frame_id: %s\n"
193 " • stamp (sec): %.6f\n"
194 " • encoding: %s\n"
195 " • size (WxH): %u x %u\n"
196 " • step (bytes/row):%u\n"
197 " • is_bigendian: %u\n"
198 " • recv FPS (1s): %.1f",
199 topic_type_.c_str(), msg->header.frame_id.c_str(),
200 rclcpp::Time(msg->header.stamp).seconds(),
201 msg->encoding.c_str(), msg->width, msg->height, msg->step,
202 msg->is_bigendian, get_fps());
203 }
204
205 // Video dump is supported only for RGB images
206 if ((topic_type_ == "left_rgb_image" || topic_type_ == "right_rgb_image") &&
207 !dump_video_path_.empty()) {
208 dump_image_to_video(msg);
209 }
210 }
211
212 // CompressedImage callback (left/right RGB compressed image)
213 void cb_compressed(const sensor_msgs::msg::CompressedImage::SharedPtr msg) {
214 update_arrivals();
215
216 if (should_print()) {
217 RCLCPP_INFO(get_logger(),
218 "🗜️ %s received\n"
219 " • frame_id: %s\n"
220 " • stamp (sec): %.6f\n"
221 " • format: %s\n"
222 " • data size: %zu\n"
223 " • recv FPS (1s): %.1f",
224 topic_type_.c_str(), msg->header.frame_id.c_str(),
225 rclcpp::Time(msg->header.stamp).seconds(),
226 msg->format.c_str(), msg->data.size(), get_fps());
227 }
228 }
229
230 // CameraInfo callback (left/right camera intrinsic parameters)
231 void cb_camerainfo(const sensor_msgs::msg::CameraInfo::SharedPtr msg) {
232 // CameraInfo is typically published once; print it once
233 std::ostringstream oss;
234 oss << "📷 " << topic_type_ << " received\n"
235 << " • frame_id: " << msg->header.frame_id << "\n"
236 << " • stamp (sec): " << rclcpp::Time(msg->header.stamp).seconds()
237 << "\n"
238 << " • width x height: " << msg->width << " x " << msg->height << "\n"
239 << " • distortion_model:" << msg->distortion_model << "\n"
240 << " • D: [";
241 for (size_t i = 0; i < msg->d.size(); ++i) {
242 oss << msg->d[i];
243 if (i + 1 < msg->d.size())
244 oss << ", ";
245 }
246 oss << "]\n • K: [";
247 for (int i = 0; i < 9; ++i) {
248 oss << msg->k[i];
249 if (i + 1 < 9)
250 oss << ", ";
251 }
252 oss << "]\n • R: [";
253 for (int i = 0; i < 9; ++i) {
254 oss << msg->r[i];
255 if (i + 1 < 9)
256 oss << ", ";
257 }
258 oss << "]\n • P: [";
259 for (int i = 0; i < 12; ++i) {
260 oss << msg->p[i];
261 if (i + 1 < 12)
262 oss << ", ";
263 }
264 oss << "]\n"
265 << " • binning_x: " << msg->binning_x << "\n"
266 << " • binning_y: " << msg->binning_y << "\n"
267 << " • roi: { x_offset: " << msg->roi.x_offset
268 << ", y_offset: " << msg->roi.y_offset
269 << ", height: " << msg->roi.height << ", width: " << msg->roi.width
270 << ", do_rectify: " << (msg->roi.do_rectify ? "true" : "false") << " }";
271 RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
272 }
273
274 // Track arrival timestamps to compute FPS
275 void update_arrivals() {
276 const rclcpp::Time now = this->get_clock()->now();
277 arrivals_.push_back(now);
278 while (!arrivals_.empty() && (now - arrivals_.front()).seconds() > 1.0) {
279 arrivals_.pop_front();
280 }
281 }
282 double get_fps() const { return static_cast<double>(arrivals_.size()); }
283
284 // Control printing frequency
285 bool should_print() {
286 const rclcpp::Time now = this->get_clock()->now();
287 if ((now - last_print_).seconds() >= 1.0) {
288 last_print_ = now;
289 return true;
290 }
291 return false;
292 }
293
294 // Dump received images to a video file (RGB images only)
295 void dump_image_to_video(const sensor_msgs::msg::Image::SharedPtr &msg) {
296 cv::Mat image;
297 try {
298 // Obtain the Mat without copying by not specifying encoding
299 cv_bridge::CvImageConstPtr cvp = cv_bridge::toCvShare(msg);
300 image = cvp->image;
301 // Convert to BGR for uniform saving
302 if (msg->encoding == "rgb8") {
303 cv::cvtColor(image, image, cv::COLOR_RGB2BGR);
304 } else {
305 RCLCPP_WARN(get_logger(), "image encoding not expected: %s",
306 msg->encoding.c_str());
307 return;
308 }
309 } catch (const std::exception &e) {
310 RCLCPP_WARN(get_logger(), "cv_bridge exception: %s", e.what());
311 return;
312 }
313
314 // Initialize VideoWriter
315 if (!video_writer_.isOpened()) {
316 int fourcc = cv::VideoWriter::fourcc('M', 'J', 'P', 'G');
317 double fps = std::max(1.0, get_fps());
318 bool ok = video_writer_.open(dump_video_path_, fourcc, fps,
319 cv::Size(image.cols, image.rows), true);
320 if (!ok) {
321 RCLCPP_ERROR(get_logger(), "Failed to open video file: %s",
322 dump_video_path_.c_str());
323 dump_video_path_.clear(); // stop trying
324 return;
325 }
326 RCLCPP_INFO(get_logger(), "VideoWriter started: %s, size=%dx%d, fps=%.1f",
327 dump_video_path_.c_str(), image.cols, image.rows, fps);
328 }
329 video_writer_.write(image);
330 }
331
332 // Member variables
333 std::string topic_type_;
334 std::string topic_name_;
335 std::string dump_video_path_;
336
337 // Subscriptions
338 rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr sub_image_;
339 rclcpp::Subscription<sensor_msgs::msg::CompressedImage>::SharedPtr
340 sub_compressed_;
341 rclcpp::Subscription<sensor_msgs::msg::CameraInfo>::SharedPtr sub_camerainfo_;
342
343 // FPS statistics
344 rclcpp::Time last_print_{0, 0, RCL_ROS_TIME};
345 std::deque<rclcpp::Time> arrivals_;
346
347 // Video writer
348 cv::VideoWriter video_writer_;
349};
350
351int main(int argc, char **argv) {
352 rclcpp::init(argc, argv);
353 auto node = std::make_shared<StereoCameraTopicEcho>();
354 rclcpp::spin(node);
355 rclcpp::shutdown();
356 return 0;
357}
使用说明:
订阅左相机RGB图像:
ros2 run examples echo_camera_stereo --ros-args -p topic_type:=left_rgb_image
订阅右相机RGB图像:
ros2 run examples echo_camera_stereo --ros-args -p topic_type:=right_rgb_image
订阅左相机内参:
ros2 run examples echo_camera_stereo --ros-args -p topic_type:=left_camera_info
录制左相机视频:
# dump_video_path的值可改为其他路径, 注意提前创建该文件所在目录才能保存 ros2 run examples echo_camera_stereo --ros-args -p topic_type:=left_rgb_image -p dump_video_path:=$PWD/left_camera.avi
头部后置单目相机数据订阅
该示例中用到了echo_camera_head_rear,通过订阅/aima/hal/sensor/rgb_head_rear/话题来接收机器人的头部后置单目相机数据,支持RGB图、压缩图和相机内参数据。
功能特点:
支持头部后置相机数据订阅
实时FPS统计和数据显示
支持RGB图视频录制功能
可配置的topic类型选择
支持的数据类型:
rgb_image: RGB图像 (sensor_msgs/Image)rgb_image_compressed: 压缩RGB图像 (sensor_msgs/CompressedImage)camera_info: 相机内参 (sensor_msgs/CameraInfo)
1#include <deque>
2#include <iomanip>
3#include <memory>
4#include <rclcpp/rclcpp.hpp>
5#include <sensor_msgs/msg/camera_info.hpp>
6#include <sensor_msgs/msg/compressed_image.hpp>
7#include <sensor_msgs/msg/image.hpp>
8#include <sstream>
9#include <string>
10#include <vector>
11
12// OpenCV headers for image/video writing
13#include <cv_bridge/cv_bridge.h>
14#include <opencv2/opencv.hpp>
15
16/**
17 * @brief Example of subscribing to multiple topics for the rear head monocular
18 * camera
19 *
20 * You can select which topic type to subscribe to via the startup argument
21 * --ros-args -p topic_type:=<type>:
22 * - rgb_image: RGB image (sensor_msgs/Image)
23 * - rgb_image_compressed: RGB compressed image (sensor_msgs/CompressedImage)
24 * - camera_info: Camera intrinsic parameters (sensor_msgs/CameraInfo)
25 *
26 * Examples:
27 * ros2 run examples echo_camera_head_rear --ros-args -p topic_type:=rgb_image
28 * ros2 run examples echo_camera_head_rear --ros-args -p
29 * topic_type:=rgb_image_compressed ros2 run examples echo_camera_head_rear
30 * --ros-args -p topic_type:=camera_info
31 *
32 * topic_type defaults to "rgb_image"
33 *
34 * See individual callbacks for more detailed comments
35 */
36class HeadRearCameraTopicEcho : public rclcpp::Node {
37public:
38 HeadRearCameraTopicEcho() : Node("head_rear_camera_topic_echo") {
39 // Select which topic type to subscribe to
40 topic_type_ = declare_parameter<std::string>("topic_type", "rgb_image");
41 dump_video_path_ = declare_parameter<std::string>("dump_video_path", "");
42
43 // Subscribed topics and their message layouts
44 // 1. /aima/hal/sensor/rgb_head_rear/rgb_image
45 // - topic_type: rgb_image
46 // - message type: sensor_msgs::msg::Image
47 // - frame_id: rgb_head_rear
48 // - child_frame_id: /
49 // - contents: raw image data
50 // 2. /aima/hal/sensor/rgb_head_rear/rgb_image/compressed
51 // - topic_type: rgb_image_compressed
52 // - message type: sensor_msgs::msg::CompressedImage
53 // - frame_id: rgb_head_rear
54 // - contents: compressed image data
55 // 3. /aima/hal/sensor/rgb_head_rear/camera_info
56 // - topic_type: camera_info
57 // - message type: sensor_msgs::msg::CameraInfo
58 // - frame_id: rgb_head_rear
59 // - contents: camera intrinsic parameters
60
61 // Set QoS parameters - use SensorData QoS
62 auto qos = rclcpp::SensorDataQoS();
63
64 // Enable RGB image subscription
65 if (topic_type_ == "rgb_image") {
66 topic_name_ = "/aima/hal/sensor/rgb_head_rear/rgb_image";
67 sub_image_ = create_subscription<sensor_msgs::msg::Image>(
68 topic_name_, qos,
69 std::bind(&HeadRearCameraTopicEcho::cb_image, this,
70 std::placeholders::_1));
71 RCLCPP_INFO(get_logger(), "✅ Subscribing RGB Image: %s",
72 topic_name_.c_str());
73 if (!dump_video_path_.empty()) {
74 RCLCPP_INFO(get_logger(), "📝 Will dump received images to video: %s",
75 dump_video_path_.c_str());
76 }
77
78 // Enable RGB compressed image subscription
79 } else if (topic_type_ == "rgb_image_compressed") {
80 topic_name_ = "/aima/hal/sensor/rgb_head_rear/rgb_image/compressed";
81 sub_compressed_ = create_subscription<sensor_msgs::msg::CompressedImage>(
82 topic_name_, qos,
83 std::bind(&HeadRearCameraTopicEcho::cb_compressed, this,
84 std::placeholders::_1));
85 RCLCPP_INFO(get_logger(), "✅ Subscribing CompressedImage: %s",
86 topic_name_.c_str());
87
88 // Enable camera info subscription
89 } else if (topic_type_ == "camera_info") {
90 topic_name_ = "/aima/hal/sensor/rgb_head_rear/camera_info";
91 // CameraInfo subscriptions must use reliable + transient_local
92 // QoS in order to receive latched/history messages (even if only one
93 // message was published). Here we use keep_last(1) + reliable
94 // + transient_local.
95 sub_camerainfo_ = create_subscription<sensor_msgs::msg::CameraInfo>(
96 topic_name_,
97 rclcpp::QoS(rclcpp::KeepLast(1)).reliable().transient_local(),
98 std::bind(&HeadRearCameraTopicEcho::cb_camerainfo, this,
99 std::placeholders::_1));
100 RCLCPP_INFO(get_logger(),
101 "✅ Subscribing CameraInfo (with transient_local): %s",
102 topic_name_.c_str());
103
104 // Unknown topic_type error
105 } else {
106 RCLCPP_ERROR(get_logger(), "Unknown topic_type: %s", topic_type_.c_str());
107 throw std::runtime_error("Unknown topic_type");
108 }
109 }
110
111 ~HeadRearCameraTopicEcho() override {
112 if (video_writer_.isOpened()) {
113 video_writer_.release();
114 RCLCPP_INFO(get_logger(), "Video file closed.");
115 }
116 }
117
118private:
119 // Image callback (RGB image)
120 void cb_image(const sensor_msgs::msg::Image::SharedPtr msg) {
121 update_arrivals();
122
123 if (should_print()) {
124 RCLCPP_INFO(get_logger(),
125 "📸 %s received\n"
126 " • frame_id: %s\n"
127 " • stamp (sec): %.6f\n"
128 " • encoding: %s\n"
129 " • size (WxH): %u x %u\n"
130 " • step (bytes/row):%u\n"
131 " • is_bigendian: %u\n"
132 " • recv FPS (1s): %.1f",
133 topic_type_.c_str(), msg->header.frame_id.c_str(),
134 rclcpp::Time(msg->header.stamp).seconds(),
135 msg->encoding.c_str(), msg->width, msg->height, msg->step,
136 msg->is_bigendian, get_fps());
137 }
138
139 // Video dump is supported only for RGB images
140 if (topic_type_ == "rgb_image" && !dump_video_path_.empty()) {
141 dump_image_to_video(msg);
142 }
143 }
144
145 // CompressedImage callback (RGB compressed image)
146 void cb_compressed(const sensor_msgs::msg::CompressedImage::SharedPtr msg) {
147 update_arrivals();
148
149 if (should_print()) {
150 RCLCPP_INFO(get_logger(),
151 "🗜️ %s received\n"
152 " • frame_id: %s\n"
153 " • stamp (sec): %.6f\n"
154 " • format: %s\n"
155 " • data size: %zu\n"
156 " • recv FPS (1s): %.1f",
157 topic_type_.c_str(), msg->header.frame_id.c_str(),
158 rclcpp::Time(msg->header.stamp).seconds(),
159 msg->format.c_str(), msg->data.size(), get_fps());
160 }
161 }
162
163 // CameraInfo callback (camera intrinsic parameters)
164 void cb_camerainfo(const sensor_msgs::msg::CameraInfo::SharedPtr msg) {
165 // CameraInfo is typically published once; print it once
166 std::ostringstream oss;
167 oss << "📷 " << topic_type_ << " received\n"
168 << " • frame_id: " << msg->header.frame_id << "\n"
169 << " • stamp (sec): " << rclcpp::Time(msg->header.stamp).seconds()
170 << "\n"
171 << " • width x height: " << msg->width << " x " << msg->height << "\n"
172 << " • distortion_model:" << msg->distortion_model << "\n"
173 << " • D: [";
174 for (size_t i = 0; i < msg->d.size(); ++i) {
175 oss << msg->d[i];
176 if (i + 1 < msg->d.size())
177 oss << ", ";
178 }
179 oss << "]\n • K: [";
180 for (int i = 0; i < 9; ++i) {
181 oss << msg->k[i];
182 if (i + 1 < 9)
183 oss << ", ";
184 }
185 oss << "]\n • R: [";
186 for (int i = 0; i < 9; ++i) {
187 oss << msg->r[i];
188 if (i + 1 < 9)
189 oss << ", ";
190 }
191 oss << "]\n • P: [";
192 for (int i = 0; i < 12; ++i) {
193 oss << msg->p[i];
194 if (i + 1 < 12)
195 oss << ", ";
196 }
197 oss << "]\n"
198 << " • binning_x: " << msg->binning_x << "\n"
199 << " • binning_y: " << msg->binning_y << "\n"
200 << " • roi: { x_offset: " << msg->roi.x_offset
201 << ", y_offset: " << msg->roi.y_offset
202 << ", height: " << msg->roi.height << ", width: " << msg->roi.width
203 << ", do_rectify: " << (msg->roi.do_rectify ? "true" : "false") << " }";
204 RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
205 }
206
207 // Track arrival timestamps to compute FPS
208 void update_arrivals() {
209 const rclcpp::Time now = this->get_clock()->now();
210 arrivals_.push_back(now);
211 while (!arrivals_.empty() && (now - arrivals_.front()).seconds() > 1.0) {
212 arrivals_.pop_front();
213 }
214 }
215 double get_fps() const { return static_cast<double>(arrivals_.size()); }
216
217 // Control printing frequency
218 bool should_print() {
219 const rclcpp::Time now = this->get_clock()->now();
220 if ((now - last_print_).seconds() >= 1.0) {
221 last_print_ = now;
222 return true;
223 }
224 return false;
225 }
226
227 // Dump received images to a video file (RGB images only)
228 void dump_image_to_video(const sensor_msgs::msg::Image::SharedPtr &msg) {
229 cv::Mat image;
230 try {
231 // Obtain the Mat without copying by not specifying encoding
232 cv_bridge::CvImageConstPtr cvp = cv_bridge::toCvShare(msg);
233 image = cvp->image;
234 // Convert to BGR for uniform saving
235 if (msg->encoding == "rgb8") {
236 cv::cvtColor(image, image, cv::COLOR_RGB2BGR);
237 } else {
238 RCLCPP_WARN(get_logger(), "image encoding not expected: %s",
239 msg->encoding.c_str());
240 return;
241 }
242 } catch (const std::exception &e) {
243 RCLCPP_WARN(get_logger(), "cv_bridge exception: %s", e.what());
244 return;
245 }
246
247 // Initialize VideoWriter
248 if (!video_writer_.isOpened()) {
249 int fourcc = cv::VideoWriter::fourcc('M', 'J', 'P', 'G');
250 double fps = std::max(1.0, get_fps());
251 bool ok = video_writer_.open(dump_video_path_, fourcc, fps,
252 cv::Size(image.cols, image.rows), true);
253 if (!ok) {
254 RCLCPP_ERROR(get_logger(), "Failed to open video file: %s",
255 dump_video_path_.c_str());
256 dump_video_path_.clear(); // stop trying
257 return;
258 }
259 RCLCPP_INFO(get_logger(), "VideoWriter started: %s, size=%dx%d, fps=%.1f",
260 dump_video_path_.c_str(), image.cols, image.rows, fps);
261 }
262 video_writer_.write(image);
263 }
264
265 // Member variables
266 std::string topic_type_;
267 std::string topic_name_;
268 std::string dump_video_path_;
269
270 // Subscriptions
271 rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr sub_image_;
272 rclcpp::Subscription<sensor_msgs::msg::CompressedImage>::SharedPtr
273 sub_compressed_;
274 rclcpp::Subscription<sensor_msgs::msg::CameraInfo>::SharedPtr sub_camerainfo_;
275
276 // FPS statistics
277 rclcpp::Time last_print_{0, 0, RCL_ROS_TIME};
278 std::deque<rclcpp::Time> arrivals_;
279
280 // Video writer
281 cv::VideoWriter video_writer_;
282};
283
284int main(int argc, char **argv) {
285 rclcpp::init(argc, argv);
286 auto node = std::make_shared<HeadRearCameraTopicEcho>();
287 rclcpp::spin(node);
288 rclcpp::shutdown();
289 return 0;
290}
使用说明:
订阅RGB图像数据:
ros2 run examples echo_camera_head_rear --ros-args -p topic_type:=rgb_image
订阅压缩图像数据:
ros2 run examples echo_camera_head_rear --ros-args -p topic_type:=rgb_image_compressed
订阅相机内参:
ros2 run examples echo_camera_head_rear --ros-args -p topic_type:=camera_info
录制视频:
# dump_video_path的值可改为其他路径, 注意提前创建该文件所在目录才能保存 ros2 run examples echo_camera_head_rear --ros-args -p topic_type:=rgb_image -p dump_video_path:=$PWD/rear_camera.avi
应用场景:
人脸识别和追踪
目标检测和识别
视觉SLAM
图像处理和计算机视觉算法开发
机器人视觉导航
6.2.13 激光雷达数据订阅
该示例中用到了echo_lidar_data,通过订阅/aima/hal/sensor/lidar_chest_front/话题来接收机器人的激光雷达数据,支持点云数据和IMU数据两种数据类型。
功能特点:
支持激光雷达点云数据订阅
支持激光雷达IMU数据订阅
实时FPS统计和数据显示
可配置的topic类型选择
详细的数据字段信息输出
支持的数据类型:
PointCloud2: 激光雷达点云数据 (sensor_msgs/PointCloud2)Imu: 激光雷达IMU数据 (sensor_msgs/Imu)
技术实现:
使用SensorDataQoS配置(
BEST_EFFORT+VOLATILE)支持点云字段信息解析和显示
支持IMU四元数、角速度和线性加速度数据
提供详细的调试日志输出
应用场景:
激光雷达数据采集和分析
点云数据处理和可视化
机器人导航和定位
SLAM算法开发
环境感知和建图
1#include <deque>
2#include <iomanip>
3#include <memory>
4#include <rclcpp/rclcpp.hpp>
5#include <sensor_msgs/msg/imu.hpp>
6#include <sensor_msgs/msg/point_cloud2.hpp>
7#include <sstream>
8#include <string>
9#include <vector>
10
11/**
12 * @brief Example for subscribing to chest LIDAR data
13 *
14 * Supports subscribing to the following topics:
15 * 1. /aima/hal/sensor/lidar_chest_front/lidar_pointcloud
16 * - Data type: sensor_msgs::msg::PointCloud2
17 * - frame_id: lidar_chest_front
18 * - child_frame_id: /
19 * - Content: LIDAR point cloud data
20 * 2. /aima/hal/sensor/lidar_chest_front/imu
21 * - Data type: sensor_msgs::msg::Imu
22 * - frame_id: lidar_imu_chest_front
23 * - Content: LIDAR IMU data
24 *
25 * You can select the topic type to subscribe to using the launch parameter
26 * --ros-args -p topic_type:=<type>:
27 * - pointcloud: Subscribe to LIDAR point cloud
28 * - imu: Subscribe to LIDAR IMU
29 * The default topic_type is pointcloud
30 */
31class LidarChestEcho : public rclcpp::Node {
32public:
33 LidarChestEcho() : Node("lidar_chest_echo") {
34 topic_type_ = declare_parameter<std::string>("topic_type", "pointcloud");
35
36 auto qos = rclcpp::SensorDataQoS();
37
38 if (topic_type_ == "pointcloud") {
39 topic_name_ = "/aima/hal/sensor/lidar_chest_front/lidar_pointcloud";
40 sub_pointcloud_ = create_subscription<sensor_msgs::msg::PointCloud2>(
41 topic_name_, qos,
42 std::bind(&LidarChestEcho::cb_pointcloud, this,
43 std::placeholders::_1));
44 RCLCPP_INFO(get_logger(), "✅ Subscribing LIDAR PointCloud2: %s",
45 topic_name_.c_str());
46 } else if (topic_type_ == "imu") {
47 topic_name_ = "/aima/hal/sensor/lidar_chest_front/imu";
48 sub_imu_ = create_subscription<sensor_msgs::msg::Imu>(
49 topic_name_, qos,
50 std::bind(&LidarChestEcho::cb_imu, this, std::placeholders::_1));
51 RCLCPP_INFO(get_logger(), "✅ Subscribing LIDAR IMU: %s",
52 topic_name_.c_str());
53 } else {
54 RCLCPP_ERROR(get_logger(), "Unknown topic_type: %s", topic_type_.c_str());
55 throw std::runtime_error("Unknown topic_type");
56 }
57 }
58
59private:
60 // PointCloud2 callback
61 void cb_pointcloud(const sensor_msgs::msg::PointCloud2::SharedPtr msg) {
62 update_arrivals();
63
64 if (should_print()) {
65 std::ostringstream oss;
66 oss << "🟢 LIDAR PointCloud2 received\n"
67 << " • frame_id: " << msg->header.frame_id << "\n"
68 << " • stamp (sec): "
69 << rclcpp::Time(msg->header.stamp).seconds() << "\n"
70 << " • width x height: " << msg->width << " x " << msg->height
71 << "\n"
72 << " • point_step: " << msg->point_step << "\n"
73 << " • row_step: " << msg->row_step << "\n"
74 << " • fields: ";
75 for (const auto &f : msg->fields)
76 oss << f.name << "(" << (int)f.datatype << ") ";
77 oss << "\n • is_bigendian: " << msg->is_bigendian
78 << "\n • is_dense: " << msg->is_dense
79 << "\n • data size: " << msg->data.size()
80 << "\n • recv FPS (1s): " << get_fps();
81 RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
82 }
83 }
84
85 // IMU callback
86 void cb_imu(const sensor_msgs::msg::Imu::SharedPtr msg) {
87 update_arrivals();
88
89 if (should_print()) {
90 std::ostringstream oss;
91 oss << "🟢 LIDAR IMU received\n"
92 << " • frame_id: " << msg->header.frame_id << "\n"
93 << " • stamp (sec): "
94 << rclcpp::Time(msg->header.stamp).seconds() << "\n"
95 << " • orientation: [" << msg->orientation.x << ", "
96 << msg->orientation.y << ", " << msg->orientation.z << ", "
97 << msg->orientation.w << "]\n"
98 << " • angular_velocity:[" << msg->angular_velocity.x << ", "
99 << msg->angular_velocity.y << ", " << msg->angular_velocity.z << "]\n"
100 << " • linear_accel: [" << msg->linear_acceleration.x << ", "
101 << msg->linear_acceleration.y << ", " << msg->linear_acceleration.z
102 << "]\n"
103 << " • recv FPS (1s): " << get_fps();
104 RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
105 }
106 }
107
108 // Update FPS statistics
109 void update_arrivals() {
110 const rclcpp::Time now = this->get_clock()->now();
111 arrivals_.push_back(now);
112 while (!arrivals_.empty() && (now - arrivals_.front()).seconds() > 1.0) {
113 arrivals_.pop_front();
114 }
115 }
116 double get_fps() const { return static_cast<double>(arrivals_.size()); }
117
118 // Control print frequency
119 bool should_print() {
120 const rclcpp::Time now = this->get_clock()->now();
121 if ((now - last_print_).seconds() >= 1.0) {
122 last_print_ = now;
123 return true;
124 }
125 return false;
126 }
127
128 // Member variables
129 std::string topic_type_;
130 std::string topic_name_;
131
132 rclcpp::Subscription<sensor_msgs::msg::PointCloud2>::SharedPtr
133 sub_pointcloud_;
134 rclcpp::Subscription<sensor_msgs::msg::Imu>::SharedPtr sub_imu_;
135
136 rclcpp::Time last_print_{0, 0, RCL_ROS_TIME};
137 std::deque<rclcpp::Time> arrivals_;
138};
139
140int main(int argc, char **argv) {
141 rclcpp::init(argc, argv);
142 auto node = std::make_shared<LidarChestEcho>();
143 rclcpp::spin(node);
144 rclcpp::shutdown();
145 return 0;
146}
使用说明:
# 订阅激光雷达点云数据
ros2 run examples echo_lidar_data --ros-args -p topic_type:=pointcloud
# 订阅激光雷达IMU数据
ros2 run examples echo_lidar_data --ros-args -p topic_type:=imu
输出示例:
[INFO] [lidar_chest_echo]: ✅ Subscribing LIDAR PointCloud2: /aima/hal/sensor/lidar_chest_front/lidar_pointcloud
[INFO] [lidar_chest_echo]: 🟢 LIDAR PointCloud2 received
• frame_id: lidar_chest_front
• stamp (sec): 1234567890.123456
• width x height: 1 x 36000
• point_step: 16
• row_step: 16
• fields: x(7) y(7) z(7) intensity(7)
• is_bigendian: False
• is_dense: True
• data size: 576000
• recv FPS (1s): 10.0
6.2.14 播放视频
该示例中用到了play_video,在运行节点程序前,需要先将视频上传到机器人的**交互计算单元(PC3)**上(用户可在其上创建一个用来存储视频的目录),然后将节点程序中的video_path改为需要播放视频的路径。
注意
⚠️ 请注意!交互计算单元(PC3)独立于二开程序所在的开发计算单元(PC2), 音视频文件务必存入交互计算单元(IP: 10.0.1.42)。
功能说明
通过调用PlayVideo服务,可以让机器人在屏幕上播放指定路径的视频文件。请确保视频文件已上传到交互计算单元,否则播放会失败。
1#include "aimdk_msgs/srv/play_video.hpp"
2#include "aimdk_msgs/msg/common_request.hpp"
3#include "rclcpp/rclcpp.hpp"
4#include <atomic>
5#include <chrono>
6#include <memory>
7#include <string>
8#include <thread>
9
10using namespace std::chrono_literals;
11
12class PlayVideoClient : public rclcpp::Node {
13public:
14 PlayVideoClient() : Node("play_video_client"), finished_(false) {
15 client_ = this->create_client<aimdk_msgs::srv::PlayVideo>(
16 "/face_ui_proxy/play_video");
17 RCLCPP_INFO(this->get_logger(), "PlayVideo client node started.");
18 }
19
20 void send_request(const std::string &video_path, uint8_t mode,
21 uint8_t priority) {
22 while (!client_->wait_for_service(1s) && rclcpp::ok()) {
23 RCLCPP_WARN(this->get_logger(), "Waiting for PlayVideo service...");
24 }
25 if (!rclcpp::ok())
26 return;
27
28 auto request = std::make_shared<aimdk_msgs::srv::PlayVideo::Request>();
29 request->header.header.stamp = this->now();
30 request->video_path = video_path;
31 request->mode = mode;
32 request->priority = priority;
33
34 using ServiceResponseFuture =
35 rclcpp::Client<aimdk_msgs::srv::PlayVideo>::SharedFuture;
36 auto response_callback = [this](ServiceResponseFuture future) {
37 auto response = future.get();
38 if (response->success) {
39 RCLCPP_INFO(this->get_logger(), "✅ Video played successfully: %s",
40 response->message.c_str());
41 } else {
42 RCLCPP_ERROR(this->get_logger(), "❌ Video play failed: %s",
43 response->message.c_str());
44 }
45 finished_ = true;
46 };
47 client_->async_send_request(request, response_callback);
48 }
49
50 bool finished() const { return finished_; }
51
52private:
53 rclcpp::Client<aimdk_msgs::srv::PlayVideo>::SharedPtr client_;
54 std::atomic_bool finished_;
55};
56
57int main(int argc, char **argv) {
58 rclcpp::init(argc, argv);
59
60 std::string video_path =
61 "/agibot/data/home/agi/zhiyuan.mp4"; // Default video path; modify as
62 // needed
63 int mode = 2; // Loop playback
64 std::cout << "Enter video play mode (1: once, 2: loop): " << std::endl;
65 std::cin >> mode;
66 uint8_t priority = 5;
67
68 auto node = std::make_shared<PlayVideoClient>();
69 node->send_request(video_path, mode, priority);
70
71 // Poll using spin_some and check for shutdown
72 while (rclcpp::ok() && !node->finished()) {
73 rclcpp::spin_some(node);
74 std::this_thread::sleep_for(50ms);
75 }
76
77 node.reset();
78 rclcpp::shutdown();
79 return 0;
80}
6.2.15 媒体文件播放
该示例中用到了play_media,通过该节点可实现播放指定的媒体文件(如音频文件),支持WAV、MP3等格式的音频文件播放。
功能特点:
支持多种音频格式播放(WAV、MP3等)
支持优先级控制,可设置播放优先级
支持打断机制,可中断当前播放
支持自定义文件路径和播放参数
提供完整的错误处理和状态反馈
技术实现:
使用PlayMediaFile服务进行媒体文件播放
支持优先级级别设置(0-99)
支持中断控制(is_interrupted参数)
提供详细的播放状态反馈
应用场景:
音频文件播放和媒体控制
语音提示和音效播放
多媒体应用开发
机器人交互音频反馈
注意
⚠️ 请注意!交互计算单元(PC3)独立于二开程序所在的开发计算单元(PC2), 音视频文件务必存入交互计算单元(IP: 10.0.1.42)。
1#include <aimdk_msgs/msg/tts_priority_level.hpp>
2#include <aimdk_msgs/srv/play_media_file.hpp>
3#include <iostream>
4#include <rclcpp/rclcpp.hpp>
5#include <string>
6
7using PlayMediaFile = aimdk_msgs::srv::PlayMediaFile;
8
9int main(int argc, char **argv) {
10 rclcpp::init(argc, argv);
11 auto node = rclcpp::Node::make_shared("play_media_file_client_min");
12
13 // 1) Service name
14 const std::string service_name = "/aimdk_5Fmsgs/srv/PlayMediaFile";
15 auto client = node->create_client<PlayMediaFile>(service_name);
16
17 // 2) Input file path (prompt user if not provided as argument)
18 std::string default_file =
19 "/agibot/data/var/interaction/tts_cache/normal/demo.wav";
20 std::string file_name;
21
22 if (argc > 1) {
23 file_name = argv[1];
24 } else {
25 std::cout << "Enter the media file path to play (default: " << default_file
26 << "): ";
27 std::getline(std::cin, file_name);
28 if (file_name.empty()) {
29 file_name = default_file;
30 }
31 }
32
33 // 3) Build the request
34 auto req = std::make_shared<PlayMediaFile::Request>();
35 // CommonRequest request -> RequestHeader header -> builtin_interfaces/Time
36 // stamp
37 req->header.header.stamp = node->now();
38
39 // PlayMediaFileRequest required fields
40 req->media_file_req.file_name = file_name;
41 req->media_file_req.domain = "demo_client"; // Required: identifies the caller
42 req->media_file_req.trace_id = "demo"; // Optional: request identifier
43 req->media_file_req.is_interrupted =
44 true; // Whether to interrupt same-priority playback
45 req->media_file_req.priority_weight = 0; // Optional: 0~99
46 // Priority level: default INTERACTION_L6
47 req->media_file_req.priority_level.value = 6;
48
49 // 4) Wait for service and call
50 RCLCPP_INFO(node->get_logger(), "Waiting for service: %s",
51 service_name.c_str());
52 if (!client->wait_for_service(std::chrono::seconds(5))) {
53 RCLCPP_ERROR(node->get_logger(), "Service unavailable: %s",
54 service_name.c_str());
55 rclcpp::shutdown();
56 return 1;
57 }
58
59 auto future = client->async_send_request(req);
60 auto rc = rclcpp::spin_until_future_complete(node, future,
61 std::chrono::seconds(10));
62
63 if (rc == rclcpp::FutureReturnCode::INTERRUPTED) {
64 RCLCPP_WARN(node->get_logger(), "Interrupted while waiting");
65 rclcpp::shutdown();
66 return 1;
67 }
68
69 if (rc != rclcpp::FutureReturnCode::SUCCESS) {
70 RCLCPP_ERROR(node->get_logger(), "Call timed out or did not complete");
71 rclcpp::shutdown();
72 return 1;
73 }
74
75 // 5) Handle response (success is in tts_resp)
76 try {
77 const auto resp = future.get();
78 bool success = resp->tts_resp.is_success;
79
80 if (success) {
81 RCLCPP_INFO(node->get_logger(), "✅ Media file play request succeeded: %s",
82 file_name.c_str());
83 } else {
84 RCLCPP_ERROR(node->get_logger(), "❌ Media file play request failed: %s",
85 file_name.c_str());
86 }
87 } catch (const std::exception &e) {
88 RCLCPP_ERROR(node->get_logger(), "Call exception: %s", e.what());
89 }
90
91 rclcpp::shutdown();
92 return 0;
93}
使用说明:
# 播放默认音频文件
ros2 run examples play_media
# 播放指定音频文件
# 注意替换/path/to/your/audio_file.wav为交互板上实际文件路径
ros2 run examples play_media /path/to/your/audio_file.wav
# 播放TTS缓存文件
ros2 run examples play_media /agibot/data/var/interaction/tts_cache/normal/demo.wav
输出示例:
[INFO] [play_media_file_client_min]: ✅ 媒体文件播放请求成功
注意事项:
确保音频文件路径正确且文件存在
支持的文件格式:WAV、MP3等
优先级设置影响播放队列顺序
打断功能可中断当前播放的音频
6.2.16 TTS (文字转语音)
该示例中用到了play_tts,通过该节点可实现语音播放输入的文字,用户可根据不同的场景输入相应的文本。
功能特点:
支持命令行参数和交互式输入
完整的服务可用性检查和错误处理
支持优先级控制和打断机制
提供详细的播放状态反馈
核心代码
1#include <aimdk_msgs/msg/tts_priority_level.hpp>
2#include <aimdk_msgs/srv/play_tts.hpp>
3#include <iostream>
4#include <rclcpp/rclcpp.hpp>
5#include <string>
6
7using PlayTTS = aimdk_msgs::srv::PlayTts;
8
9int main(int argc, char **argv) {
10 rclcpp::init(argc, argv);
11 auto node = rclcpp::Node::make_shared("play_tts_client_min");
12
13 const std::string service_name = "/aimdk_5Fmsgs/srv/PlayTts";
14 auto client = node->create_client<PlayTTS>(service_name);
15
16 // Get text to speak
17 std::string tts_text;
18 if (argc > 1) {
19 tts_text = argv[1];
20 } else {
21 std::cout << "Enter text to speak: ";
22 std::getline(std::cin, tts_text);
23 if (tts_text.empty()) {
24 tts_text = "Hello, I am Lingxi X2.";
25 }
26 }
27
28 auto req = std::make_shared<PlayTTS::Request>();
29 req->header.header.stamp = node->now();
30 req->tts_req.text = tts_text;
31 req->tts_req.domain = "demo_client"; // Required: identifies the caller
32 req->tts_req.trace_id =
33 "demo"; // Optional: request identifier for the TTS request
34 req->tts_req.is_interrupted =
35 true; // Required: whether to interrupt same-priority playback
36 req->tts_req.priority_weight = 0;
37 req->tts_req.priority_level.value = 6;
38
39 if (!client->wait_for_service(
40 std::chrono::duration_cast<std::chrono::seconds>(
41 std::chrono::seconds(5)))) {
42 RCLCPP_ERROR(node->get_logger(), "Service unavailable: %s",
43 service_name.c_str());
44 rclcpp::shutdown();
45 return 1;
46 }
47
48 auto future = client->async_send_request(req);
49 if (rclcpp::spin_until_future_complete(
50 node, future,
51 std::chrono::duration_cast<std::chrono::seconds>(
52 std::chrono::seconds(10))) != rclcpp::FutureReturnCode::SUCCESS) {
53 RCLCPP_ERROR(node->get_logger(), "Call timed out");
54 rclcpp::shutdown();
55 return 1;
56 }
57
58 const auto resp = future.get();
59 if (resp->tts_resp.is_success) {
60 RCLCPP_INFO(node->get_logger(), "✅ TTS play request succeeded");
61 } else {
62 RCLCPP_ERROR(node->get_logger(), "❌ TTS play request failed");
63 }
64
65 rclcpp::shutdown();
66 return 0;
67}
使用说明
# 使用命令行参数播报文本(推荐)
ros2 run examples play_tts "你好,我是灵犀X2机器人"
# 或者不带参数运行,程序会提示用户输入
ros2 run examples play_tts
输出示例
[INFO] [play_tts_client_min]: ✅ 播报请求成功
注意事项
确保TTS服务正常运行
支持中文和英文文本播报
优先级设置影响播放队列顺序
打断功能可中断当前播放的语音
接口参考
服务:
/aimdk_5Fmsgs/srv/PlayTts消息:
aimdk_msgs/srv/PlayTts
6.2.17 麦克风数据接收
该示例中用到了mic_receiver,通过订阅/agent/process_audio_output话题来接收机器人的降噪音频数据,支持内置麦克风和外置麦克风两种音频流,并根据VAD(语音活动检测)状态自动保存完整的语音片段为PCM文件。
功能特点:
支持多音频流同时接收(内置麦克风 stream_id=1,外置麦克风 stream_id=2)
基于VAD状态自动检测语音开始、处理中、结束
自动保存完整语音片段为PCM格式文件
按时间戳和音频流分类存储
支持音频时长计算和统计信息输出
VAD状态说明:
0: 无语音1: 语音开始2: 语音处理中3: 语音结束
1#include <aimdk_msgs/msg/audio_vad_state_type.hpp>
2#include <aimdk_msgs/msg/processed_audio_output.hpp>
3#include <chrono>
4#include <ctime>
5#include <filesystem>
6#include <fstream>
7#include <iomanip>
8#include <rclcpp/rclcpp.hpp>
9#include <sstream>
10#include <string>
11#include <unordered_map>
12#include <vector>
13
14namespace fs = std::filesystem;
15
16class AudioSubscriber : public rclcpp::Node {
17public:
18 AudioSubscriber() : rclcpp::Node("audio_subscriber") {
19 // Audio buffers, stored separately by stream_id
20 // stream_id -> buffer
21 audio_buffers_ = {};
22 recording_state_ = {};
23
24 audio_output_dir_ = "audio_recordings";
25 fs::create_directories(audio_output_dir_);
26
27 auto qos = rclcpp::QoS(
28 rclcpp::QoSInitialization::from_rmw(rmw_qos_profile_sensor_data));
29 qos.keep_last(10).best_effort();
30
31 subscription_ =
32 this->create_subscription<aimdk_msgs::msg::ProcessedAudioOutput>(
33 "/agent/process_audio_output", qos,
34 std::bind(&AudioSubscriber::audio_callback, this,
35 std::placeholders::_1));
36
37 RCLCPP_INFO(this->get_logger(),
38 "Starting to subscribe to denoised audio data...");
39 }
40
41private:
42 void
43 audio_callback(const aimdk_msgs::msg::ProcessedAudioOutput::SharedPtr msg) {
44 try {
45 uint32_t stream_id = msg->stream_id;
46 uint8_t vad_state = msg->audio_vad_state.value;
47 const std::vector<uint8_t> &audio_data = msg->audio_data;
48
49 static const std::unordered_map<uint8_t, std::string> vad_state_names = {
50 {0, "No Speech"},
51 {1, "Speech Start"},
52 {2, "Speech Processing"},
53 {3, "Speech End"}};
54 static const std::unordered_map<uint32_t, std::string> stream_names = {
55 {1, "Internal Microphone"}, {2, "External Microphone"}};
56
57 RCLCPP_INFO(this->get_logger(),
58 "Audio data received: stream_id=%u, vad_state=%u(%s), "
59 "audio_size=%zu bytes",
60 stream_id, vad_state,
61 vad_state_names.count(vad_state)
62 ? vad_state_names.at(vad_state).c_str()
63 : "Unknown State",
64 audio_data.size());
65
66 handle_vad_state(stream_id, vad_state, audio_data);
67 } catch (const std::exception &e) {
68 RCLCPP_ERROR(this->get_logger(), "Error processing audio message: %s",
69 e.what());
70 }
71 }
72
73 void handle_vad_state(uint32_t stream_id, uint8_t vad_state,
74 const std::vector<uint8_t> &audio_data) {
75 // Initialize the buffer for this stream_id (if it does not exist)
76 if (audio_buffers_.count(stream_id) == 0) {
77 audio_buffers_[stream_id] = std::vector<uint8_t>();
78 recording_state_[stream_id] = false;
79 }
80
81 static const std::unordered_map<uint8_t, std::string> vad_state_names = {
82 {0, "No Speech"},
83 {1, "Speech Start"},
84 {2, "Speech Processing"},
85 {3, "Speech End"}};
86 static const std::unordered_map<uint32_t, std::string> stream_names = {
87 {1, "Internal Microphone"}, {2, "External Microphone"}};
88
89 RCLCPP_INFO(this->get_logger(), "[%s] VAD Atate: %s Audio Data: %zu bytes",
90 stream_names.count(stream_id)
91 ? stream_names.at(stream_id).c_str()
92 : ("Unknown Stream " + std::to_string(stream_id)).c_str(),
93 vad_state_names.count(vad_state)
94 ? vad_state_names.at(vad_state).c_str()
95 : ("Unknown State" + std::to_string(vad_state)).c_str(),
96 audio_data.size());
97
98 // AUDIO_VAD_STATE_BEGIN
99 if (vad_state == 1) {
100 RCLCPP_INFO(this->get_logger(), "🎤 Speech detected - Start");
101 audio_buffers_[stream_id].clear();
102 recording_state_[stream_id] = true;
103 if (!audio_data.empty()) {
104 audio_buffers_[stream_id].insert(audio_buffers_[stream_id].end(),
105 audio_data.begin(), audio_data.end());
106 }
107
108 // AUDIO_VAD_STATE_PROCESSING
109 } else if (vad_state == 2) {
110 RCLCPP_INFO(this->get_logger(), "🔄 Speech Processing...");
111 if (recording_state_[stream_id] && !audio_data.empty()) {
112 audio_buffers_[stream_id].insert(audio_buffers_[stream_id].end(),
113 audio_data.begin(), audio_data.end());
114 }
115
116 // AUDIO_VAD_STATE_END
117 } else if (vad_state == 3) {
118 RCLCPP_INFO(this->get_logger(), "✅ Speech End");
119 if (recording_state_[stream_id] && !audio_data.empty()) {
120 audio_buffers_[stream_id].insert(audio_buffers_[stream_id].end(),
121 audio_data.begin(), audio_data.end());
122 }
123 if (recording_state_[stream_id] && !audio_buffers_[stream_id].empty()) {
124 save_audio_segment(audio_buffers_[stream_id], stream_id);
125 }
126 recording_state_[stream_id] = false;
127
128 // AUDIO_VAD_STATE_NONE
129 } else if (vad_state == 0) {
130 if (recording_state_[stream_id]) {
131 RCLCPP_INFO(this->get_logger(), "⏹️ Recording state reset");
132 recording_state_[stream_id] = false;
133 }
134 }
135
136 // Output the current buffer status.
137 size_t buffer_size = audio_buffers_[stream_id].size();
138 bool recording = recording_state_[stream_id];
139 RCLCPP_DEBUG(this->get_logger(),
140 "[Stream %u] Buffer size: %zu bytes, Recording state: %s",
141 stream_id, buffer_size, recording ? "true" : "false");
142 }
143
144 void save_audio_segment(const std::vector<uint8_t> &audio_data,
145 uint32_t stream_id) {
146 if (audio_data.empty())
147 return;
148
149 // Get the current timestamp.
150 auto now = std::chrono::system_clock::now();
151 std::time_t t = std::chrono::system_clock::to_time_t(now);
152 auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
153 now.time_since_epoch()) %
154 1000;
155
156 std::ostringstream oss;
157 oss << std::put_time(std::localtime(&t), "%Y%m%d_%H%M%S") << "_"
158 << std::setw(3) << std::setfill('0') << ms.count();
159
160 // Create a subdirectory by stream_id.
161 fs::path stream_dir =
162 fs::path(audio_output_dir_) / ("stream_" + std::to_string(stream_id));
163 fs::create_directories(stream_dir);
164
165 static const std::unordered_map<uint32_t, std::string> stream_names = {
166 {1, "internal_mic"}, {2, "external_mic"}};
167 std::string stream_name = stream_names.count(stream_id)
168 ? stream_names.at(stream_id)
169 : ("stream_" + std::to_string(stream_id));
170 std::string filename = stream_name + "_" + oss.str() + ".pcm";
171 fs::path filepath = stream_dir / filename;
172
173 try {
174 std::ofstream ofs(filepath, std::ios::binary);
175 ofs.write(reinterpret_cast<const char *>(audio_data.data()),
176 audio_data.size());
177 ofs.close();
178 RCLCPP_INFO(this->get_logger(),
179 "Audio segment saved: %s (size: %zu bytes)", filepath.c_str(),
180 audio_data.size());
181
182 // Record audio file duration (assuming 16kHz, 16-bit, mono)
183 int sample_rate = 16000;
184 int bits_per_sample = 16;
185 int channels = 1;
186 int bytes_per_sample = bits_per_sample / 8;
187 size_t total_samples = audio_data.size() / (bytes_per_sample * channels);
188 double duration_seconds =
189 static_cast<double>(total_samples) / sample_rate;
190
191 RCLCPP_INFO(this->get_logger(),
192 "Audio duration: %.2f seconds (%zu samples)",
193 duration_seconds, total_samples);
194 } catch (const std::exception &e) {
195 RCLCPP_ERROR(this->get_logger(), "Failed to save audio file: %s",
196 e.what());
197 }
198 }
199
200 // Member variables
201 std::unordered_map<uint32_t, std::vector<uint8_t>> audio_buffers_;
202 std::unordered_map<uint32_t, bool> recording_state_;
203 std::string audio_output_dir_;
204 rclcpp::Subscription<aimdk_msgs::msg::ProcessedAudioOutput>::SharedPtr
205 subscription_;
206};
207
208int main(int argc, char **argv) {
209 rclcpp::init(argc, argv);
210 auto node = std::make_shared<AudioSubscriber>();
211 RCLCPP_INFO(node->get_logger(),
212 "Listening for denoised audio data, press Ctrl+C to exit...");
213 rclcpp::spin(node);
214 rclcpp::shutdown();
215 return 0;
216}
使用说明:
运行节点后会自动创建
audio_recordings目录音频文件按stream_id分类存储:
stream_1/: 内置麦克风音频stream_2/: 外置麦克风音频
文件命名格式:
{stream_name}_{timestamp}.pcm音频格式:16kHz, 16位, 单声道PCM
可通过以下命令播放保存的PCM文件:
aplay -r 16000 -f S16_LE -c 1 external_mic_20250909_133649_738.pcm
输出示例:
[INFO] 开始订阅降噪音频数据...
[INFO] 收到音频数据: stream_id=2, vad_state=1(语音开始), audio_size=320 bytes
[INFO] [外置麦克风] VAD状态: 语音开始 音频数据: 320 bytes
[INFO] 🎤 检测到语音开始
[INFO] 收到音频数据: stream_id=2, vad_state=2(语音处理中), audio_size=320 bytes
[INFO] [外置麦克风] VAD状态: 语音处理中 音频数据: 320 bytes
[INFO] 🔄 语音处理中...
[INFO] 收到音频数据: stream_id=2, vad_state=3(语音结束), audio_size=320 bytes
[INFO] [外置麦克风] VAD状态: 语音结束 音频数据: 320 bytes
[INFO] ✅ 语音结束
[INFO] 音频段已保存: audio_recordings/stream_2/external_mic_20250909_133649_738.pcm (大小: 960 bytes)
[INFO] 音频时长: 0.06 秒 (480 样本)
播放PCM音频文件示例 (Linux下使用aplay命令)
假设你已经录制并保存了音频文件 external_mic_20250909_151117_223.pcm, 可以通过如下命令进行播放:
aplay -r 16000 -f S16_LE -c 1 audio_recordings/stream_2/external_mic_20250909_151117_223.pcm
参数说明:
-r 16000 # 采样率16kHz
-f S16_LE # 16位小端格式
-c 1 # 单声道 你也可以用其他音频播放器(如Audacity)以原始PCM格式导入并播放。
注意:如果你保存的是内置麦克风音频,路径应为 audio_recordings/stream_1/internal_mic_xxx.pcm
6.2.18 表情控制
该示例中用到了play_emoji,通过该节点可实现表达指定的表情,用户可根据已有的表情列表来选择要变化的表情,具体表情列表可参考表情列表
1#include <aimdk_msgs/msg/common_request.hpp>
2#include <aimdk_msgs/srv/play_emoji.hpp>
3#include <chrono>
4#include <rclcpp/rclcpp.hpp>
5#include <string>
6#include <thread>
7using namespace std::chrono_literals;
8
9class PlayEmojiClient : public rclcpp::Node {
10public:
11 PlayEmojiClient() : Node("play_emoji_client"), finished_(false) {
12 client_ = this->create_client<aimdk_msgs::srv::PlayEmoji>(
13 "/face_ui_proxy/play_emoji");
14 RCLCPP_INFO(this->get_logger(), "PlayEmoji client node started.");
15 }
16
17 void send_request(uint8_t emoji, uint8_t mode, uint8_t priority) {
18 while (!client_->wait_for_service(1s) && rclcpp::ok()) {
19 RCLCPP_WARN(this->get_logger(), "Waiting for PlayEmoji service...");
20 }
21 if (!rclcpp::ok())
22 return;
23
24 auto request = std::make_shared<aimdk_msgs::srv::PlayEmoji::Request>();
25 request->header.header.stamp = this->now();
26 request->emotion_id = emoji;
27 request->mode = mode;
28 request->priority = priority;
29
30 using ServiceResponseFuture =
31 rclcpp::Client<aimdk_msgs::srv::PlayEmoji>::SharedFuture;
32 auto response_callback = [this](ServiceResponseFuture future) {
33 auto response = future.get();
34 if (response->success) {
35 RCLCPP_INFO(this->get_logger(), "✅ Emoji played successfully: %s",
36 response->message.c_str());
37 } else {
38 RCLCPP_ERROR(this->get_logger(), "❌ Emoji play failed: %s",
39 response->message.c_str());
40 }
41 finished_ = true;
42 };
43 client_->async_send_request(request, response_callback);
44 }
45
46 bool finished() const { return finished_; }
47
48private:
49 rclcpp::Client<aimdk_msgs::srv::PlayEmoji>::SharedPtr client_;
50 std::atomic_bool finished_;
51};
52
53int main(int argc, char **argv) {
54 rclcpp::init(argc, argv);
55
56 PlayEmojiClient client_node;
57
58 int emotion = 1; // Expression type, 1 means Blink
59 int mode = 1; // Playback mode, 1 means play once, 2 means loop
60 int priority = 10; // Default priority
61
62 std::cout
63 << "Enter expression ID: 1-Blink, 60-Bored, 70-Abnormal, 80-Sleeping, "
64 "90-Happy, 190-Very Angry, 200-Adoration"
65 << std::endl;
66 std::cin >> emotion;
67 std::cout << "Enter playback mode (1: Play Once, 2: Loop): " << std::endl;
68 std::cin >> mode;
69
70 client_node.send_request(emotion, mode, priority);
71
72 while (rclcpp::ok() && !client_node.finished()) {
73 rclcpp::spin_some(client_node.get_node_base_interface());
74 std::this_thread::sleep_for(100ms);
75 }
76
77 rclcpp::shutdown();
78 return 0;
79}
6.2.19 LED灯带控制
功能说明:演示如何控制机器人的LED灯带,支持多种显示模式和自定义颜色。
核心代码:
1#include <aimdk_msgs/msg/common_request.hpp>
2#include <aimdk_msgs/srv/led_strip_command.hpp>
3#include <chrono>
4#include <memory>
5#include <rclcpp/rclcpp.hpp>
6#include <signal.h>
7#include <string>
8
9using namespace std::chrono_literals;
10
11std::shared_ptr<rclcpp::Node> g_node = nullptr;
12
13void signal_handler(int signal) {
14 if (g_node) {
15 RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
16 signal);
17 rclcpp::shutdown();
18 }
19}
20
21class PlayLightsClient : public rclcpp::Node {
22public:
23 PlayLightsClient() : Node("play_lights_client") {
24 client_ = this->create_client<aimdk_msgs::srv::LedStripCommand>(
25 "/aimdk_5Fmsgs/srv/LedStripCommand");
26 RCLCPP_INFO(this->get_logger(), "PlayLights client node started.");
27 }
28
29 bool send_request(uint8_t led_mode, uint8_t r, uint8_t g, uint8_t b) {
30 while (!client_->wait_for_service(1s) && rclcpp::ok()) {
31 RCLCPP_WARN(this->get_logger(), "Waiting for LedStripCommand service...");
32 }
33
34 if (!rclcpp::ok())
35 return false;
36
37 auto request =
38 std::make_shared<aimdk_msgs::srv::LedStripCommand::Request>();
39
40 request->led_strip_mode = led_mode;
41 request->r = r;
42 request->g = g;
43 request->b = b;
44
45 // LED strip is slow to response (up to ~5s)
46 const std::chrono::milliseconds timeout(5000);
47
48 for (int i = 0; i < 4; i++) {
49 request->request.header.stamp = this->now();
50 auto future = client_->async_send_request(request);
51 auto retcode = rclcpp::spin_until_future_complete(
52 this->shared_from_this(), future, timeout);
53 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
54 // retry as remote peer module can be under heavy load
55 RCLCPP_INFO(this->get_logger(),
56 "trying to send LED strip command... [%d]", i);
57 continue;
58 }
59 // future.done
60 auto response = future.get();
61 if (response->status_code == 0) {
62 RCLCPP_INFO(this->get_logger(),
63 "✅ LED strip command sent successfully");
64 return true;
65 } else {
66 RCLCPP_ERROR(this->get_logger(),
67 "❌ LED strip command failed with status: %d",
68 response->status_code);
69 return false;
70 }
71 }
72 RCLCPP_ERROR(this->get_logger(), "Service call failed or timed out");
73 return false;
74 }
75
76private:
77 rclcpp::Client<aimdk_msgs::srv::LedStripCommand>::SharedPtr client_;
78};
79
80int main(int argc, char **argv) {
81 try {
82 rclcpp::init(argc, argv);
83 signal(SIGINT, signal_handler);
84 signal(SIGTERM, signal_handler);
85
86 auto client_node = std::make_shared<PlayLightsClient>();
87 g_node = client_node;
88
89 int led_mode = 0; // LED Strip Mode
90 int r = 255, g = 0, b = 0; // RGB values
91
92 std::cout << "=== LED Strip Control Example ===" << std::endl;
93 std::cout << "Select LED strip mode:" << std::endl;
94 std::cout << "0 - Steady On" << std::endl;
95 std::cout << "1 - Breathing (4s period, sinusoidal brightness)"
96 << std::endl;
97 std::cout << "2 - Blinking (1s period, 0.5s on, 0.5s off)" << std::endl;
98 std::cout << "3 - Flow (2s period, lights turn on left to right)"
99 << std::endl;
100 std::cout << "Enter mode (0-3): ";
101 std::cin >> led_mode;
102
103 std::cout << "\nSet RGB color values (0-255):" << std::endl;
104 std::cout << "Red component (R): ";
105 std::cin >> r;
106 std::cout << "Green component (G): ";
107 std::cin >> g;
108 std::cout << "Blue component (B): ";
109 std::cin >> b;
110
111 // 验证输入范围
112 led_mode = std::max(0, std::min(3, led_mode));
113 r = std::max(0, std::min(255, r));
114 g = std::max(0, std::min(255, g));
115 b = std::max(0, std::min(255, b));
116
117 std::cout << "\nSending LED control command..." << std::endl;
118 std::cout << "Mode: " << led_mode << ", Color: RGB(" << r << ", " << g
119 << ", " << b << ")" << std::endl;
120
121 if (!client_node->send_request(led_mode, r, g, b)) {
122 RCLCPP_ERROR(client_node->get_logger(),
123 "LED strip control request failed");
124 }
125
126 // Shutdown ROS first, then release resources
127 rclcpp::shutdown();
128 client_node.reset();
129 g_node.reset();
130
131 return 0;
132 } catch (const std::exception &e) {
133 RCLCPP_ERROR(rclcpp::get_logger("main"),
134 "Program terminated with exception: %s", e.what());
135 rclcpp::shutdown();
136 return 1;
137 }
138}
使用说明:
# 构建
colcon build --packages-select examples
# 运行
ros2 run examples play_lights
输出示例:
=== LED灯带控制示例 ===
请选择灯带模式:
0 - 常亮模式
1 - 呼吸模式 (4s周期,亮度正弦变化)
2 - 闪烁模式 (1s周期,0.5s亮,0.5s灭)
3 - 流水模式 (2s周期,从左到右依次点亮)
请输入模式 (0-3): 1
请设置RGB颜色值 (0-255):
红色分量 (R): 255
绿色分量 (G): 0
蓝色分量 (B): 0
发送LED控制命令...
模式: 1, 颜色: RGB(255, 0, 0)
✅ LED strip command sent successfully
技术特点:
支持4种LED显示模式
RGB颜色自定义
异步服务调用
输入参数验证
友好的用户交互界面