6.2 C++接口使用示例
本章节将带您实现索引所示若干功能
构建 & 运行说明
进入到解压后的 sdk 顶层目录,执行以下命令
source /opt/ros/humble/setup.bash colcon build source install/setup.bash ros2 run examples '对应功能名称如: get_mc_action'
📝 代码说明
完整的代码实现包含完整的错误处理、信号处理、超时处理等机制,确保程序的健壮性。请您在 examples 目录下查看/修改
小心
ROS的服务(service)机制在跨板通信时存在一些待优化问题, 二次开发时请参考例程添加异常处理、快速重试等保护机制
6.2.1 进入开发者模式
通过调用'GetSystemState'服务获取机器人当前的系统状态,并且通过'MigrateSystemState'进入对应的开发者模式。
1#include <algorithm>
2#include <chrono>
3#include <iostream>
4#include <memory>
5#include <string>
6
7#include "aimdk_msgs/srv/get_system_state.hpp"
8#include "aimdk_msgs/srv/migrate_system_state.hpp"
9#include "rclcpp/rclcpp.hpp"
10
11using namespace std::chrono_literals;
12
13/**
14 * @class SystemStateMigrator
15 * @brief ROS2 client for migrating system state to any target state
16 */
17class SystemStateMigrator : public rclcpp::Node {
18public:
19 /**
20 * @brief Constructor for SystemStateMigrator
21 * @param target_state The target system state to migrate to
22 */
23 SystemStateMigrator(const std::string &target_state)
24 : Node("system_state_migrator"), target_state_(target_state) {
25 // Create service clients
26 migrate_client_ = this->create_client<aimdk_msgs::srv::MigrateSystemState>(
27 "/aimdk_5Fmsgs/srv/MigrateSystemState");
28
29 get_state_client_ = this->create_client<aimdk_msgs::srv::GetSystemState>(
30 "/aimdk_5Fmsgs/srv/GetSystemState");
31
32 // Log target state
33 RCLCPP_INFO(this->get_logger(), "Target state: %s", target_state_.c_str());
34
35 // Start the migration process
36 start_migration_process();
37 }
38
39private:
40 /**
41 * @brief Start the migration process
42 */
43 void start_migration_process() {
44 // Create a timer to check service availability
45 check_timer_ = this->create_wall_timer(
46 1s, std::bind(&SystemStateMigrator::check_services_and_start, this));
47 }
48
49 /**
50 * @brief Check if services are available and start migration
51 */
52 void check_services_and_start() {
53 // Check if both services are available
54 if (migrate_client_->service_is_ready() &&
55 get_state_client_->service_is_ready()) {
56 // Services are available, cancel timer and start migration
57 check_timer_->cancel();
58 RCLCPP_INFO(this->get_logger(),
59 "Both services are available, starting migration to %s...",
60 target_state_.c_str());
61 migrate_to_target_state();
62 } else {
63 RCLCPP_INFO(this->get_logger(), "Waiting for services...");
64 }
65 }
66
67 /**
68 * @brief Migrate system state to target state with retry
69 */
70 void migrate_to_target_state() {
71 if (migrate_pending_) {
72 return;
73 }
74 if (migrate_retry_count_ >= max_migrate_retries_) {
75 RCLCPP_ERROR(this->get_logger(),
76 "Migration request failed after %d attempts",
77 max_migrate_retries_);
78 rclcpp::shutdown();
79 return;
80 }
81
82 auto request =
83 std::make_shared<aimdk_msgs::srv::MigrateSystemState::Request>();
84
85 auto now = this->now();
86 request->header.header.stamp.sec = now.seconds();
87 request->header.header.stamp.nanosec = now.nanoseconds() % 1000000000;
88
89 request->state = target_state_;
90
91 migrate_retry_count_++;
92 migrate_pending_ = true;
93 uint64_t req_id = ++migrate_request_id_;
94
95 RCLCPP_INFO(this->get_logger(),
96 "Sending migration request to %s state (attempt %d/%d)...",
97 target_state_.c_str(), migrate_retry_count_,
98 max_migrate_retries_);
99
100 migrate_timeout_timer_ = this->create_wall_timer(30s, [this, req_id]() {
101 if (req_id != migrate_request_id_) {
102 return;
103 }
104 migrate_timeout_timer_.reset();
105 migrate_pending_ = false;
106 RCLCPP_WARN(this->get_logger(), "Migration request timeout, retrying...");
107 migrate_to_target_state();
108 });
109
110 auto future = migrate_client_->async_send_request(
111 request,
112 [this, req_id](
113 rclcpp::Client<aimdk_msgs::srv::MigrateSystemState>::SharedFuture
114 future) {
115 if (req_id != migrate_request_id_) {
116 return;
117 }
118 if (migrate_timeout_timer_) {
119 migrate_timeout_timer_->cancel();
120 migrate_timeout_timer_.reset();
121 }
122 migrate_pending_ = false;
123
124 try {
125 auto response = future.get();
126 if (response->header.status.value ==
127 aimdk_msgs::msg::CommonState::SUCCESS) {
128 migrate_retry_count_ = 0;
129 ++migrate_request_id_;
130 RCLCPP_INFO(this->get_logger(),
131 "Migration request accepted, starting to monitor "
132 "state...");
133 start_state_monitoring();
134 } else {
135 ++migrate_request_id_;
136 RCLCPP_ERROR(this->get_logger(),
137 "Migration request failed with status: %d, message: "
138 "%s",
139 response->header.status.value,
140 response->header.message.c_str());
141 rclcpp::shutdown();
142 }
143 } catch (const std::exception &e) {
144 ++migrate_request_id_;
145 RCLCPP_ERROR(this->get_logger(),
146 "Migration service call failed: %s", e.what());
147 rclcpp::shutdown();
148 }
149 });
150 }
151
152 /**
153 * @brief Start monitoring system state
154 */
155 void start_state_monitoring() {
156 RCLCPP_INFO(this->get_logger(), "Starting to monitor system state...");
157
158 // Create a timer to periodically check system state
159 monitor_timer_ = this->create_wall_timer(
160 1s, std::bind(&SystemStateMigrator::check_system_state, this));
161 }
162
163 /**
164 * @brief Check current system state with retry
165 */
166 void check_system_state() {
167 if (get_state_pending_) {
168 return;
169 }
170 if (get_state_retry_count_ >= max_get_state_retries_) {
171 RCLCPP_ERROR(this->get_logger(),
172 "Get system state failed after %d attempts, will retry on "
173 "next monitor cycle",
174 max_get_state_retries_);
175 get_state_retry_count_ = 0;
176 return;
177 }
178
179 auto request = std::make_shared<aimdk_msgs::srv::GetSystemState::Request>();
180
181 auto now = this->now();
182 request->header.header.stamp.sec = now.seconds();
183 request->header.header.stamp.nanosec = now.nanoseconds() % 1000000000;
184
185 get_state_retry_count_++;
186 get_state_pending_ = true;
187 uint64_t req_id = ++get_state_request_id_;
188
189 get_state_timeout_timer_ = this->create_wall_timer(10s, [this, req_id]() {
190 if (req_id != get_state_request_id_) {
191 return;
192 }
193 get_state_timeout_timer_.reset();
194 get_state_pending_ = false;
195 RCLCPP_WARN(this->get_logger(),
196 "GetSystemState request timeout (attempt %d/%d), "
197 "retrying...",
198 get_state_retry_count_, max_get_state_retries_);
199 check_system_state();
200 });
201
202 auto future = get_state_client_->async_send_request(
203 request,
204 [this,
205 req_id](rclcpp::Client<aimdk_msgs::srv::GetSystemState>::SharedFuture
206 future) {
207 if (req_id != get_state_request_id_) {
208 return;
209 }
210 if (get_state_timeout_timer_) {
211 get_state_timeout_timer_->cancel();
212 get_state_timeout_timer_.reset();
213 }
214 get_state_pending_ = false;
215
216 try {
217 auto response = future.get();
218 get_state_retry_count_ = 0;
219 ++get_state_request_id_;
220
221 RCLCPP_INFO(
222 this->get_logger(), "Current State: %s, System Status: %u",
223 response->cur_state.c_str(), response->curr_status.value);
224
225 if (is_migration_complete(response)) {
226 RCLCPP_INFO(this->get_logger(),
227 "Migration to %s completed successfully!",
228 target_state_.c_str());
229 monitor_timer_->cancel();
230 rclcpp::shutdown();
231 } else {
232 RCLCPP_INFO(this->get_logger(),
233 "Migration in progress, will check again in 1 "
234 "second...");
235 }
236 } catch (const std::exception &e) {
237 RCLCPP_ERROR(this->get_logger(),
238 "GetSystemState service call failed: %s", e.what());
239 }
240 });
241 }
242
243 /**
244 * @brief Check if migration to target state is complete
245 * @param response Response from GetSystemState service
246 * @return true if migration is complete, false otherwise
247 */
248 bool is_migration_complete(
249 const aimdk_msgs::srv::GetSystemState::Response::SharedPtr response) {
250 // Convert current state and target state to lowercase for case-insensitive
251 // comparison
252 std::string current_state_lower = response->cur_state;
253 std::transform(current_state_lower.begin(), current_state_lower.end(),
254 current_state_lower.begin(), ::tolower);
255
256 std::string target_state_lower = target_state_;
257 std::transform(target_state_lower.begin(), target_state_lower.end(),
258 target_state_lower.begin(), ::tolower);
259
260 // Check conditions:
261 // 1. Current state equals target state (case-insensitive)
262 // 2. System status equals 1 (IN_READY)
263 bool state_match = (current_state_lower == target_state_lower);
264 bool status_match = (response->curr_status.value ==
265 aimdk_msgs::msg::SystemStatus::IN_READY);
266
267 if (state_match && status_match) {
268 return true;
269 }
270
271 // Log detailed information if not complete
272 if (!state_match) {
273 RCLCPP_INFO(this->get_logger(),
274 "State mismatch: current='%s', expected='%s'",
275 response->cur_state.c_str(), target_state_.c_str());
276 }
277 if (!status_match) {
278 RCLCPP_INFO(this->get_logger(),
279 "Status mismatch: current= %u, expected=1 (IN_READY)",
280 response->curr_status.value);
281 }
282 if (state_match && !state_match) {
283 RCLCPP_ERROR(this->get_logger(), "State Migrate Fail");
284 exit(0);
285 }
286
287 return false;
288 }
289
290 // Target state to migrate to
291 std::string target_state_;
292
293 // Service clients
294 rclcpp::Client<aimdk_msgs::srv::MigrateSystemState>::SharedPtr
295 migrate_client_;
296 rclcpp::Client<aimdk_msgs::srv::GetSystemState>::SharedPtr get_state_client_;
297
298 // Timers
299 rclcpp::TimerBase::SharedPtr check_timer_;
300 rclcpp::TimerBase::SharedPtr monitor_timer_;
301
302 // Retry counters and timeout timers for migration
303 int migrate_retry_count_ = 0;
304 const int max_migrate_retries_ = 5;
305 uint64_t migrate_request_id_ = 0;
306 bool migrate_pending_ = false;
307 rclcpp::TimerBase::SharedPtr migrate_timeout_timer_;
308
309 // Retry counters and timeout timers for get state
310 int get_state_retry_count_ = 0;
311 const int max_get_state_retries_ = 3;
312 uint64_t get_state_request_id_ = 0;
313 bool get_state_pending_ = false;
314 rclcpp::TimerBase::SharedPtr get_state_timeout_timer_;
315};
316
317/**
318 * @brief Print usage information with detailed mode descriptions
319 */
320void print_usage(const char *program_name) {
321 std::cout << "Usage: " << program_name << " <target_state>" << std::endl;
322 std::cout << std::endl;
323 std::cout << "Available target states:" << std::endl;
324 std::cout << " " << program_name << " Ready" << std::endl;
325 std::cout << " " << program_name << " Develop_Nav" << std::endl;
326 std::cout << " " << program_name << " Develop_Audio_Linux" << std::endl;
327 std::cout << " " << program_name << " Develop_Audio_ROS" << std::endl;
328 std::cout << " " << program_name << " Develop_MC" << std::endl;
329 std::cout << std::endl;
330
331 std::cout << "Mode Descriptions:" << std::endl;
332 std::cout << "==================" << std::endl;
333
334 // Ready mode description
335 std::cout << "1. Ready - System Default Mode" << std::endl;
336 std::cout
337 << " • Description: System enters this mode by default after startup"
338 << std::endl;
339 std::cout << " • Purpose: Normal operation mode, ready for general tasks"
340 << std::endl;
341 std::cout
342 << " • Use case: Return to normal operation after development work"
343 << std::endl;
344 std::cout << std::endl;
345
346 // Develop_Nav mode description
347 std::cout << "2. Develop_Nav - Navigation Development Mode" << std::endl;
348 std::cout
349 << " • Description: Special mode for navigation system development"
350 << std::endl;
351 std::cout << " • Purpose: Enables navigation-related debugging and testing"
352 << std::endl;
353 std::cout << " • Use case: When developing or testing navigation "
354 "algorithms, SLAM, path planning"
355 << std::endl;
356 std::cout << " • Features: May provide access to raw sensor data, "
357 "navigation debug topics"
358 << std::endl;
359 std::cout << std::endl;
360
361 // Develop_Audio_Linux mode description
362 std::cout << "3. Develop_Audio_Linux - System-Level Audio Development Mode"
363 << std::endl;
364 std::cout << " • Description: Mode for low-level audio system development"
365 << std::endl;
366 std::cout << " • Purpose: Access to system audio streams at Linux level"
367 << std::endl;
368 std::cout << " • Use case: When developing audio drivers, audio processing "
369 "at system level"
370 << std::endl;
371 std::cout << " • Features: Direct access to audio hardware, "
372 "ALSA/PulseAudio interfaces"
373 << std::endl;
374 std::cout << std::endl;
375
376 // Develop_Audio_ROS mode description
377 std::cout << "4. Develop_Audio_ROS - ROS Audio Development Mode" << std::endl;
378 std::cout
379 << " • Description: Mode for ROS-based audio application development"
380 << std::endl;
381 std::cout << " • Purpose: Access to audio data through ROS topics"
382 << std::endl;
383 std::cout << " • Use case: When developing ROS nodes that process audio, "
384 "speech recognition"
385 << std::endl;
386 std::cout << " • Features: Audio data published as ROS topics, ROS message "
387 "interfaces"
388 << std::endl;
389 std::cout << std::endl;
390
391 // Develop_MC mode description
392 std::cout << "5. Develop_MC - Motion Control Development Mode" << std::endl;
393 std::cout << " • Description: Mode for motion control system development"
394 << std::endl;
395 std::cout
396 << " • Purpose: Enables direct control and testing of motion systems"
397 << std::endl;
398 std::cout << " • Use case: When developing motor control algorithms, "
399 "testing motion hardware"
400 << std::endl;
401 std::cout << " • Features: Low-level motor control access, motion system "
402 "debugging tools"
403 << std::endl;
404 std::cout << std::endl;
405
406 std::cout << "Program Workflow:" << std::endl;
407 std::cout << "=================" << std::endl;
408 std::cout
409 << "1. Calls MigrateSystemState service with the specified target state"
410 << std::endl;
411 std::cout << "2. Continuously monitors GetSystemState service every second"
412 << std::endl;
413 std::cout << "3. Exits when both conditions are met:" << std::endl;
414 std::cout << " • Current State equals target state (case-insensitive)"
415 << std::endl;
416 std::cout << " • System Status equals 1 (IN_READY)" << std::endl;
417 std::cout << std::endl;
418 std::cout << "Note: The program will automatically exit when migration is "
419 "complete or on error."
420 << std::endl;
421}
422
423/**
424 * @brief Validate if the target state is a known development mode
425 * @param state The state to validate
426 * @return true if state is valid, false otherwise
427 */
428bool is_valid_state(const std::string &state) {
429 // List of known valid states
430 const std::vector<std::string> valid_states = {
431 "Ready", "Develop_Nav", "Develop_Audio_Linux", "Develop_Audio_ROS",
432 "Develop_MC"};
433
434 // Convert input to lowercase for case-insensitive comparison
435 std::string state_lower = state;
436 std::transform(state_lower.begin(), state_lower.end(), state_lower.begin(),
437 ::tolower);
438
439 // Check if state is in the list of valid states
440 for (const auto &valid_state : valid_states) {
441 std::string valid_state_lower = valid_state;
442 std::transform(valid_state_lower.begin(), valid_state_lower.end(),
443 valid_state_lower.begin(), ::tolower);
444
445 if (state_lower == valid_state_lower) {
446 return true;
447 }
448 }
449
450 return false;
451}
452
453/**
454 * @brief Main function
455 * @param argc Argument count
456 * @param argv Argument vector
457 * @return Exit code
458 */
459int main(int argc, char **argv) {
460 // Check command line arguments
461 if (argc != 2) {
462 print_usage(argv[0]);
463 return 1;
464 }
465
466 // Get target state from command line argument
467 std::string target_state = argv[1];
468
469 // Validate target state
470 if (target_state.empty()) {
471 std::cerr << "Error: Target state cannot be empty" << std::endl;
472 print_usage(argv[0]);
473 return 1;
474 }
475
476 // Optional: Validate against known states
477 if (!is_valid_state(target_state)) {
478 std::cout << "Warning: '" << target_state
479 << "' is not in the list of known states." << std::endl;
480 std::cout << "The program will still attempt to migrate to this state."
481 << std::endl;
482 std::cout << "Continue? (y/n): ";
483
484 std::string response;
485 std::getline(std::cin, response);
486
487 if (response != "y" && response != "Y") {
488 std::cout << "Operation cancelled." << std::endl;
489 return 0;
490 }
491 }
492
493 // Initialize ROS2
494 rclcpp::init(argc, argv);
495
496 // Create migrator node with target state
497 auto node = std::make_shared<SystemStateMigrator>(target_state);
498
499 // Create single-threaded executor
500 auto executor = std::make_shared<rclcpp::executors::SingleThreadedExecutor>();
501
502 // Add node to executor
503 executor->add_node(node);
504
505 // Run executor (blocking call)
506 executor->spin();
507
508 // Cleanup
509 executor->remove_node(node);
510 rclcpp::shutdown();
511
512 return 0;
513}
使用说明
ros2 run examples migrate_system_state Develop_MC
输出示例
[INFO] [1770125761.202173770] [system_state_migrator]: Target state: Develop_MC
[INFO] [1770125762.202825297] [system_state_migrator]: Both services are available, starting migration to Develop_MC...
[INFO] [1770125762.202949414] [system_state_migrator]: Sending migration request to Develop_MC state...
[INFO] [1770125762.264580770] [system_state_migrator]: Migration request accepted, starting to monitor state...
[INFO] [1770125762.264608479] [system_state_migrator]: Starting to monitor system state...
[INFO] [1770125763.268422431] [system_state_migrator]: Current State: Business, System Status: 2
[INFO] [1770125763.268513271] [system_state_migrator]: State mismatch: current='Business', expected='Develop_MC'
[INFO] [1770125763.268531394] [system_state_migrator]: Status mismatch: current= 2, expected=1 (IN_READY)
[INFO] [1770125763.268545617] [system_state_migrator]: Migration in progress, will check again in 1 second...
[INFO] [1770125764.266388052] [system_state_migrator]: Current State: Business, System Status: 2
[INFO] [1770125764.266492040] [system_state_migrator]: State mismatch: current='Business', expected='Develop_MC'
[INFO] [1770125764.266509640] [system_state_migrator]: Status mismatch: current= 2, expected=1 (IN_READY)
[INFO] [1770125764.266532986] [system_state_migrator]: Migration in progress, will check again in 1 second...
[INFO] [1770125765.266452308] [system_state_migrator]: Current State: Develop_MC, System Status: 1
[INFO] [1770125765.266562888] [system_state_migrator]: Migration to Develop_MC completed successfully!
6.2.2 获取机器人模式
通过调用GetMcAction服务获取机器人当前的运行模式,包括名称和状态信息。
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 g_node.reset();
18 }
19 rclcpp::shutdown();
20 exit(signal);
21}
22
23class GetMcActionClient : public rclcpp::Node {
24public:
25 GetMcActionClient() : Node("get_mc_action_client") {
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(), "✅ GetMcAction client node created.");
30
31 while (!client_->wait_for_service(std::chrono::seconds(2))) {
32 RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
33 }
34 RCLCPP_INFO(this->get_logger(),
35 "🟢 Service available, ready to send request.");
36 }
37
38 void send_request() {
39 try {
40 auto request = std::make_shared<aimdk_msgs::srv::GetMcAction::Request>();
41 request->request = aimdk_msgs::msg::CommonRequest();
42
43 RCLCPP_INFO(this->get_logger(), "📨 Sending request to get robot mode");
44
45 // Set a service call timeout
46 const std::chrono::milliseconds timeout(250);
47 for (int i = 0; i < 8; i++) {
48 request->request.header.stamp = this->now();
49 auto future = client_->async_send_request(request);
50 auto retcode = rclcpp::spin_until_future_complete(shared_from_this(),
51 future, timeout);
52 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
53 // retry as remote peer is NOT handled well by ROS
54 RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
55 continue;
56 }
57 // future.done
58 auto response = future.get();
59 RCLCPP_INFO(this->get_logger(), "✅ Robot mode get successfully.");
60 RCLCPP_INFO(this->get_logger(), "Mode name: %s",
61 response->info.action_desc.c_str());
62 RCLCPP_INFO(this->get_logger(), "Mode status: %d",
63 response->info.status.value);
64 return;
65 }
66 RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
67 } catch (const std::exception &e) {
68 RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
69 }
70 }
71
72private:
73 rclcpp::Client<aimdk_msgs::srv::GetMcAction>::SharedPtr client_;
74};
75
76int main(int argc, char *argv[]) {
77 try {
78 rclcpp::init(argc, argv);
79
80 // Set up signal handlers
81 signal(SIGINT, signal_handler);
82 signal(SIGTERM, signal_handler);
83
84 // Create node
85 g_node = std::make_shared<GetMcActionClient>();
86 auto client = std::dynamic_pointer_cast<GetMcActionClient>(g_node);
87 if (client) {
88 client->send_request();
89 }
90
91 // Clean up resources
92 g_node.reset();
93 rclcpp::shutdown();
94
95 return 0;
96 } catch (const std::exception &e) {
97 RCLCPP_ERROR(rclcpp::get_logger("main"),
98 "Program exited with exception: %s", e.what());
99 return 1;
100 }
101}
使用说明
ros2 run examples get_mc_action
输出示例
...
[INFO] [1764066631.021247791] [get_mc_action_client]: Current robot mode:
[INFO] [1764066631.021832667] [get_mc_action_client]: Mode name: PASSIVE_DEFAULT
[INFO] [1764066631.022396136] [get_mc_action_client]: Mode status: 100
接口参考
服务:
/aimdk_5Fmsgs/srv/GetMcAction消息:
aimdk_msgs/srv/GetMcAction
6.2.3 设置机器人模式
该示例中用到了SetMcAction服务,运行节点程序后终端输入模式对应的字段值,机器人会尝试进入相应的运动模式。
切入稳定站立(STAND_DEFAULT)模式前,确保机器人已立起且双脚着地。
模式切换需遵循模式转换图, 跨模式转换不能成功
走跑模式(LOCOMOTION_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 <iomanip>
10#include <memory>
11#include <signal.h>
12#include <unordered_map>
13#include <vector>
14
15// Global variable used for signal handling
16std::shared_ptr<rclcpp::Node> g_node = nullptr;
17
18// Signal handler function
19void signal_handler(int signal) {
20 if (g_node) {
21 RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
22 signal);
23 g_node.reset();
24 }
25 rclcpp::shutdown();
26 exit(signal);
27}
28
29class SetMcActionClient : public rclcpp::Node {
30public:
31 SetMcActionClient() : Node("set_mc_action_client") {
32
33 client_ = this->create_client<aimdk_msgs::srv::SetMcAction>(
34 "/aimdk_5Fmsgs/srv/SetMcAction");
35 RCLCPP_INFO(this->get_logger(), "✅ SetMcAction client node created.");
36
37 // Wait for the service to become available
38 while (!client_->wait_for_service(std::chrono::seconds(2))) {
39 RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
40 }
41 RCLCPP_INFO(this->get_logger(),
42 "🟢 Service available, ready to send request.");
43 }
44
45 bool send_request(std::string &action_name) {
46 try {
47 auto request = std::make_shared<aimdk_msgs::srv::SetMcAction::Request>();
48 request->header = aimdk_msgs::msg::RequestHeader();
49
50 request->source = "node";
51
52 // Set robot mode
53 aimdk_msgs::msg::McActionCommand command;
54 command.action_desc = action_name;
55 request->command = command;
56
57 RCLCPP_INFO(this->get_logger(), "📨 Sending request to set robot mode: %s",
58 action_name.c_str());
59
60 // Set Service Call Timeout
61 const std::chrono::milliseconds timeout(250);
62 for (int i = 0; i < 8; i++) {
63 request->header.stamp = this->now();
64 auto future = client_->async_send_request(request);
65 auto retcode = rclcpp::spin_until_future_complete(shared_from_this(),
66 future, timeout);
67 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
68 // retry as remote peer is NOT handled well by ROS
69 RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
70 continue;
71 }
72 // future.done
73 auto response = future.get();
74 if (response->response.status.value ==
75 aimdk_msgs::msg::CommonState::SUCCESS) {
76 RCLCPP_INFO(this->get_logger(), "✅ Robot mode set successfully.");
77 return true;
78 } else {
79 RCLCPP_ERROR(this->get_logger(), "❌ Failed to set robot mode: %s",
80 response->response.message.c_str());
81 return false;
82 }
83 }
84 RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
85 return false;
86 } catch (const std::exception &e) {
87 RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
88 return false;
89 }
90 }
91
92private:
93 rclcpp::Client<aimdk_msgs::srv::SetMcAction>::SharedPtr client_;
94};
95
96static std::unordered_map<std::string, std::vector<std::string>> g_action_info =
97 {
98 {"PASSIVE_DEFAULT", {"PD", "joints with zero torque"}},
99 {"DAMPING_DEFAULT", {"DD", "joints in damping mode"}},
100 {"JOINT_DEFAULT", {"JD", "Position Control Stand (joints locked)"}},
101 {"STAND_DEFAULT", {"SD", "Stable Stand (auto-balance)"}},
102 {"LOCOMOTION_DEFAULT", {"LD", "locomotion mode (walk or run)"}},
103};
104
105int main(int argc, char *argv[]) {
106 try {
107 rclcpp::init(argc, argv);
108
109 // Set up signal handlers
110 signal(SIGINT, signal_handler);
111 signal(SIGTERM, signal_handler);
112
113 // Create node
114 g_node = std::make_shared<SetMcActionClient>();
115 auto client = std::dynamic_pointer_cast<SetMcActionClient>(g_node);
116
117 if (client) {
118 std::unordered_map<std::string, std::string> choices;
119 std::string motion;
120
121 // Prefer command-line argument; otherwise prompt user
122 if (argc > 1) {
123 motion = argv[1];
124 RCLCPP_INFO(g_node->get_logger(),
125 "Using abbr of motion mode from cmdline: %s", argv[1]);
126 } else {
127 std::cout << std::left << std::setw(4) << "abbr"
128 << " - " << std::setw(20) << "robot mode"
129 << " : "
130 << "description" << std::endl;
131 for (auto &it : g_action_info) {
132 std::cout << std::left << std::setw(4) << it.second[0] << " - "
133 << std::setw(20) << it.first << " : " << it.second[1]
134 << std::endl;
135 }
136 std::cout << "Enter abbr of motion mode:";
137 std::cin >> motion;
138 }
139 for (auto &it : g_action_info) {
140 choices[it.second[0]] = it.first;
141 }
142
143 auto m = choices.find(motion);
144 if (m != choices.end()) {
145 auto &action_name = m->second;
146 client->send_request(action_name);
147 } else {
148 RCLCPP_ERROR(g_node->get_logger(), "Invalid abbr of robot mode: %s",
149 motion.c_str());
150 }
151 }
152
153 // Clean up resources
154 g_node.reset();
155 rclcpp::shutdown();
156
157 return 0;
158 } catch (const std::exception &e) {
159 RCLCPP_ERROR(rclcpp::get_logger("main"),
160 "Program exited with exception: %s", e.what());
161 return 1;
162 }
163}
使用说明
# 使用命令行参数设置模式(推荐)
ros2 run examples set_mc_action JD # 零力矩模式>>站姿预备(位控站立)
ros2 run examples set_mc_action SD # 机器人已立起且脚着地后方可执行,站姿预备>>稳定站立
# 稳定站立>>走跑模式 自动切换,无需手动切换
# 或者不带参数运行,程序会提示用户输入双字母缩写代码
ros2 run examples set_mc_action
输出示例
...
[INFO] [1764066567.502968540] [set_mc_action_client]: ✅ Robot mode set successfully.
注意事项
切入
STAND_DEFAULT模式前,确保机器人脚已经着地模式切换可能需要几秒钟时间完成
接口参考
服务:
/aimdk_5Fmsgs/srv/SetMcAction消息:
aimdk_msgs/srv/SetMcAction
6.2.4 设置机器人动作
该示例中用到了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 g_node.reset();
20 }
21 rclcpp::shutdown();
22 exit(signal);
23}
24
25class PresetMotionClient : public rclcpp::Node {
26public:
27 PresetMotionClient() : Node("preset_motion_client") {
28 const std::chrono::seconds timeout(8);
29
30 client_ = this->create_client<aimdk_msgs::srv::SetMcPresetMotion>(
31 "/aimdk_5Fmsgs/srv/SetMcPresetMotion");
32
33 RCLCPP_INFO(this->get_logger(), "✅ SetMcPresetMotion client node created.");
34
35 // Wait for the service to become available
36 while (!client_->wait_for_service(std::chrono::seconds(2))) {
37 RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
38 }
39 RCLCPP_INFO(this->get_logger(),
40 "🟢 Service available, ready to send request.");
41 }
42
43 bool send_request(int area_id, int motion_id) {
44 try {
45 auto request =
46 std::make_shared<aimdk_msgs::srv::SetMcPresetMotion::Request>();
47 request->header = aimdk_msgs::msg::RequestHeader();
48
49 aimdk_msgs::msg::McPresetMotion motion;
50 aimdk_msgs::msg::McControlArea area;
51
52 motion.value = motion_id; // Preset motion ID
53 area.value = area_id; // Control area ID
54 request->motion = motion;
55 request->area = area;
56 request->interrupt = false; // Not interrupt current motion
57
58 RCLCPP_INFO(this->get_logger(),
59 "📨 Sending request to set preset motion: motion=%d, area=%d",
60 motion_id, area_id);
61
62 const std::chrono::milliseconds timeout(250);
63 for (int i = 0; i < 8; i++) {
64 request->header.stamp = this->now();
65 auto future = client_->async_send_request(request);
66 auto retcode = rclcpp::spin_until_future_complete(shared_from_this(),
67 future, timeout);
68 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
69 // retry as remote peer is NOT handled well by ROS
70 RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
71 continue;
72 }
73 // future.done
74 auto response = future.get();
75 if (response->response.header.code == 0) {
76 RCLCPP_INFO(this->get_logger(),
77 "✅ Preset motion set successfully: %lu",
78 response->response.task_id);
79 return true;
80 } else if (response->response.state.value ==
81 aimdk_msgs::msg::CommonState::RUNNING) {
82 RCLCPP_INFO(this->get_logger(), "⏳ Preset motion executing: %lu",
83 response->response.task_id);
84 return true;
85 } else {
86 RCLCPP_WARN(this->get_logger(), "❌ Failed to set preset motion: %lu",
87 response->response.task_id);
88 return false;
89 }
90 }
91 RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
92 return false;
93 } catch (const std::exception &e) {
94 RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
95 return false;
96 }
97 }
98
99private:
100 rclcpp::Client<aimdk_msgs::srv::SetMcPresetMotion>::SharedPtr client_;
101};
102
103int main(int argc, char *argv[]) {
104 try {
105 rclcpp::init(argc, argv);
106 signal(SIGINT, signal_handler);
107 signal(SIGTERM, signal_handler);
108
109 g_node = std::make_shared<PresetMotionClient>();
110 // Cast g_node (std::shared_ptr<rclcpp::Node>) to a derived
111 // PresetMotionClient pointer (std::shared_ptr<PresetMotionClient>)
112 auto client = std::dynamic_pointer_cast<PresetMotionClient>(g_node);
113
114 int area = 1;
115 int motion = 1003;
116 std::cout << "Enter arm area ID (1-left, 2-right): ";
117 std::cin >> area;
118 std::cout
119 << "Enter preset motion ID (1001-raise, 1002-wave, 1003-handshake, "
120 "1004-airkiss): ";
121 std::cin >> motion;
122 if (client) {
123 client->send_request(area, motion);
124 }
125
126 // Clean up resources
127 g_node.reset();
128 rclcpp::shutdown();
129
130 return 0;
131 } catch (const std::exception &e) {
132 RCLCPP_ERROR(rclcpp::get_logger("main"),
133 "Program exited with exception: %s", e.what());
134 return 1;
135 }
136}
6.2.5 夹爪控制
该示例中用到了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 "Switched to the next parameter group, index=%zu (left=%.2f, "
39 "right=%.2f)",
40 current_index_, position_pairs_[current_index_].first,
41 position_pairs_[current_index_].second);
42 }
43
44 auto msg = std::make_unique<aimdk_msgs::msg::HandCommandArray>();
45 msg->header = aimdk_msgs::msg::MessageHeader();
46
47 float left_position = position_pairs_[current_index_].first;
48 float right_position = position_pairs_[current_index_].second;
49
50 aimdk_msgs::msg::HandCommand left_hands;
51 left_hands.name = "left_hand";
52 left_hands.position = left_position;
53 left_hands.velocity = 1.0;
54 left_hands.acceleration = 1.0;
55 left_hands.deceleration = 1.0;
56 left_hands.effort = 1.0;
57
58 aimdk_msgs::msg::HandCommand right_hands;
59 right_hands.name = "right_hand";
60 right_hands.position = right_position;
61 right_hands.velocity = 1.0;
62 right_hands.acceleration = 1.0;
63 right_hands.deceleration = 1.0;
64 right_hands.effort = 1.0;
65
66 msg->left_hands.push_back(left_hands);
67 msg->right_hands.push_back(right_hands);
68 msg->left_hand_type.value = 2;
69 msg->right_hand_type.value = 2;
70
71 publisher_->publish(std::move(msg));
72 }
73
74private:
75 rclcpp::Publisher<aimdk_msgs::msg::HandCommandArray>::SharedPtr publisher_;
76 rclcpp::TimerBase::SharedPtr timer_;
77
78 std::vector<std::pair<float, float>> position_pairs_;
79 size_t current_index_;
80
81 rclcpp::Time last_switch_time_;
82};
83
84int main(int argc, char *argv[]) {
85 rclcpp::init(argc, argv);
86 auto hand_control_node = std::make_shared<HandControl>();
87 rclcpp::spin(hand_control_node);
88 rclcpp::shutdown();
89 return 0;
90}
6.2.6 灵巧手控制
该示例中用到了omnihand_control,通过往话题/aima/hal/joint/hand/command发布消息来控制灵巧手的运动
注意
注意⚠️ :运行本示例前需要在机器人运控计算单元(PC1)使用aima em stop-app mc关闭原生运控模块,获取机器人控制权。注意确保机器人安全
1#include <aimdk_msgs/msg/hand_command_array.hpp>
2#include <chrono>
3#include <cmath>
4#include <memory>
5#include <rclcpp/rclcpp.hpp>
6
7using namespace std::chrono_literals;
8
9class HandCommandPublisher : public rclcpp::Node {
10public:
11 HandCommandPublisher() : Node("hand_command_publisher") {
12 publisher_ = this->create_publisher<aimdk_msgs::msg::HandCommandArray>(
13 "/aima/hal/joint/hand/command", 10);
14
15 // Create a timer to publish once per second
16 timer_ = this->create_wall_timer(
17 1s, std::bind(&HandCommandPublisher::publish_command, this));
18 }
19
20private:
21 void publish_command() {
22 auto message = aimdk_msgs::msg::HandCommandArray();
23
24 // Set hander
25 message.header.stamp = this->now();
26 message.header.frame_id = "hand_command";
27
28 // Set the hand type
29 message.left_hand_type.value = 1; // NIMBLE_HANDS
30 message.right_hand_type.value = 1; // NIMBLE_HANDS
31
32 // Create left hand command array
33 message.left_hands.resize(10);
34
35 // Set left thumb
36 message.left_hands[0].name = "left_thumb";
37 message.left_hands[0].position = 0.0;
38 message.left_hands[0].velocity = 0.1;
39 message.left_hands[0].acceleration = 0.0;
40 message.left_hands[0].deceleration = 0.0;
41 message.left_hands[0].effort = 0.0;
42 // Set other left fingers
43 for (int i = 1; i < 10; i++) {
44 message.left_hands[i].name = "left_index";
45 message.left_hands[i].position = 0.0;
46 message.left_hands[i].velocity = 0.1;
47 message.left_hands[i].acceleration = 0.0;
48 message.left_hands[i].deceleration = 0.0;
49 message.left_hands[i].effort = 0.0;
50 }
51
52 // Create right hand command array
53 message.right_hands.resize(10);
54
55 // Set right thumb
56 message.right_hands[0].name = "right_thumb";
57 message.right_hands[0].position = 0.0;
58 message.right_hands[0].velocity = 0.1;
59 message.right_hands[0].acceleration = 0.0;
60 message.right_hands[0].deceleration = 0.0;
61 message.right_hands[0].effort = 0.0;
62
63 // Set other right fingers (pinky)
64 for (int i = 1; i < 10; i++) {
65 message.right_hands[i].name = "right_pinky";
66 message.right_hands[i].position = 0.0;
67 message.right_hands[i].velocity = 0.1;
68 message.right_hands[i].acceleration = 0.0;
69 message.right_hands[i].deceleration = 0.0;
70 message.right_hands[i].effort = 0.0;
71 }
72
73 if (target_finger <= 10) {
74 message.right_hands[target_finger].position = 0.8;
75 } else {
76 int target_finger_ = target_finger - 10;
77 double target_position = 0.8;
78 if (target_finger_ < 3) {
79 // The three thumb motors on the left hand need their signs inverted to
80 // mirror the right hand's motion
81 target_position = -target_position;
82 }
83 message.left_hands[target_finger_].position = target_position;
84 }
85
86 // Publish the message
87 publisher_->publish(message);
88
89 RCLCPP_INFO(this->get_logger(),
90 "Published hand command with target_finger: %d", target_finger);
91
92 update_target_finger();
93 }
94
95 void update_target_finger() {
96 if (increasing_) {
97 target_finger += step_;
98 if (target_finger >= 19) {
99 target_finger = 19;
100 increasing_ = false;
101 }
102 } else {
103 target_finger -= step_;
104 if (target_finger <= 0) {
105 target_finger = 0;
106 increasing_ = true;
107 }
108 }
109 }
110
111 rclcpp::Publisher<aimdk_msgs::msg::HandCommandArray>::SharedPtr publisher_;
112 rclcpp::TimerBase::SharedPtr timer_;
113
114 int target_finger = 0;
115 int step_ = 1;
116 bool increasing_ = true;
117};
118
119int main(int argc, char **argv) {
120 rclcpp::init(argc, argv);
121 auto node = std::make_shared<HandCommandPublisher>();
122 rclcpp::spin(node);
123 rclcpp::shutdown();
124 return 0;
125}
6.2.7 灵巧手触觉数据(T2.1支持)
该示例中用到了 omnihand_touch,通过订阅话题/aima/hal/joint/hand/state来接收灵巧手指尖压力传感器的数据
注意
注意⚠️ :运行本示例前需要在机器人运控计算单元(PC1)使用aima em stop-app mc关闭原生运控模块,获取机器人控制权。注意确保机器人安全
1//
2// Created by agiuser on 2026/1/27.
3//
4#include <aimdk_msgs/msg/hand_command_array.hpp>
5#include <aimdk_msgs/msg/hand_state_array.hpp>
6#include <chrono>
7#include <iomanip>
8#include <iostream>
9#include <rclcpp/rclcpp.hpp>
10
11using namespace std::chrono_literals;
12
13class HandStateSubscriber : public rclcpp::Node {
14public:
15 HandStateSubscriber() : Node("hand_state_subscriber") {
16 publisher_ = this->create_publisher<aimdk_msgs::msg::HandCommandArray>(
17 "/aima/hal/joint/hand/command", 10);
18 rclcpp::QoS qos_profile(rclcpp::KeepLast(10));
19 qos_profile.best_effort(); // Set reliability to BEST_EFFORT
20 // Create subscriber
21 subscription_ = this->create_subscription<aimdk_msgs::msg::HandStateArray>(
22 "/aima/hal/joint/hand/state", qos_profile,
23 std::bind(&HandStateSubscriber::topic_callback, this,
24 std::placeholders::_1));
25 // Create a timer to publish once per second
26 timer_ = this->create_wall_timer(
27 1s, std::bind(&HandStateSubscriber::publish_command, this));
28 RCLCPP_INFO(
29 this->get_logger(),
30 "Subscriber started, listening to /aima/hal/joint/hand/state topic...");
31 }
32
33private:
34 void topic_callback(const aimdk_msgs::msg::HandStateArray::SharedPtr msg) {
35 // Print message header information
36 RCLCPP_INFO(this->get_logger(),
37 "Message received - Sequence: %u, Timestamp: %d.%09d",
38 msg->header.sequence, msg->header.stamp.sec,
39 msg->header.stamp.nanosec);
40
41 // Print left hand touch sensor data
42 print_touch_sensor_data("Left Hand", msg->left_touch_sensors);
43
44 // Print right hand touch sensor data
45 print_touch_sensor_data("Right Hand", msg->right_touch_sensors);
46
47 std::cout << std::endl;
48 }
49
50 /**
51 * @brief Print touch sensor data for a specific hand
52 * @param hand_name Name of the hand (Left Hand/Right Hand)
53 * @param sensor_data Touch sensor data structure
54 */
55 void print_touch_sensor_data(
56 const std::string &hand_name,
57 const aimdk_msgs::msg::HandTouchSensorData &sensor_data) {
58 std::cout << "=== " << hand_name << " Touch Sensor Data ===" << std::endl;
59
60 // Print palm touch data
61 std::cout << "Palm Touch Data (36 elements): ";
62 print_array(sensor_data.palm_touch_data);
63
64 // Print back of hand touch data
65 std::cout << "Back of Hand Touch Data (36 elements): ";
66 print_array(sensor_data.back_of_hand_touch_data);
67
68 // Print finger touch data
69 std::cout << "Thumb Touch Data (16 elements): ";
70 print_array(sensor_data.thumb_touch_data);
71
72 std::cout << "Index Finger Touch Data (16 elements): ";
73 print_array(sensor_data.index_finger_touch_data);
74
75 std::cout << "Middle Finger Touch Data (16 elements): ";
76 print_array(sensor_data.middle_finger_touch_data);
77
78 std::cout << "Ring Finger Touch Data (16 elements): ";
79 print_array(sensor_data.ring_finger_touch_data);
80
81 std::cout << "Little Finger Touch Data (16 elements): ";
82 print_array(sensor_data.little_finger_touch_data);
83 }
84
85 /**
86 * @brief Print array of 25 uint8_t elements
87 * @param arr Array to print
88 */
89 void print_array(const std::array<uint8_t, 36> &arr) {
90 std::cout << "[";
91 for (size_t i = 0; i < arr.size(); ++i) {
92 std::cout << std::setw(3) << static_cast<int>(arr[i]);
93 if (i < arr.size() - 1)
94 std::cout << " ";
95 }
96 std::cout << "]" << std::endl;
97 }
98
99 /**
100 * @brief Print array of 16 uint8_t elements
101 * @param arr Array to print
102 */
103 void print_array(const std::array<uint8_t, 16> &arr) {
104 std::cout << "[";
105 for (size_t i = 0; i < arr.size(); ++i) {
106 std::cout << std::setw(3) << static_cast<int>(arr[i]);
107 if (i < arr.size() - 1)
108 std::cout << " ";
109 }
110 std::cout << "]" << std::endl;
111 }
112
113 void publish_command() {
114 auto message = aimdk_msgs::msg::HandCommandArray();
115
116 // Set hander
117 message.header.stamp = this->now();
118 message.header.frame_id = "hand_command";
119
120 // Set the hand type
121 message.left_hand_type.value = 1; // NIMBLE_HANDS
122 message.right_hand_type.value = 1; // NIMBLE_HANDS
123
124 // Create left hand command array
125 message.left_hands.resize(10);
126
127 // Set left thumb
128 message.left_hands[0].name = "left_thumb";
129 message.left_hands[0].position = 0.0;
130 message.left_hands[0].velocity = 0.1;
131 message.left_hands[0].acceleration = 0.0;
132 message.left_hands[0].deceleration = 0.0;
133 message.left_hands[0].effort = 0.0;
134 // Set other left fingers
135 for (int i = 1; i < 10; i++) {
136 message.left_hands[i].name = "left_index";
137 message.left_hands[i].position = 0.0;
138 message.left_hands[i].velocity = 0.1;
139 message.left_hands[i].acceleration = 0.0;
140 message.left_hands[i].deceleration = 0.0;
141 message.left_hands[i].effort = 0.0;
142 }
143
144 // Create right hand command array
145 message.right_hands.resize(10);
146
147 // Set right thumb
148 message.right_hands[0].name = "right_thumb";
149 message.right_hands[0].position = 0.0;
150 message.right_hands[0].velocity = 0.1;
151 message.right_hands[0].acceleration = 0.0;
152 message.right_hands[0].deceleration = 0.0;
153 message.right_hands[0].effort = 0.0;
154
155 // Set other right fingers (pinky)
156 for (int i = 1; i < 10; i++) {
157 message.right_hands[i].name = "right_pinky";
158 message.right_hands[i].position = 0.0;
159 message.right_hands[i].velocity = 0.1;
160 message.right_hands[i].acceleration = 0.0;
161 message.right_hands[i].deceleration = 0.0;
162 message.right_hands[i].effort = 0.0;
163 }
164
165 // Publish the message
166 publisher_->publish(message);
167
168 RCLCPP_INFO(this->get_logger(), "Published hand command");
169 }
170
171 rclcpp::Publisher<aimdk_msgs::msg::HandCommandArray>::SharedPtr publisher_;
172 rclcpp::Subscription<aimdk_msgs::msg::HandStateArray>::SharedPtr
173 subscription_;
174 rclcpp::TimerBase::SharedPtr timer_;
175};
176
177int main(int argc, char *argv[]) {
178 rclcpp::init(argc, argv);
179 auto node = std::make_shared<HandStateSubscriber>();
180 rclcpp::spin(node);
181 rclcpp::shutdown();
182 return 0;
183}
压力传感器位于五个手指的指尖、手心、手背。需要给灵巧手发送至少一个运动指令,才能获得对应的压力数据上报。
6.2.8 注册二开输入源
对于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 g_node.reset();
19 }
20 rclcpp::shutdown();
21 exit(signal);
22}
23
24class McInputClient : public rclcpp::Node {
25public:
26 McInputClient() : Node("set_mc_input_source_client") {
27 client_ = this->create_client<aimdk_msgs::srv::SetMcInputSource>(
28 "/aimdk_5Fmsgs/srv/SetMcInputSource");
29
30 RCLCPP_INFO(this->get_logger(), "✅ SetMcInputSource client node created.");
31
32 // Wait for the service to become available
33 while (!client_->wait_for_service(std::chrono::seconds(2))) {
34 RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
35 }
36 RCLCPP_INFO(this->get_logger(),
37 "🟢 Service available, ready to send request.");
38 }
39
40 bool send_request() {
41 try {
42 auto request =
43 std::make_shared<aimdk_msgs::srv::SetMcInputSource::Request>();
44
45 // Set request data
46 request->action.value = 1001; // Add new input source
47 request->input_source.name = "node"; // Set message source
48 request->input_source.priority = 40; // Set priority
49 request->input_source.timeout = 1000; // Set timeout (ms)
50
51 RCLCPP_INFO(this->get_logger(), "📨 Sending input source request: (ID=%d)",
52 request->action.value);
53
54 auto timeout = std::chrono::milliseconds(250);
55 for (int i = 0; i < 8; i++) {
56 // Set header timestamp
57 request->request.header.stamp = this->now(); // use Node::now()
58 auto future = client_->async_send_request(request);
59 auto retcode = rclcpp::spin_until_future_complete(
60 this->shared_from_this(), future, timeout);
61 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
62 // retry as remote peer is NOT handled well by ROS
63 RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
64 continue;
65 }
66 // future.done
67 auto response = future.get();
68 auto code = response->response.header.code;
69 if (code == 0) {
70 RCLCPP_INFO(this->get_logger(),
71 "✅ Input source set successfully. task_id=%lu",
72 response->response.task_id);
73 return true;
74 } else {
75 RCLCPP_ERROR(
76 this->get_logger(),
77 "❌ Input source set failed. ret_code=%ld, task_id=%lu "
78 "(duplicated ADD? or MODIFY/ENABLE/DISABLE for unknown source?)",
79 code, response->response.task_id);
80 return false;
81 }
82 }
83 RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
84 return false;
85 } catch (const std::exception &e) {
86 RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
87 return false;
88 }
89 }
90
91private:
92 rclcpp::Client<aimdk_msgs::srv::SetMcInputSource>::SharedPtr client_;
93};
94
95int main(int argc, char *argv[]) {
96 try {
97 rclcpp::init(argc, argv);
98 signal(SIGINT, signal_handler);
99 signal(SIGTERM, signal_handler);
100
101 g_node = std::make_shared<McInputClient>();
102 auto client = std::dynamic_pointer_cast<McInputClient>(g_node);
103
104 if (client) {
105 client->send_request();
106 }
107
108 g_node.reset();
109 rclcpp::shutdown();
110
111 return 0;
112 } catch (const std::exception &e) {
113 RCLCPP_ERROR(rclcpp::get_logger("main"),
114 "Program terminated with exception: %s", e.what());
115 return 1;
116 }
117}
6.2.9 获取当前输入源
该示例中用到了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 g_node.reset();
18 }
19 rclcpp::shutdown();
20 exit(signal);
21}
22
23// Client Class
24class GetCurrentInputSourceClient : public rclcpp::Node {
25public:
26 GetCurrentInputSourceClient() : Node("get_current_input_source_client") {
27
28 client_ = this->create_client<aimdk_msgs::srv::GetCurrentInputSource>(
29 "/aimdk_5Fmsgs/srv/GetCurrentInputSource");
30
31 RCLCPP_INFO(this->get_logger(),
32 "✅ GetCurrentInputSource client node created.");
33
34 // Wait for the service to become available
35 while (!client_->wait_for_service(std::chrono::seconds(2))) {
36 RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
37 }
38 RCLCPP_INFO(this->get_logger(),
39 "🟢 Service available, ready to send request.");
40 }
41
42 void send_request() {
43 try {
44 auto request =
45 std::make_shared<aimdk_msgs::srv::GetCurrentInputSource::Request>();
46 request->request = aimdk_msgs::msg::CommonRequest();
47
48 RCLCPP_INFO(this->get_logger(),
49 "📨 Sending request to get current input source");
50
51 auto timeout = std::chrono::milliseconds(250);
52
53 for (int i = 0; i < 8; i++) {
54 request->request.header.stamp = this->now();
55 auto future = client_->async_send_request(request);
56 auto retcode = rclcpp::spin_until_future_complete(
57 this->shared_from_this(), future, timeout);
58 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
59 // retry as remote peer is NOT handled well by ROS
60 RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
61 continue;
62 }
63 // future.done
64 auto response = future.get();
65 if (response->response.header.code == 0) {
66 RCLCPP_INFO(this->get_logger(),
67 "✅ Current input source get successfully:");
68 RCLCPP_INFO(this->get_logger(), "Name: %s",
69 response->input_source.name.c_str());
70 RCLCPP_INFO(this->get_logger(), "Priority: %d",
71 response->input_source.priority);
72 RCLCPP_INFO(this->get_logger(), "Timeout: %d",
73 response->input_source.timeout);
74 } else {
75 RCLCPP_WARN(this->get_logger(),
76 "❌ Current input source get failed, return code: %ld",
77 response->response.header.code);
78 }
79 return;
80 }
81 RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
82 } catch (const std::exception &e) {
83 RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
84 }
85 }
86
87private:
88 rclcpp::Client<aimdk_msgs::srv::GetCurrentInputSource>::SharedPtr client_;
89};
90
91int main(int argc, char *argv[]) {
92 try {
93 rclcpp::init(argc, argv);
94
95 signal(SIGINT, signal_handler);
96 signal(SIGTERM, signal_handler);
97
98 g_node = std::make_shared<GetCurrentInputSourceClient>();
99 auto client =
100 std::dynamic_pointer_cast<GetCurrentInputSourceClient>(g_node);
101
102 if (client) {
103 client->send_request();
104 }
105
106 g_node.reset();
107 rclcpp::shutdown();
108 return 0;
109 } catch (const std::exception &e) {
110 RCLCPP_ERROR(rclcpp::get_logger("main"),
111 "Program exited with exception: %s", e.what());
112 return 1;
113 }
114}
使用说明
# 获取当前输入源信息
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.10 机器人走跑控制
该示例中用到了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_ = 1.0; // 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 is NOT handled well by ROS
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 exit(sig);
184}
185
186int main(int argc, char *argv[]) {
187 rclcpp::init(argc, argv);
188 signal(SIGINT, signal_handler);
189 signal(SIGTERM, signal_handler);
190
191 global_node = std::make_shared<DirectVelocityControl>();
192 auto node = global_node;
193
194 if (!node->register_input_source()) {
195 RCLCPP_ERROR(node->get_logger(),
196 "Input source registration failed, exiting");
197 global_node.reset();
198 rclcpp::shutdown();
199 return 1;
200 }
201
202 // get and check control values
203 // notice that mc has thresholds to start movement
204 double forward, lateral, angular;
205 std::cout << "Enter forward speed 0 or ±(0.2 ~ 1.0) m/s: ";
206 std::cin >> forward;
207 if (!node->set_forward(forward)) {
208 return 2;
209 }
210 std::cout << "Enter lateral speed 0 or ±(0.2 ~ 1.0) m/s: ";
211 std::cin >> lateral;
212 if (!node->set_lateral(lateral)) {
213 return 2;
214 }
215 std::cout << "Enter angular speed 0 or ±(0.1 ~ 1.0) rad/s: ";
216 std::cin >> angular;
217 if (!node->set_angular(angular)) {
218 return 2;
219 }
220
221 RCLCPP_INFO(node->get_logger(), "Setting velocity; moving for 5 seconds");
222
223 node->start_publish();
224
225 auto start_time = node->now();
226 while ((node->now() - start_time).seconds() < 5.0) {
227 rclcpp::spin_some(node);
228 std::this_thread::sleep_for(std::chrono::milliseconds(1));
229 }
230
231 node->clear_velocity();
232 RCLCPP_INFO(node->get_logger(), "5 seconds elapsed; robot stopped");
233
234 rclcpp::spin(node);
235 rclcpp::shutdown();
236 return 0;
237}
6.2.11 关节电机控制
本示例展示了如何使用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.704, 2.556, 40.0, 4.0},
58 {"left_hip_roll_joint", -0.235, 2.906, 40.0, 4.0},
59 {"left_hip_yaw_joint", -1.684, 3.430, 30.0, 3.0},
60 {"left_knee_joint", 0.0000, 2.4073, 80.0, 8.0},
61 {"left_ankle_pitch_joint", -0.803, 0.453, 40.0, 4.0},
62 {"left_ankle_roll_joint", -0.2625, 0.2625, 20.0, 2.0},
63 // Right leg joint configuration
64 {"right_hip_pitch_joint", -2.704, 2.556, 40.0, 4.0},
65 {"right_hip_roll_joint", -2.906, 0.235, 40.0, 4.0},
66 {"right_hip_yaw_joint", -3.430, 1.684, 30.0, 3.0},
67 {"right_knee_joint", 0.0000, 2.4073, 80.0, 8.0},
68 {"right_ankle_pitch_joint", -0.803, 0.453, 40.0, 4.0},
69 {"right_ankle_roll_joint", -0.2625, 0.2625, 20.0, 2.0},
70 }},
71
72 {JointArea::WAIST,
73 {
74 // Waist joint configuration
75 {"waist_yaw_joint", -3.43, 2.382, 20.0, 4.0},
76 {"waist_pitch_joint", -0.314, 0.314, 20.0, 4.0},
77 {"waist_roll_joint", -0.488, 0.488, 20.0, 4.0},
78 }},
79 {JointArea::ARM,
80 {
81 // Left arm joint configuration
82 {"left_shoulder_pitch_joint", -3.08, 2.04, 20.0, 2.0},
83 {"left_shoulder_roll_joint", -0.061, 2.993, 20.0, 2.0},
84 {"left_shoulder_yaw_joint", -2.556, 2.556, 20.0, 2.0},
85 {"left_elbow_joint", -2.3556, 0.0, 20.0, 2.0},
86 {"left_wrist_yaw_joint", -2.556, 2.556, 20.0, 2.0},
87 {"left_wrist_pitch_joint", -0.558, 0.558, 20.0, 2.0},
88 {"left_wrist_roll_joint", -1.571, 0.724, 20.0, 2.0},
89 // Right arm joint configuration
90 {"right_shoulder_pitch_joint", -3.08, 2.04, 20.0, 2.0},
91 {"right_shoulder_roll_joint", -2.993, 0.061, 20.0, 2.0},
92 {"right_shoulder_yaw_joint", -2.556, 2.556, 20.0, 2.0},
93 {"right_elbow_joint", -2.3556, 0.0000, 20.0, 2.0},
94 {"right_wrist_yaw_joint", -2.556, 2.556, 20.0, 2.0},
95 {"right_wrist_pitch_joint", -0.558, 0.558, 20.0, 2.0},
96 {"right_wrist_roll_joint", -0.724, 1.571, 20.0, 2.0},
97 }},
98 {JointArea::HEAD,
99 {
100 // Head joint configuration
101 {"head_yaw_joint", -0.366, 0.366, 20.0, 2.0},
102 {"head_pitch_joint", -0.3838, 0.3838, 20.0, 2.0},
103 }},
104};
105
106/**
107 * @brief Joint controller node class
108 * @tparam DOFs Degrees of freedom
109 * @tparam Area Joint area
110 */
111template <int DOFs, JointArea Area>
112class JointControllerNode : public rclcpp::Node {
113public:
114 /**
115 * @brief Constructor
116 * @param node_name Node name
117 * @param sub_topic Subscription topic name
118 * @param pub_topic Publication topic name
119 * @param qos QoS configuration
120 */
121 JointControllerNode(std::string node_name, std::string sub_topic,
122 std::string pub_topic,
123 rclcpp::QoS qos = rclcpp::SensorDataQoS())
124 : Node(node_name), ruckig(0.002) {
125 joint_info_ = robot_model[Area];
126 if (joint_info_.size() != DOFs) {
127 RCLCPP_ERROR(this->get_logger(), "Joint count mismatch.");
128 exit(1);
129 }
130
131 // Set motion constraints for Ruckig trajectory planner
132 for (int i = 0; i < DOFs; ++i) {
133 input.max_velocity[i] = 1.0; // Max velocity limit
134 input.max_acceleration[i] = 1.0; // Max acceleration limit
135 input.max_jerk[i] = 25.0; // Max jerk (change of acceleration) limit
136 }
137
138 // Create joint state subscriber
139 sub_ = this->create_subscription<aimdk_msgs::msg::JointStateArray>(
140 sub_topic, qos,
141 std::bind(&JointControllerNode::JointStateCallback, this,
142 std::placeholders::_1));
143
144 // Create joint command publisher
145 pub_ = this->create_publisher<aimdk_msgs::msg::JointCommandArray>(pub_topic,
146 qos);
147 }
148
149private:
150 // Ruckig trajectory planner variables
151 ruckig::Ruckig<DOFs> ruckig; // Trajectory planner instance
152 ruckig::InputParameter<DOFs> input; // Input parameters
153 ruckig::OutputParameter<DOFs> output; // Output parameters
154 bool ruckig_initialized_ = false; // Trajectory planner initialization flag
155 std::vector<JointInfo> joint_info_; // Joint information list
156
157 // ROS communication variables
158 rclcpp::Subscription<aimdk_msgs::msg::JointStateArray>::SharedPtr
159 sub_; // State subscriber
160 rclcpp::Publisher<aimdk_msgs::msg::JointCommandArray>::SharedPtr
161 pub_; // Command publisher
162
163 /**
164 * @brief Joint state callback function
165 * @param msg Joint state message
166 */
167 void
168 JointStateCallback(const aimdk_msgs::msg::JointStateArray::SharedPtr msg) {
169 // Initialize trajectory planner on first state reception
170 if (!ruckig_initialized_) {
171 for (int i = 0; i < DOFs; ++i) {
172 input.current_position[i] = msg->joints[i].position;
173 input.current_velocity[i] = msg->joints[i].velocity;
174 input.current_acceleration[i] = 0.0;
175 }
176 ruckig_initialized_ = true;
177 RCLCPP_INFO(this->get_logger(),
178 "Ruckig trajectory planner initialization complete");
179 }
180 }
181
182public:
183 /**
184 * @brief Set target joint position
185 * @param joint_name Joint name
186 * @param target_position Target position
187 * @return Whether the target position was successfully set
188 */
189 bool SetTargetPosition(std::string joint_name, double target_position) {
190 if (!ruckig_initialized_) {
191 RCLCPP_WARN(this->get_logger(),
192 "Ruckig trajectory planner not initialized");
193 return false;
194 }
195
196 // Find target joint and set its position
197 int target_joint = -1;
198 for (int i = 0; i < DOFs; ++i) {
199 if (joint_info_[i].name == joint_name) {
200 // Check if target position is within limits
201 if (target_position < joint_info_[i].lower_limit ||
202 target_position > joint_info_[i].upper_limit) {
203 RCLCPP_ERROR(
204 this->get_logger(),
205 "Target position %.3f exceeds limit for joint %s [%.3f, %.3f]",
206 target_position, joint_name.c_str(), joint_info_[i].lower_limit,
207 joint_info_[i].upper_limit);
208 return false;
209 }
210 input.target_position[i] = target_position;
211 input.target_velocity[i] = 0.0;
212 input.target_acceleration[i] = 0.0;
213 target_joint = i;
214 } else {
215 input.target_position[i] = input.current_position[i];
216 input.target_velocity[i] = 0.0;
217 input.target_acceleration[i] = 0.0;
218 }
219 }
220
221 if (target_joint == -1) {
222 RCLCPP_ERROR(this->get_logger(), "Joint %s not found",
223 joint_name.c_str());
224 return false;
225 }
226
227 // Perform trajectory planning and send command using Ruckig
228 const double tolerance = 1e-6;
229 while (g_running && rclcpp::ok() && !g_emergency_stop) {
230 auto result = ruckig.update(input, output);
231 if (result != ruckig::Result::Working &&
232 result != ruckig::Result::Finished) {
233 RCLCPP_WARN(this->get_logger(), "Trajectory planning failed");
234 break;
235 }
236
237 // Update current state
238 for (int i = 0; i < DOFs; ++i) {
239 input.current_position[i] = output.new_position[i];
240 input.current_velocity[i] = output.new_velocity[i];
241 input.current_acceleration[i] = output.new_acceleration[i];
242 }
243
244 // Check if target position is reached
245 if (std::abs(output.new_position[target_joint] - target_position) <
246 tolerance) {
247 RCLCPP_INFO(this->get_logger(), "Joint %s reached target position",
248 joint_name.c_str());
249 break;
250 }
251
252 // Create and send joint command
253 aimdk_msgs::msg::JointCommandArray cmd;
254 cmd.joints.resize(DOFs);
255 for (int i = 0; i < DOFs; ++i) {
256 auto &joint = joint_info_[i];
257 cmd.joints[i].name = joint.name;
258 cmd.joints[i].position = output.new_position[i];
259 cmd.joints[i].velocity = output.new_velocity[i];
260 cmd.joints[i].stiffness = joint.kp;
261 cmd.joints[i].damping = joint.kd;
262 }
263 pub_->publish(cmd);
264
265 // Short delay to avoid excessive CPU usage
266 std::this_thread::sleep_for(std::chrono::milliseconds(2));
267 }
268
269 return true;
270 }
271
272 /**
273 * @brief Safely stop all joints
274 */
275 void safe_stop() {
276 if (!ruckig_initialized_) {
277 RCLCPP_WARN(this->get_logger(), "Ruckig trajectory planner not "
278 "initialized, cannot perform safe stop");
279 return;
280 }
281
282 RCLCPP_INFO(this->get_logger(), "Performing safe stop...");
283
284 // Set all joint target positions to current positions
285 for (int i = 0; i < DOFs; ++i) {
286 input.target_position[i] = input.current_position[i];
287 input.target_velocity[i] = 0.0;
288 input.target_acceleration[i] = 0.0;
289 }
290
291 // Send final command to ensure joints stop
292 aimdk_msgs::msg::JointCommandArray cmd;
293 cmd.joints.resize(DOFs);
294 for (int i = 0; i < DOFs; ++i) {
295 auto &joint = joint_info_[i];
296 cmd.joints[i].name = joint.name;
297 cmd.joints[i].position = input.current_position[i];
298 cmd.joints[i].velocity = 0.0;
299 cmd.joints[i].stiffness = joint.kp;
300 cmd.joints[i].damping = joint.kd;
301 }
302 pub_->publish(cmd);
303
304 RCLCPP_INFO(this->get_logger(), "Safe stop complete");
305 }
306
307 /**
308 * @brief Emergency stop for all joints
309 */
310 void emergency_stop() {
311 g_emergency_stop = true;
312 safe_stop();
313 RCLCPP_ERROR(this->get_logger(), "Emergency stop triggered");
314 }
315};
316
317/**
318 * @brief Main function
319 */
320int main(int argc, char *argv[]) {
321 rclcpp::init(argc, argv);
322
323 // Set up signal handling
324 signal(SIGINT, signal_handler);
325 signal(SIGTERM, signal_handler);
326
327 try {
328 // Create leg controller node
329 auto leg_node = std::make_shared<JointControllerNode<12, JointArea::LEG>>(
330 "leg_node", "/aima/hal/joint/leg/state", "/aima/hal/joint/leg/command");
331
332 // Create timer node
333 rclcpp::Node::SharedPtr timer_node =
334 rclcpp::Node::make_shared("timer_node");
335 double position = 0.8;
336
337 // Create timer callback function
338 auto timer = timer_node->create_wall_timer(std::chrono::seconds(3), [&]() {
339 if (!g_running || g_emergency_stop)
340 return; // If the program is shutting down or emergency stopped, do not
341 // execute new actions
342 position = -position;
343 position = 1.3 + position;
344 if (!leg_node->SetTargetPosition("left_knee_joint", position)) {
345 RCLCPP_ERROR(rclcpp::get_logger("main"),
346 "Failed to set target position");
347 }
348 });
349
350 // Create executor
351 rclcpp::executors::MultiThreadedExecutor executor;
352 executor.add_node(leg_node);
353 executor.add_node(timer_node);
354
355 // Main loop
356 while (g_running && rclcpp::ok() && !g_emergency_stop) {
357 executor.spin_once(std::chrono::milliseconds(100));
358 }
359
360 // Safely stop all joints
361 RCLCPP_INFO(rclcpp::get_logger("main"), "Safely stopping all joints...");
362 leg_node->safe_stop();
363
364 // Wait a short time to ensure command transmission is complete
365 std::this_thread::sleep_for(std::chrono::milliseconds(100));
366
367 // Clean up resources
368 RCLCPP_INFO(rclcpp::get_logger("main"), "Cleaning up resources...");
369 leg_node.reset();
370 timer_node.reset();
371
372 } catch (const std::exception &e) {
373 RCLCPP_ERROR(rclcpp::get_logger("main"), "Exception occurred: %s",
374 e.what());
375 g_emergency_stop = true;
376 } catch (...) {
377 RCLCPP_ERROR(rclcpp::get_logger("main"), "Unknown exception occurred");
378 g_emergency_stop = true;
379 }
380
381 RCLCPP_INFO(rclcpp::get_logger("main"), "Program exited safely");
382 rclcpp::shutdown();
383 return 0;
384}
6.2.12 键盘控制机器人
本示例实现了通过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 is NOT handled well by ROS
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.13 拍照
该示例中用到了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 Use this directly; add more branches as needed to support
40 // additional encodings.
41
42 auto now = std::chrono::system_clock::now();
43 auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
44 now.time_since_epoch())
45 .count();
46 std::string path = "images/frame_" + std::to_string(ms) + ".png";
47
48 if (cv::imwrite(path, img)) {
49 RCLCPP_INFO(get_logger(), "Saved: %s (%dx%d)", path.c_str(), img.cols,
50 img.rows);
51 saved_ = true;
52 rclcpp::shutdown();
53 } else {
54 RCLCPP_ERROR(get_logger(), "cv::imwrite failed: %s", path.c_str());
55 }
56 } catch (const std::exception &e) {
57 RCLCPP_ERROR(get_logger(), "raw decode failed: %s", e.what());
58 // Do not set the saved flag; wait for the next frame
59 }
60 }
61
62 std::string topic_;
63 bool saved_;
64 rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr sub_;
65};
66
67int main(int argc, char **argv) {
68 rclcpp::init(argc, argv);
69 rclcpp::spin(std::make_shared<SaveOneRaw>());
70 return 0;
71}
6.2.14 相机推流示例集
该示例集提供了多种相机数据订阅和处理功能,支持深度相机、双目相机和单目相机的数据流订阅。
这些相机数据订阅 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_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 <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 head depth camera
18 *
19 * You can select which topic type to subscribe to via the startup argument
20 * --ros-args -p topic_type:=<type>:
21 * - depth_image: Depth image (sensor_msgs/Image)
22 * - rgb_image: RGB image (sensor_msgs/Image)
23 * - rgb_image_compressed: RGB compressed image (sensor_msgs/CompressedImage)
24 * - rgb_camera_info: Camera intrinsic parameters (sensor_msgs/CameraInfo)
25 * - depth_camera_info: Camera intrinsic parameters (sensor_msgs/CameraInfo)
26 *
27 * Examples:
28 * ros2 run examples echo_camera_rgbd --ros-args -p
29 * topic_type:=rgb_image ros2
30 * run examples echo_camera_rgbd --ros-args -p
31 * topic_type:=rgb_camera_info
32 *
33 * topic_type defaults to "rgb_image"
34 *
35 * See individual callbacks for more detailed comments
36 */
37class CameraTopicEcho : public rclcpp::Node {
38public:
39 CameraTopicEcho() : Node("camera_topic_echo") {
40 // Select which topic type to subscribe to
41 topic_type_ = declare_parameter<std::string>("topic_type", "rgb_image");
42 dump_video_path_ = declare_parameter<std::string>("dump_video_path", "");
43
44 // Subscribed topics and their message layouts
45 // 1. /aima/hal/sensor/rgbd_head_front/depth_image
46 // - topic_type: depth_image
47 // - message type: sensor_msgs::msg::Image
48 // - frame_id: rgbd_head_front
49 // - contents: depth image
50 // 2. /aima/hal/sensor/rgbd_head_front/rgb_image
51 // - topic_type: rgb_image
52 // - message type: sensor_msgs::msg::Image
53 // - frame_id: rgbd_head_front
54 // - contents: RGB image
55 // 3. /aima/hal/sensor/rgbd_head_front/rgb_image/compressed
56 // - topic_type: rgb_image_compressed
57 // - message type: sensor_msgs::msg::CompressedImage
58 // - frame_id: rgbd_head_front
59 // - contents: RGB compressed image
60 // 4. /aima/hal/sensor/rgbd_head_front/rgb_camera_info
61 // - topic_type: camera_info
62 // - message type: sensor_msgs::msg::CameraInfo
63 // - frame_id: rgbd_head_front
64 // - contents: RGB camera intrinsic parameters
65 // 5. /aima/hal/sensor/rgbd_head_front/depth_camera_info
66 // - topic_type: camera_info
67 // - message type: sensor_msgs::msg::CameraInfo
68 // - frame_id: rgbd_head_front
69 // - contents: RGB camera intrinsic parameters
70
71 auto qos = rclcpp::SensorDataQoS();
72
73 // Enable depth image subscription
74 if (topic_type_ == "depth_image") {
75 topic_name_ = "/aima/hal/sensor/rgbd_head_front/depth_image";
76 sub_image_ = create_subscription<sensor_msgs::msg::Image>(
77 topic_name_, qos,
78 std::bind(&CameraTopicEcho::cb_image, this, std::placeholders::_1));
79 RCLCPP_INFO(get_logger(), "✅ Subscribing Depth Image: %s",
80 topic_name_.c_str());
81
82 // Enable RGB image subscription
83 } else if (topic_type_ == "rgb_image") {
84 topic_name_ = "/aima/hal/sensor/rgbd_head_front/rgb_image";
85 sub_image_ = create_subscription<sensor_msgs::msg::Image>(
86 topic_name_, qos,
87 std::bind(&CameraTopicEcho::cb_image, this, std::placeholders::_1));
88 RCLCPP_INFO(get_logger(), "✅ Subscribing RGB Image: %s",
89 topic_name_.c_str());
90 if (!dump_video_path_.empty()) {
91 RCLCPP_INFO(get_logger(), "📝 Will dump received images to video: %s",
92 dump_video_path_.c_str());
93 }
94
95 // Enable RGB compressed image subscription
96 } else if (topic_type_ == "rgb_image_compressed") {
97 topic_name_ = "/aima/hal/sensor/rgbd_head_front/rgb_image/compressed";
98 sub_compressed_ = create_subscription<sensor_msgs::msg::CompressedImage>(
99 topic_name_, qos,
100 std::bind(&CameraTopicEcho::cb_compressed, this,
101 std::placeholders::_1));
102 RCLCPP_INFO(get_logger(), "✅ Subscribing CompressedImage: %s",
103 topic_name_.c_str());
104
105 // Enable rgb camera info subscription
106 } else if (topic_type_ == "rgb_camera_info") {
107 topic_name_ = "/aima/hal/sensor/rgbd_head_front/rgb_camera_info";
108 // RGB-D CameraInfo subscriptions is different with other cameras.
109 // The messages arrive in about 10Hz and SensorDataQoS is enough.
110 sub_camerainfo_ = create_subscription<sensor_msgs::msg::CameraInfo>(
111 topic_name_, qos,
112 std::bind(&CameraTopicEcho::cb_camerainfo, this,
113 std::placeholders::_1));
114 RCLCPP_INFO(get_logger(), "✅ Subscribing RGB CameraInfo: %s",
115 topic_name_.c_str());
116
117 // Enable depth camera info subscription
118 } else if (topic_type_ == "depth_camera_info") {
119 topic_name_ = "/aima/hal/sensor/rgbd_head_front/depth_camera_info";
120 // RGB-D CameraInfo subscriptions is different with other cameras.
121 // The messages arrive in about 10Hz and SensorDataQoS is enough.
122 sub_camerainfo_ = create_subscription<sensor_msgs::msg::CameraInfo>(
123 topic_name_, qos,
124 std::bind(&CameraTopicEcho::cb_camerainfo, this,
125 std::placeholders::_1));
126 RCLCPP_INFO(get_logger(), "✅ Subscribing Depth CameraInfo: %s",
127 topic_name_.c_str());
128
129 // Unknown topic_type error
130 } else {
131 RCLCPP_ERROR(get_logger(), "Unknown topic_type: %s", topic_type_.c_str());
132 throw std::runtime_error("Unknown topic_type");
133 }
134 }
135
136 ~CameraTopicEcho() override {
137 if (video_writer_.isOpened()) {
138 video_writer_.release();
139 RCLCPP_INFO(get_logger(), "Video file closed.");
140 }
141 }
142
143private:
144 // Image callback (depth/RGB image)
145 void cb_image(const sensor_msgs::msg::Image::SharedPtr msg) {
146 update_arrivals();
147
148 if (should_print()) {
149 RCLCPP_INFO(get_logger(),
150 "📸 %s received\n"
151 " • frame_id: %s\n"
152 " • stamp (sec): %.6f\n"
153 " • encoding: %s\n"
154 " • size (WxH): %u x %u\n"
155 " • step (bytes/row):%u\n"
156 " • is_bigendian: %u\n"
157 " • recv FPS (1s): %.1f",
158 topic_type_.c_str(), msg->header.frame_id.c_str(),
159 rclcpp::Time(msg->header.stamp).seconds(),
160 msg->encoding.c_str(), msg->width, msg->height, msg->step,
161 msg->is_bigendian, get_fps());
162 }
163
164 // Video dump is supported only for RGB images
165 if (topic_type_ == "rgb_image" && !dump_video_path_.empty()) {
166 dump_image_to_video(msg);
167 }
168 }
169
170 // CompressedImage callback
171 void cb_compressed(const sensor_msgs::msg::CompressedImage::SharedPtr msg) {
172 update_arrivals();
173
174 if (should_print()) {
175 RCLCPP_INFO(get_logger(),
176 "🗜️ CompressedImage received\n"
177 " • frame_id: %s\n"
178 " • stamp (sec): %.6f\n"
179 " • format: %s\n"
180 " • data size: %zu\n"
181 " • recv FPS (1s): %.1f",
182 msg->header.frame_id.c_str(),
183 rclcpp::Time(msg->header.stamp).seconds(),
184 msg->format.c_str(), msg->data.size(), get_fps());
185 }
186 }
187
188 // CameraInfo callback (camera intrinsic parameters)
189 void cb_camerainfo(const sensor_msgs::msg::CameraInfo::SharedPtr msg) {
190 // CameraInfo is typically published once; print it once
191 std::ostringstream oss;
192 oss << "📷 " << topic_type_ << " received\n"
193 << " • frame_id: " << msg->header.frame_id << "\n"
194 << " • stamp (sec): " << rclcpp::Time(msg->header.stamp).seconds()
195 << "\n"
196 << " • width x height: " << msg->width << " x " << msg->height << "\n"
197 << " • distortion_model:" << msg->distortion_model << "\n"
198 << " • D: [";
199 for (size_t i = 0; i < msg->d.size(); ++i) {
200 oss << msg->d[i];
201 if (i + 1 < msg->d.size())
202 oss << ", ";
203 }
204 oss << "]\n • K: [";
205 for (int i = 0; i < 9; ++i) {
206 oss << msg->k[i];
207 if (i + 1 < 9)
208 oss << ", ";
209 }
210 oss << "]\n • R: [";
211 for (int i = 0; i < 9; ++i) {
212 oss << msg->r[i];
213 if (i + 1 < 9)
214 oss << ", ";
215 }
216 oss << "]\n • P: [";
217 for (int i = 0; i < 12; ++i) {
218 oss << msg->p[i];
219 if (i + 1 < 12)
220 oss << ", ";
221 }
222 oss << "]\n"
223 << " • binning_x: " << msg->binning_x << "\n"
224 << " • binning_y: " << msg->binning_y << "\n"
225 << " • roi: { x_offset: " << msg->roi.x_offset
226 << ", y_offset: " << msg->roi.y_offset
227 << ", height: " << msg->roi.height << ", width: " << msg->roi.width
228 << ", do_rectify: " << (msg->roi.do_rectify ? "true" : "false") << " }";
229 RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
230 }
231
232 // Track arrival timestamps to compute FPS
233 void update_arrivals() {
234 const rclcpp::Time now = this->get_clock()->now();
235 arrivals_.push_back(now);
236 while (!arrivals_.empty() && (now - arrivals_.front()).seconds() > 1.0) {
237 arrivals_.pop_front();
238 }
239 }
240 double get_fps() const { return static_cast<double>(arrivals_.size()); }
241
242 // Control printing frequency
243 bool should_print() {
244 const rclcpp::Time now = this->get_clock()->now();
245 if ((now - last_print_).seconds() >= 1.0) {
246 last_print_ = now;
247 return true;
248 }
249 return false;
250 }
251
252 // Dump received images to a video file (RGB images only)
253 void dump_image_to_video(const sensor_msgs::msg::Image::SharedPtr &msg) {
254 cv::Mat image;
255 try {
256 // Obtain the Mat without copying by not specifying encoding
257 cv_bridge::CvImageConstPtr cvp = cv_bridge::toCvShare(msg);
258 image = cvp->image;
259 // Convert to BGR for uniform saving
260 if (msg->encoding == "rgb8") {
261 cv::cvtColor(image, image, cv::COLOR_RGB2BGR);
262 } else {
263 RCLCPP_WARN(get_logger(), "image encoding not expected: %s",
264 msg->encoding.c_str());
265 return;
266 }
267 } catch (const std::exception &e) {
268 RCLCPP_WARN(get_logger(), "cv_bridge exception: %s", e.what());
269 return;
270 }
271
272 // Initialize VideoWriter
273 if (!video_writer_.isOpened()) {
274 int fourcc = cv::VideoWriter::fourcc('m', 'p', '4', 'v');
275 double fps = std::max(1.0, get_fps());
276 bool ok = video_writer_.open(dump_video_path_, fourcc, fps,
277 cv::Size(image.cols, image.rows), true);
278 if (!ok) {
279 RCLCPP_ERROR(get_logger(), "Failed to open video file: %s",
280 dump_video_path_.c_str());
281 dump_video_path_.clear(); // stop trying
282 return;
283 }
284 RCLCPP_INFO(get_logger(), "VideoWriter started: %s, size=%dx%d, fps=%.1f",
285 dump_video_path_.c_str(), image.cols, image.rows, fps);
286 }
287 video_writer_.write(image);
288 }
289
290 // Member variables
291 std::string topic_type_;
292 std::string topic_name_;
293 std::string dump_video_path_;
294
295 // Subscriptions
296 rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr sub_image_;
297 rclcpp::Subscription<sensor_msgs::msg::CompressedImage>::SharedPtr
298 sub_compressed_;
299 rclcpp::Subscription<sensor_msgs::msg::CameraInfo>::SharedPtr sub_camerainfo_;
300
301 // FPS statistics
302 rclcpp::Time last_print_{0, 0, RCL_ROS_TIME};
303 std::deque<rclcpp::Time> arrivals_;
304
305 // Video writer
306 cv::VideoWriter video_writer_;
307};
308
309int main(int argc, char **argv) {
310 rclcpp::init(argc, argv);
311 auto node = std::make_shared<CameraTopicEcho>();
312 rclcpp::spin(node);
313 rclcpp::shutdown();
314 return 0;
315}
使用说明:
订阅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图(及其mask处理)、压缩图和相机内参数据。
功能特点:
支持头部后置相机数据订阅
实时FPS统计和数据显示
支持RGB图视频录制功能及把手遮挡区域mask处理
可配置的topic类型选择
支持的数据类型:
rgb_image: RGB图像 (sensor_msgs/Image)rgb_image_compressed: 压缩RGB图像 (sensor_msgs/CompressedImage)camera_info: 相机内参 (sensor_msgs/CameraInfo)
1#include <deque>
2#include <filesystem>
3#include <iomanip>
4#include <memory>
5#include <rclcpp/rclcpp.hpp>
6#include <sensor_msgs/msg/camera_info.hpp>
7#include <sensor_msgs/msg/compressed_image.hpp>
8#include <sensor_msgs/msg/image.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 rear head monocular
19 * camera
20 *
21 * You can select which topic type to subscribe to via the startup argument
22 * --ros-args -p topic_type:=<type>:
23 * - rgb_image: RGB image (sensor_msgs/Image)
24 * - rgb_image_compressed: RGB compressed image (sensor_msgs/CompressedImage)
25 * - camera_info: Camera intrinsic parameters (sensor_msgs/CameraInfo)
26 *
27 * Examples:
28 * ros2 run examples echo_camera_head_rear --ros-args -p topic_type:=rgb_image
29 * ros2 run examples echo_camera_head_rear --ros-args -p
30 * topic_type:=rgb_image_compressed ros2 run examples echo_camera_head_rear
31 * --ros-args -p topic_type:=camera_info
32 *
33 * topic_type defaults to "rgb_image"
34 *
35 * See individual callbacks for more detailed comments
36 */
37class HeadRearCameraTopicEcho : public rclcpp::Node {
38public:
39 HeadRearCameraTopicEcho() : Node("head_rear_camera_topic_echo") {
40 // Select which topic type to subscribe to
41 topic_type_ = declare_parameter<std::string>("topic_type", "rgb_image");
42 dump_video_path_ = declare_parameter<std::string>("dump_video_path", "");
43 with_mask_ = declare_parameter<bool>("with_mask", false);
44
45 // Subscribed topics and their message layouts
46 // 1. /aima/hal/sensor/rgb_head_rear/rgb_image
47 // - topic_type: rgb_image
48 // - message type: sensor_msgs::msg::Image
49 // - frame_id: rgb_head_rear
50 // - child_frame_id: /
51 // - contents: raw image data
52 // 2. /aima/hal/sensor/rgb_head_rear/rgb_image/compressed
53 // - topic_type: rgb_image_compressed
54 // - message type: sensor_msgs::msg::CompressedImage
55 // - frame_id: rgb_head_rear
56 // - contents: compressed image data
57 // 3. /aima/hal/sensor/rgb_head_rear/camera_info
58 // - topic_type: camera_info
59 // - message type: sensor_msgs::msg::CameraInfo
60 // - frame_id: rgb_head_rear
61 // - contents: camera intrinsic parameters
62
63 // Set QoS parameters - use SensorData QoS
64 auto qos = rclcpp::SensorDataQoS();
65
66 if (with_mask_ && !dump_video_path_.empty()) {
67 auto mask_path =
68 std::filesystem::read_symlink("/proc/self/exe").parent_path() /
69 "data" / "rgb_head_rear_mask.png";
70 mask_image_ = cv::imread(mask_path, cv::IMREAD_GRAYSCALE);
71 if (mask_image_.empty()) {
72 RCLCPP_ERROR(get_logger(), "Failed to load mask file from %s",
73 mask_path.c_str());
74 throw std::runtime_error("Failed to load mask file");
75 }
76 }
77
78 // Enable RGB image subscription
79 if (topic_type_ == "rgb_image") {
80 topic_name_ = "/aima/hal/sensor/rgb_head_rear/rgb_image";
81 sub_image_ = create_subscription<sensor_msgs::msg::Image>(
82 topic_name_, qos,
83 std::bind(&HeadRearCameraTopicEcho::cb_image, this,
84 std::placeholders::_1));
85 RCLCPP_INFO(get_logger(), "✅ Subscribing RGB Image: %s",
86 topic_name_.c_str());
87 if (!dump_video_path_.empty()) {
88 RCLCPP_INFO(
89 get_logger(), "📝 Will dump received images %s mask to video: %s",
90 (with_mask_ ? "with" : "without"), dump_video_path_.c_str());
91 }
92 }
93
94 // Enable RGB compressed image subscription
95 else if (topic_type_ == "rgb_image_compressed") {
96 topic_name_ = "/aima/hal/sensor/rgb_head_rear/rgb_image/compressed";
97 sub_compressed_ = create_subscription<sensor_msgs::msg::CompressedImage>(
98 topic_name_, qos,
99 std::bind(&HeadRearCameraTopicEcho::cb_compressed, this,
100 std::placeholders::_1));
101 RCLCPP_INFO(get_logger(), "✅ Subscribing CompressedImage: %s",
102 topic_name_.c_str());
103
104 // Enable camera info subscription
105 } else if (topic_type_ == "camera_info") {
106 topic_name_ = "/aima/hal/sensor/rgb_head_rear/camera_info";
107 // CameraInfo subscriptions must use reliable + transient_local
108 // QoS in order to receive latched/history messages (even if only one
109 // message was published). Here we use keep_last(1) + reliable
110 // + transient_local.
111 sub_camerainfo_ = create_subscription<sensor_msgs::msg::CameraInfo>(
112 topic_name_,
113 rclcpp::QoS(rclcpp::KeepLast(1)).reliable().transient_local(),
114 std::bind(&HeadRearCameraTopicEcho::cb_camerainfo, this,
115 std::placeholders::_1));
116 RCLCPP_INFO(get_logger(),
117 "✅ Subscribing CameraInfo (with transient_local): %s",
118 topic_name_.c_str());
119
120 // Unknown topic_type error
121 } else {
122 RCLCPP_ERROR(get_logger(), "Unknown topic_type: %s", topic_type_.c_str());
123 throw std::runtime_error("Unknown topic_type");
124 }
125 }
126
127 ~HeadRearCameraTopicEcho() override {
128 if (video_writer_.isOpened()) {
129 video_writer_.release();
130 RCLCPP_INFO(get_logger(), "Video file closed.");
131 }
132 }
133
134private:
135 // Image callback (RGB image)
136 void cb_image(const sensor_msgs::msg::Image::SharedPtr msg) {
137 update_arrivals();
138
139 if (should_print()) {
140 RCLCPP_INFO(get_logger(),
141 "📸 %s received\n"
142 " • frame_id: %s\n"
143 " • stamp (sec): %.6f\n"
144 " • encoding: %s\n"
145 " • size (WxH): %u x %u\n"
146 " • step (bytes/row):%u\n"
147 " • is_bigendian: %u\n"
148 " • recv FPS (1s): %.1f",
149 topic_type_.c_str(), msg->header.frame_id.c_str(),
150 rclcpp::Time(msg->header.stamp).seconds(),
151 msg->encoding.c_str(), msg->width, msg->height, msg->step,
152 msg->is_bigendian, get_fps());
153 }
154
155 // Video dump is supported only for RGB images
156 if (topic_type_ == "rgb_image" && !dump_video_path_.empty()) {
157 dump_image_to_video(msg);
158 }
159 }
160
161 // CompressedImage callback (RGB compressed image)
162 void cb_compressed(const sensor_msgs::msg::CompressedImage::SharedPtr msg) {
163 update_arrivals();
164
165 if (should_print()) {
166 RCLCPP_INFO(get_logger(),
167 "🗜️ %s received\n"
168 " • frame_id: %s\n"
169 " • stamp (sec): %.6f\n"
170 " • format: %s\n"
171 " • data size: %zu\n"
172 " • recv FPS (1s): %.1f",
173 topic_type_.c_str(), msg->header.frame_id.c_str(),
174 rclcpp::Time(msg->header.stamp).seconds(),
175 msg->format.c_str(), msg->data.size(), get_fps());
176 }
177 }
178
179 // CameraInfo callback (camera intrinsic parameters)
180 void cb_camerainfo(const sensor_msgs::msg::CameraInfo::SharedPtr msg) {
181 // CameraInfo is typically published once; print it once
182 std::ostringstream oss;
183 oss << "📷 " << topic_type_ << " received\n"
184 << " • frame_id: " << msg->header.frame_id << "\n"
185 << " • stamp (sec): " << rclcpp::Time(msg->header.stamp).seconds()
186 << "\n"
187 << " • width x height: " << msg->width << " x " << msg->height << "\n"
188 << " • distortion_model:" << msg->distortion_model << "\n"
189 << " • D: [";
190 for (size_t i = 0; i < msg->d.size(); ++i) {
191 oss << msg->d[i];
192 if (i + 1 < msg->d.size())
193 oss << ", ";
194 }
195 oss << "]\n • K: [";
196 for (int i = 0; i < 9; ++i) {
197 oss << msg->k[i];
198 if (i + 1 < 9)
199 oss << ", ";
200 }
201 oss << "]\n • R: [";
202 for (int i = 0; i < 9; ++i) {
203 oss << msg->r[i];
204 if (i + 1 < 9)
205 oss << ", ";
206 }
207 oss << "]\n • P: [";
208 for (int i = 0; i < 12; ++i) {
209 oss << msg->p[i];
210 if (i + 1 < 12)
211 oss << ", ";
212 }
213 oss << "]\n"
214 << " • binning_x: " << msg->binning_x << "\n"
215 << " • binning_y: " << msg->binning_y << "\n"
216 << " • roi: { x_offset: " << msg->roi.x_offset
217 << ", y_offset: " << msg->roi.y_offset
218 << ", height: " << msg->roi.height << ", width: " << msg->roi.width
219 << ", do_rectify: " << (msg->roi.do_rectify ? "true" : "false") << " }";
220 RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
221 }
222
223 // Track arrival timestamps to compute FPS
224 void update_arrivals() {
225 const rclcpp::Time now = this->get_clock()->now();
226 arrivals_.push_back(now);
227 while (!arrivals_.empty() && (now - arrivals_.front()).seconds() > 1.0) {
228 arrivals_.pop_front();
229 }
230 }
231 double get_fps() const { return static_cast<double>(arrivals_.size()); }
232
233 // Control printing frequency
234 bool should_print() {
235 const rclcpp::Time now = this->get_clock()->now();
236 if ((now - last_print_).seconds() >= 1.0) {
237 last_print_ = now;
238 return true;
239 }
240 return false;
241 }
242
243 // Dump received images to a video file (RGB images only)
244 void dump_image_to_video(const sensor_msgs::msg::Image::SharedPtr &msg) {
245 cv::Mat image;
246 try {
247 // Obtain the Mat without copying by not specifying encoding
248 cv_bridge::CvImageConstPtr cvp = cv_bridge::toCvShare(msg);
249 image = cvp->image;
250 // Convert to BGR for uniform saving
251 if (msg->encoding == "rgb8") {
252 cv::cvtColor(image, image, cv::COLOR_RGB2BGR);
253 } else {
254 RCLCPP_WARN(get_logger(), "image encoding not expected: %s",
255 msg->encoding.c_str());
256 return;
257 }
258 if (with_mask_) {
259 image.setTo(cv::Scalar(0, 0, 0), mask_image_ == 0);
260 }
261 } catch (const std::exception &e) {
262 RCLCPP_WARN(get_logger(), "cv_bridge exception: %s", e.what());
263 return;
264 }
265
266 // Initialize VideoWriter
267 if (!video_writer_.isOpened()) {
268 int fourcc = cv::VideoWriter::fourcc('M', 'J', 'P', 'G');
269 double fps = std::max(1.0, get_fps());
270 bool ok = video_writer_.open(dump_video_path_, fourcc, fps,
271 cv::Size(image.cols, image.rows), true);
272 if (!ok) {
273 RCLCPP_ERROR(get_logger(), "Failed to open video file: %s",
274 dump_video_path_.c_str());
275 dump_video_path_.clear(); // stop trying
276 return;
277 }
278 RCLCPP_INFO(get_logger(), "VideoWriter started: %s, size=%dx%d, fps=%.1f",
279 dump_video_path_.c_str(), image.cols, image.rows, fps);
280 }
281 video_writer_.write(image);
282 }
283
284 // Member variables
285 std::string topic_type_;
286 std::string topic_name_;
287 std::string dump_video_path_;
288 bool with_mask_;
289 cv::Mat mask_image_;
290
291 // Subscriptions
292 rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr sub_image_;
293 rclcpp::Subscription<sensor_msgs::msg::CompressedImage>::SharedPtr
294 sub_compressed_;
295 rclcpp::Subscription<sensor_msgs::msg::CameraInfo>::SharedPtr sub_camerainfo_;
296
297 // FPS statistics
298 rclcpp::Time last_print_{0, 0, RCL_ROS_TIME};
299 std::deque<rclcpp::Time> arrivals_;
300
301 // Video writer
302 cv::VideoWriter video_writer_;
303};
304
305int main(int argc, char **argv) {
306 rclcpp::init(argc, argv);
307 auto node = std::make_shared<HeadRearCameraTopicEcho>();
308 rclcpp::spin(node);
309 rclcpp::shutdown();
310 return 0;
311}
使用说明:
订阅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
录制视频并对把手遮挡区域mask处理:
# dump_video_path的值可改为其他路径, 注意提前创建该文件所在目录才能保存 ros2 run examples echo_camera_head_rear --ros-args -p topic_type:=rgb_image -p with_mask:=true -p dump_video_path:=$PWD/rear_camera.avi
应用场景:
人脸识别和追踪
目标检测和识别
视觉SLAM
图像处理和计算机视觉算法开发
机器人视觉导航
6.2.15 头部触摸传感器数据订阅
该示例中用到了 echo_head_touch_sensor,通过订阅/aima/hal/sensor/touch_head话题来接收机器人的头部触摸传感器的反馈数据。
功能特点:
订阅了头部传感器的反馈数据,当头部被触摸时候,输出会从 IDLE->TOUCH
1//
2// Created by agiuser on 2026/1/23.
3//
4
5#include <aimdk_msgs/msg/touch_state.hpp>
6#include <rclcpp/rclcpp.hpp>
7
8class TouchStateSubscriber : public rclcpp::Node {
9public:
10 TouchStateSubscriber() : Node("touch_state_subscriber") {
11 subscription_ = this->create_subscription<aimdk_msgs::msg::TouchState>(
12 "/aima/hal/sensor/touch_head", 10,
13 std::bind(&TouchStateSubscriber::touch_callback, this,
14 std::placeholders::_1));
15
16 RCLCPP_INFO(this->get_logger(), "TouchState subscriber started, listening "
17 "to /aima/hal/sensor/touch_head");
18 }
19
20private:
21 void touch_callback(const aimdk_msgs::msg::TouchState::SharedPtr msg) {
22 // print message info
23 RCLCPP_INFO(this->get_logger(), "Received TouchState message:");
24 RCLCPP_INFO(this->get_logger(), " Timestamp: %d.%09d",
25 msg->header.stamp.sec, msg->header.stamp.nanosec);
26
27 std::string event_str = get_event_type_string(msg->event_type);
28 RCLCPP_INFO(this->get_logger(), " Event Type: %s (%d)", event_str.c_str(),
29 msg->event_type);
30 }
31
32 std::string get_event_type_string(uint8_t event_type) {
33 switch (event_type) {
34 case aimdk_msgs::msg::TouchState::UNKNOWN:
35 return "UNKNOWN";
36 case aimdk_msgs::msg::TouchState::IDLE:
37 return "IDLE";
38 case aimdk_msgs::msg::TouchState::TOUCH:
39 return "TOUCH";
40 case aimdk_msgs::msg::TouchState::SLIDE:
41 return "SLIDE";
42 case aimdk_msgs::msg::TouchState::PAT_ONCE:
43 return "PAT_ONCE";
44 case aimdk_msgs::msg::TouchState::PAT_TWICE:
45 return "PAT_TWICE";
46 case aimdk_msgs::msg::TouchState::PAT_TRIPLE:
47 return "PAT_TRIPLE";
48 default:
49 return "INVALID";
50 }
51 }
52 rclcpp::Subscription<aimdk_msgs::msg::TouchState>::SharedPtr subscription_;
53};
54
55int main(int argc, char *argv[]) {
56 rclcpp::init(argc, argv);
57 auto node = std::make_shared<TouchStateSubscriber>();
58 rclcpp::spin(node);
59 rclcpp::shutdown();
60 return 0;
61}
使用说明:
ros2 run examples echo_head_touch_sensor
输出示例:
[INFO] [1769162721.359354722] [touch_state_subscriber]: Timestamp: 1769162726.863282315
[INFO] [1769162721.359361643] [touch_state_subscriber]: Event Type: IDLE (1)
[INFO] [1769167184.142143492] [touch_state_subscriber]: Timestamp: 1769167189.364879133
[INFO] [1769167184.142147126] [touch_state_subscriber]: Event Type: TOUCH (2)
6.2.16 激光雷达数据订阅
该示例中用到了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.17 播放视频
该示例中用到了play_video,在运行节点程序前,需要先将视频上传到机器人的**交互计算单元(PC3)**上(用户可在其上创建一个用来存储视频的目录如/var/tmp/videos/),然后将节点程序中的video_path改为需要播放视频的路径。
注意
⚠️ 请注意!交互计算单元(PC3)独立于二开程序所在的开发计算单元(PC2), 音视频文件务必存入交互计算单元(IP: 10.0.1.42)。
⚠️ 音视频文件夹及该文件夹所有父目录应当为所有用户可访问读取(建议在/var/tmp/下创建子目录存放)
注意
使用本功能前,请在运控计算单元(PC1)上执行以下命令关闭 task_manager 模块,避免其与二开程序争抢显示屏控制权:
aima em stop-app task_manager
功能说明
通过调用PlayVideo服务,可以让机器人在屏幕上播放指定路径的视频文件。请确保视频文件已上传到交互计算单元,否则播放会失败。
💡 提示:视频文件的准备方式
您可以使用 echo_camera_rgbd 示例将机器人的深度相机 RGB 图像录制为
.mp4视频文件,再将其作为play_video的输入:第一步:在机器人开发计算单元(PC2)上录制视频
ros2 run examples echo_camera_rgbd --ros-args -p topic_type:=rgb_image -p dump_video_path:=$PWD/output.mp4 # 录制完成后按 Ctrl+C 停止,视频保存为 output.mp4第二步:将视频传输到交互计算单元(PC3,IP: 10.0.1.42)
scp output.mp4 user@10.0.1.42:/var/tmp/videos/output.mp4第三步:运行 play_video 播放
ros2 run examples play_video # 修改代码中 video_path 为 /var/tmp/videos/output.mp4
1#include "aimdk_msgs/srv/play_video.hpp"
2#include "aimdk_msgs/msg/common_request.hpp"
3#include "rclcpp/rclcpp.hpp"
4#include <chrono>
5#include <memory>
6#include <signal.h>
7#include <string>
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 g_node.reset();
18 }
19 rclcpp::shutdown();
20 exit(signal);
21}
22
23class PlayVideoClient : public rclcpp::Node {
24public:
25 PlayVideoClient() : Node("play_video_client") {
26 client_ = this->create_client<aimdk_msgs::srv::PlayVideo>(
27 "/face_ui_proxy/play_video");
28 RCLCPP_INFO(this->get_logger(), "✅ PlayVideo client node started.");
29
30 // Wait for the service to become available
31 while (!client_->wait_for_service(std::chrono::seconds(2))) {
32 RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
33 }
34 RCLCPP_INFO(this->get_logger(),
35 "🟢 Service available, ready to send request.");
36 }
37
38 bool send_request(const std::string &video_path, uint8_t mode,
39 int32_t priority) {
40 try {
41 auto request = std::make_shared<aimdk_msgs::srv::PlayVideo::Request>();
42
43 request->video_path = video_path;
44 request->mode = mode;
45 request->priority = priority;
46
47 RCLCPP_INFO(this->get_logger(),
48 "📨 Sending request to play video: mode=%hhu video=%s", mode,
49 video_path.c_str());
50
51 const std::chrono::milliseconds timeout(250);
52 for (int i = 0; i < 8; i++) {
53 request->header.header.stamp = this->now();
54 auto future = client_->async_send_request(request);
55 auto retcode = rclcpp::spin_until_future_complete(shared_from_this(),
56 future, timeout);
57 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
58 // retry as remote peer is NOT handled well by ROS
59 RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
60 continue;
61 }
62 // future.done
63 auto response = future.get();
64 if (response->success) {
65 RCLCPP_INFO(this->get_logger(),
66 "✅ Request to play video recorded successfully: %s",
67 response->message.c_str());
68 return true;
69 } else {
70 RCLCPP_ERROR(this->get_logger(),
71 "❌ Failed to record play-video request: %s",
72 response->message.c_str());
73 return false;
74 }
75 }
76 RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
77 return false;
78 } catch (const std::exception &e) {
79 RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
80 return false;
81 }
82 }
83
84private:
85 rclcpp::Client<aimdk_msgs::srv::PlayVideo>::SharedPtr client_;
86};
87
88int main(int argc, char **argv) {
89 try {
90 rclcpp::init(argc, argv);
91
92 // Set up signal handlers
93 signal(SIGINT, signal_handler);
94 signal(SIGTERM, signal_handler);
95
96 std::string video_path =
97 "/agibot/data/home/agi/zhiyuan.mp4"; // Default video path; modify as
98 // needed
99 int32_t priority = 5;
100 int mode = 2; // Loop playback
101 std::cout << "Enter video play mode (1: once, 2: loop): ";
102 std::cin >> mode;
103 if (mode < 1 || mode > 2) {
104 RCLCPP_ERROR(rclcpp::get_logger("main"), "Invalid play mode: %d", mode);
105 rclcpp::shutdown();
106 return 1;
107 }
108
109 g_node = std::make_shared<PlayVideoClient>();
110 auto client = std::dynamic_pointer_cast<PlayVideoClient>(g_node);
111
112 if (client) {
113 client->send_request(video_path, mode, priority);
114 }
115
116 // Clean up resources
117 g_node.reset();
118 rclcpp::shutdown();
119
120 return 0;
121 } catch (const std::exception &e) {
122 RCLCPP_ERROR(rclcpp::get_logger("main"),
123 "Program exited with exception: %s", e.what());
124 return 1;
125 }
126}
6.2.18 音频文件播放
该示例中用到了play_audio,通过该节点可实现播放指定的音频文件,支持WAV(PCM编码)、RAW PCM格式的音频文件播放,不支持MP3等其他格式。
功能特点:
支持WAV(PCM编码)、RAW PCM格式播放,不支持MP3等其他格式
支持优先级控制,可设置播放优先级
支持自定义文件路径和播放参数
提供完整的错误处理和状态反馈
技术实现:
使用PlayAudioFile服务进行音频文件播放
支持优先级设置(priority: 1~10,priority_weight: 1~100)
提供详细的播放状态反馈
应用场景:
音频文件播放和媒体控制
语音提示和音效播放
多媒体应用开发
机器人交互音频反馈
注意
⚠️ 请注意!交互计算单元(PC3)独立于二开程序所在的开发计算单元(PC2), 音视频文件务必存入交互计算单元(IP: 10.0.1.42)。
⚠️ 音视频文件夹及该文件夹所有父目录应当为所有用户可访问读取(建议在/var/tmp/下创建子目录存放)
1#include <aimdk_msgs/srv/play_audio_file.hpp>
2#include <filesystem>
3#include <iostream>
4#include <rclcpp/rclcpp.hpp>
5#include <string>
6
7using PlayAudioFile = aimdk_msgs::srv::PlayAudioFile;
8
9int main(int argc, char **argv) {
10 rclcpp::init(argc, argv);
11 auto node = rclcpp::Node::make_shared("play_audio_file_client_min");
12
13 // 1) Service name
14 const std::string service_name = "/aimdk_5Fmsgs/srv/PlayAudioFile";
15 auto client = node->create_client<PlayAudioFile>(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 if (!std::getline(std::cin, file_name) || !rclcpp::ok()) {
28 std::cout << std::endl;
29 rclcpp::shutdown();
30 return 1;
31 }
32 if (file_name.empty()) {
33 file_name = default_file;
34 }
35 }
36
37 // 3) Build the request
38 // Check if Ctrl+C was pressed during user input
39 if (!rclcpp::ok()) {
40 rclcpp::shutdown();
41 return 1;
42 }
43
44 auto req = std::make_shared<PlayAudioFile::Request>();
45 req->file.pkg_name = "demo_client";
46 req->file.file_name = std::filesystem::path(file_name).filename().string();
47 req->file.file_path =
48 std::filesystem::path(file_name).parent_path().string() + "/";
49 req->file.priority = 6;
50 req->file.priority_weight = 0;
51
52 // 4) Wait for service and call
53 RCLCPP_INFO(rclcpp::get_logger("main"), "Waiting for service: %s",
54 service_name.c_str());
55 while (!client->wait_for_service(std::chrono::seconds(2))) {
56 if (!rclcpp::ok()) {
57 RCLCPP_ERROR(node->get_logger(),
58 "Interrupted while waiting for the service. Exiting.");
59 rclcpp::shutdown();
60 return 1;
61 }
62 RCLCPP_INFO(node->get_logger(), "⏳ Service unavailable, waiting...");
63 }
64
65 rclcpp::FutureReturnCode rc = rclcpp::FutureReturnCode::TIMEOUT;
66 std::shared_future<PlayAudioFile::Response::SharedPtr> future;
67 for (int i = 0; i < 5; ++i) {
68 req->request.header.stamp = node->now();
69 future = client->async_send_request(req);
70 rc = rclcpp::spin_until_future_complete(node, future,
71 std::chrono::seconds(1));
72 if (rc == rclcpp::FutureReturnCode::SUCCESS) {
73 break;
74 }
75 if (rc == rclcpp::FutureReturnCode::INTERRUPTED) {
76 // Check if Ctrl+C was pressed during spin_until_future_complete
77 if (rclcpp::ok()) {
78 RCLCPP_WARN(node->get_logger(), "Interrupted while waiting");
79 }
80 rclcpp::shutdown();
81 return 1;
82 }
83 // retry as remote peer is NOT handled well by ROS
84 RCLCPP_INFO(node->get_logger(), "trying ... [%d]", i);
85 }
86
87 if (rc != rclcpp::FutureReturnCode::SUCCESS) {
88 // Check if Ctrl+C was pressed, avoid using node after shutdown
89 if (rclcpp::ok()) {
90 RCLCPP_ERROR(node->get_logger(), "Call timed out or did not complete");
91 }
92 rclcpp::shutdown();
93 return 1;
94 }
95
96 // 5) Handle response (success is in reponse.status)
97 try {
98 const auto resp = future.get();
99 bool success = resp->reponse.status.value == 1;
100
101 if (success) {
102 RCLCPP_INFO(node->get_logger(), "✅ Audio file play request succeeded: %s",
103 file_name.c_str());
104 } else {
105 RCLCPP_ERROR(node->get_logger(), "❌ Audio file play request failed: %s",
106 file_name.c_str());
107 }
108 } catch (const std::exception &e) {
109 RCLCPP_ERROR(node->get_logger(), "Call exception: %s", e.what());
110 }
111
112 rclcpp::shutdown();
113 return 0;
114}
使用说明:
# 播放默认音频文件
ros2 run examples play_audio
# 播放指定音频文件
# 注意替换/path/to/your/audio_file.wav为交互板上实际文件路径
ros2 run examples play_audio /path/to/your/audio_file.wav
# 播放TTS缓存文件
ros2 run examples play_audio /agibot/data/var/interaction/tts_cache/normal/demo.wav
输出示例:
[INFO] [play_audio_file_client_min]: ✅ Audio file play request succeeded
注意事项:
确保音频文件路径正确且文件存在
支持的文件格式:WAV(PCM编码)、RAW PCM,不支持MP3等其他格式
优先级设置影响播放队列顺序
6.2.19 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 if (!std::getline(std::cin, tts_text) || !rclcpp::ok()) {
23 std::cout << std::endl;
24 rclcpp::shutdown();
25 return 1;
26 }
27 if (tts_text.empty()) {
28 tts_text = "Hello, I am AgiBot X2.";
29 }
30 }
31
32 if (!rclcpp::ok()) {
33 rclcpp::shutdown();
34 return 1;
35 }
36
37 auto req = std::make_shared<PlayTTS::Request>();
38 req->header.header.stamp = node->now();
39 req->tts_req.text = tts_text;
40 req->tts_req.domain = "demo_client"; // Required: identifies the caller
41 req->tts_req.trace_id =
42 "demo"; // Optional: request identifier for the TTS request
43 req->tts_req.is_interrupted =
44 true; // Required: whether to interrupt same-priority playback
45 req->tts_req.priority_weight = 0;
46 req->tts_req.priority_level.value = 6;
47
48 if (!client->wait_for_service(
49 std::chrono::duration_cast<std::chrono::seconds>(
50 std::chrono::seconds(5)))) {
51 if (rclcpp::ok()) {
52 RCLCPP_ERROR(node->get_logger(), "Service unavailable: %s",
53 service_name.c_str());
54 }
55 rclcpp::shutdown();
56 return 1;
57 }
58
59 auto future = client->async_send_request(req);
60 if (rclcpp::spin_until_future_complete(
61 node, future,
62 std::chrono::duration_cast<std::chrono::seconds>(
63 std::chrono::seconds(10))) != rclcpp::FutureReturnCode::SUCCESS) {
64 if (rclcpp::ok()) {
65 RCLCPP_ERROR(node->get_logger(), "Call timed out");
66 }
67 rclcpp::shutdown();
68 return 1;
69 }
70
71 const auto resp = future.get();
72 if (resp->tts_resp.is_success) {
73 RCLCPP_INFO(node->get_logger(), "✅ TTS play request succeeded");
74 } else {
75 RCLCPP_ERROR(node->get_logger(), "❌ TTS play request failed");
76 }
77
78 rclcpp::shutdown();
79 return 0;
80}
使用说明
# 使用命令行参数播报文本(推荐)
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.20 麦克风数据接收
该示例中用到了mic_receiver,通过订阅/agent/process_audio_output话题来接收机器人的降噪音频数据,支持内置麦克风和外置麦克风两种音频流,并根据VAD(语音活动检测)状态自动保存完整的语音片段为PCM文件。
功能特点:
基于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 // Note: deep queue to avoid missing data in a burst at start of VAD.
28 auto qos = rclcpp::QoS(
29 rclcpp::QoSInitialization::from_rmw(rmw_qos_profile_sensor_data));
30 qos.keep_last(500).best_effort();
31
32 subscription_ =
33 this->create_subscription<aimdk_msgs::msg::ProcessedAudioOutput>(
34 "/agent/process_audio_output", qos,
35 std::bind(&AudioSubscriber::audio_callback, this,
36 std::placeholders::_1));
37
38 RCLCPP_INFO(this->get_logger(),
39 "Starting to subscribe to denoised audio data...");
40 }
41
42private:
43 void
44 audio_callback(const aimdk_msgs::msg::ProcessedAudioOutput::SharedPtr msg) {
45 try {
46 uint32_t stream_id = msg->stream_id;
47 uint8_t vad_state = msg->audio_vad_state.value;
48 const std::vector<uint8_t> &audio_data = msg->audio_data;
49
50 static const std::unordered_map<uint8_t, std::string> vad_state_names = {
51 {0, "No Speech"},
52 {1, "Speech Start"},
53 {2, "Speech Processing"},
54 {3, "Speech End"}};
55 static const std::unordered_map<uint32_t, std::string> stream_names = {
56 {1, "Internal Microphone"}, {2, "External Microphone"}};
57
58 RCLCPP_INFO(this->get_logger(),
59 "Audio data received: stream_id=%u, vad_state=%u(%s), "
60 "audio_size=%zu bytes",
61 stream_id, vad_state,
62 vad_state_names.count(vad_state)
63 ? vad_state_names.at(vad_state).c_str()
64 : "Unknown State",
65 audio_data.size());
66
67 handle_vad_state(stream_id, vad_state, audio_data);
68 } catch (const std::exception &e) {
69 RCLCPP_ERROR(this->get_logger(), "Error processing audio message: %s",
70 e.what());
71 }
72 }
73
74 void handle_vad_state(uint32_t stream_id, uint8_t vad_state,
75 const std::vector<uint8_t> &audio_data) {
76 // Initialize the buffer for this stream_id (if it does not exist)
77 if (audio_buffers_.count(stream_id) == 0) {
78 audio_buffers_[stream_id] = std::vector<uint8_t>();
79 recording_state_[stream_id] = false;
80 }
81
82 static const std::unordered_map<uint8_t, std::string> vad_state_names = {
83 {0, "No Speech"},
84 {1, "Speech Start"},
85 {2, "Speech Processing"},
86 {3, "Speech End"}};
87 static const std::unordered_map<uint32_t, std::string> stream_names = {
88 {1, "Internal Microphone"}, {2, "External Microphone"}};
89
90 RCLCPP_INFO(this->get_logger(), "[%s] VAD Atate: %s Audio Data: %zu bytes",
91 stream_names.count(stream_id)
92 ? stream_names.at(stream_id).c_str()
93 : ("Unknown Stream " + std::to_string(stream_id)).c_str(),
94 vad_state_names.count(vad_state)
95 ? vad_state_names.at(vad_state).c_str()
96 : ("Unknown State" + std::to_string(vad_state)).c_str(),
97 audio_data.size());
98
99 // AUDIO_VAD_STATE_BEGIN
100 if (vad_state == 1) {
101 RCLCPP_INFO(this->get_logger(), "🎤 Speech detected - Start");
102 if (recording_state_[stream_id] == false) {
103 audio_buffers_[stream_id].clear();
104 recording_state_[stream_id] = true;
105 }
106 if (!audio_data.empty()) {
107 audio_buffers_[stream_id].insert(audio_buffers_[stream_id].end(),
108 audio_data.begin(), audio_data.end());
109 }
110
111 // AUDIO_VAD_STATE_PROCESSING
112 } else if (vad_state == 2) {
113 RCLCPP_INFO(this->get_logger(), "🔄 Speech Processing...");
114 if (recording_state_[stream_id] && !audio_data.empty()) {
115 audio_buffers_[stream_id].insert(audio_buffers_[stream_id].end(),
116 audio_data.begin(), audio_data.end());
117 }
118
119 // AUDIO_VAD_STATE_END
120 } else if (vad_state == 3) {
121 RCLCPP_INFO(this->get_logger(), "✅ Speech End");
122 if (recording_state_[stream_id] && !audio_data.empty()) {
123 audio_buffers_[stream_id].insert(audio_buffers_[stream_id].end(),
124 audio_data.begin(), audio_data.end());
125 }
126 if (recording_state_[stream_id] && !audio_buffers_[stream_id].empty()) {
127 save_audio_segment(audio_buffers_[stream_id], stream_id);
128 }
129 recording_state_[stream_id] = false;
130
131 // AUDIO_VAD_STATE_NONE
132 } else if (vad_state == 0) {
133 if (recording_state_[stream_id]) {
134 RCLCPP_INFO(this->get_logger(), "⏹️ Recording state reset");
135 recording_state_[stream_id] = false;
136 }
137 }
138
139 // Output the current buffer status.
140 size_t buffer_size = audio_buffers_[stream_id].size();
141 bool recording = recording_state_[stream_id];
142 RCLCPP_DEBUG(this->get_logger(),
143 "[Stream %u] Buffer size: %zu bytes, Recording state: %s",
144 stream_id, buffer_size, recording ? "true" : "false");
145 }
146
147 void save_audio_segment(const std::vector<uint8_t> &audio_data,
148 uint32_t stream_id) {
149 if (audio_data.empty())
150 return;
151
152 // Get the current timestamp.
153 auto now = std::chrono::system_clock::now();
154 std::time_t t = std::chrono::system_clock::to_time_t(now);
155 auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
156 now.time_since_epoch()) %
157 1000;
158
159 std::ostringstream oss;
160 oss << std::put_time(std::localtime(&t), "%Y%m%d_%H%M%S") << "_"
161 << std::setw(3) << std::setfill('0') << ms.count();
162
163 // Create a subdirectory by stream_id.
164 fs::path stream_dir =
165 fs::path(audio_output_dir_) / ("stream_" + std::to_string(stream_id));
166 fs::create_directories(stream_dir);
167
168 static const std::unordered_map<uint32_t, std::string> stream_names = {
169 {1, "internal_mic"}, {2, "external_mic"}};
170 std::string stream_name = stream_names.count(stream_id)
171 ? stream_names.at(stream_id)
172 : ("stream_" + std::to_string(stream_id));
173 std::string filename = stream_name + "_" + oss.str() + ".pcm";
174 fs::path filepath = stream_dir / filename;
175
176 try {
177 std::ofstream ofs(filepath, std::ios::binary);
178 ofs.write(reinterpret_cast<const char *>(audio_data.data()),
179 audio_data.size());
180 ofs.close();
181 RCLCPP_INFO(this->get_logger(),
182 "Audio segment saved: %s (size: %zu bytes)", filepath.c_str(),
183 audio_data.size());
184
185 // Record audio file duration (assuming 16kHz, 16-bit, mono)
186 int sample_rate = 16000;
187 int bits_per_sample = 16;
188 int channels = 1;
189 int bytes_per_sample = bits_per_sample / 8;
190 size_t total_samples = audio_data.size() / (bytes_per_sample * channels);
191 double duration_seconds =
192 static_cast<double>(total_samples) / sample_rate;
193
194 RCLCPP_INFO(this->get_logger(),
195 "Audio duration: %.2f seconds (%zu samples)",
196 duration_seconds, total_samples);
197 } catch (const std::exception &e) {
198 RCLCPP_ERROR(this->get_logger(), "Failed to save audio file: %s",
199 e.what());
200 }
201 }
202
203 // Member variables
204 std::unordered_map<uint32_t, std::vector<uint8_t>> audio_buffers_;
205 std::unordered_map<uint32_t, bool> recording_state_;
206 std::string audio_output_dir_;
207 rclcpp::Subscription<aimdk_msgs::msg::ProcessedAudioOutput>::SharedPtr
208 subscription_;
209};
210
211int main(int argc, char **argv) {
212 rclcpp::init(argc, argv);
213 auto node = std::make_shared<AudioSubscriber>();
214 RCLCPP_INFO(node->get_logger(),
215 "Listening for denoised audio data, press Ctrl+C to exit...");
216 rclcpp::spin(node);
217 rclcpp::shutdown();
218 return 0;
219}
使用说明:
运行节点后会自动创建
audio_recordings目录音频文件按stream_id分类存储(暂不区分, 一律使用stream_1):
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
配合唤醒词触发VAD
输出示例:
[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.21 麦克风原始流式数据接收
该示例中用到了mic_raw_receiver,通过订阅/aima/hal/audio/capture话题来接收机器人的原始音频数据,自动按通道保存为PCM文件。
功能特点:
自动按时间戳创建输出目录
将多通道音频数据按通道拆分保存(麦克风通道/回采通道)
每秒刷新一次缓存数据到磁盘
支持麦克风切换检测
文件命名格式:channel_{index}_{type}.pcm,如
channel_0_mic.pcm(麦克风)channel_4_ref.pcm(回采)
1/**
2 * Microphone raw data receiving example
3 *
4 * This example subscribes to the `/aima/hal/audio/capture` topic to receive
5 * the robot's raw audio data. It supports both the built-in microphone and the
6 * external microphone audio streams, and automatically saves complete audio
7 * data as PCM files split by channel.
8 *
9 * Features:
10 * - Automatically saves raw audio as PCM files
11 * - Stores files categorized by timestamp and mic source
12 */
13
14#include <aimdk_msgs/msg/audio_capture.hpp>
15#include <chrono>
16#include <ctime>
17#include <filesystem>
18#include <fstream>
19#include <iomanip>
20#include <rclcpp/rclcpp.hpp>
21#include <sstream>
22#include <string>
23#include <unordered_map>
24#include <vector>
25
26namespace fs = std::filesystem;
27
28class RawAudioSubscriber : public rclcpp::Node {
29public:
30 RawAudioSubscriber() : rclcpp::Node("raw_audio_subscriber") {
31 mic_channels_ = 0;
32 ref_channels_ = 0;
33 inited_ = false;
34
35 // Create audio output directory named by current timestamp
36 auto now = std::chrono::system_clock::now();
37 auto time_t_now = std::chrono::system_clock::to_time_t(now);
38 auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
39 now.time_since_epoch()) %
40 1000;
41 std::ostringstream oss;
42 oss << std::put_time(std::localtime(&time_t_now), "%Y%m%d_%H%M%S_")
43 << std::setw(3) << std::setfill('0') << ms.count();
44 audio_output_dir_ = "audio_recordings/" + oss.str();
45 fs::create_directories(audio_output_dir_);
46
47 // Note: deep queue to avoid missing data
48 auto qos = rclcpp::QoS(rclcpp::KeepLast(500)).best_effort();
49
50 subscription_ = this->create_subscription<aimdk_msgs::msg::AudioCapture>(
51 "/aima/hal/audio/capture", qos,
52 std::bind(&RawAudioSubscriber::audio_callback, this,
53 std::placeholders::_1));
54
55 RCLCPP_INFO(this->get_logger(), "Start subscribing to raw audio data...");
56 }
57
58 void run() {
59 timer_ = this->create_wall_timer(
60 std::chrono::seconds(1),
61 std::bind(&RawAudioSubscriber::save_audio_segments, this));
62 rclcpp::spin(shared_from_this());
63 }
64
65private:
66 void audio_callback(const aimdk_msgs::msg::AudioCapture::SharedPtr msg) {
67 if (!inited_) {
68 mic_channels_ = msg->mic_channels;
69 ref_channels_ = msg->ref_channels;
70 inited_ = true;
71 } else if (mic_channels_ != msg->mic_channels ||
72 ref_channels_ != msg->ref_channels) {
73 // quit as MIC switched
74 RCLCPP_ERROR(
75 this->get_logger(),
76 "MIC channels info changed (mic:%u ref:%u) -> (mic:%u ref:%u)",
77 mic_channels_, ref_channels_, msg->mic_channels, msg->ref_channels);
78 rclcpp::shutdown();
79 return;
80 }
81
82 handle_audio_data(msg->data.data);
83 }
84
85 void handle_audio_data(const std::vector<uint8_t> &audio_data) {
86 // Split S16LE data into channels
87 int channels =
88 static_cast<int>(ref_channels_) + static_cast<int>(mic_channels_);
89 if (channels == 0)
90 return;
91
92 const int bytes_per_channel = 2;
93 const int unit_size = channels * bytes_per_channel;
94 size_t num_frames = audio_data.size() / unit_size;
95
96 for (size_t k = 0; k < num_frames; ++k) {
97 for (int i = 0; i < channels; ++i) {
98 size_t base = k * unit_size + i * bytes_per_channel;
99 audio_buffers_[i].push_back(audio_data[base]);
100 audio_buffers_[i].push_back(audio_data[base + 1]);
101 }
102 }
103 }
104
105 void save_audio_segments() {
106 RCLCPP_INFO(this->get_logger(), "Flashing cached audio data...");
107 int total =
108 static_cast<int>(mic_channels_) + static_cast<int>(ref_channels_);
109 for (int i = 0; i < total; ++i) {
110 bool is_ref = (i >= static_cast<int>(mic_channels_));
111 save_audio_segment(i, is_ref);
112 }
113 }
114
115 void save_audio_segment(int channel, bool is_ref) {
116 auto &buf = audio_buffers_[channel];
117 if (buf.empty())
118 return;
119
120 std::string channel_type = is_ref ? "ref" : "mic";
121 std::string filename =
122 "channel_" + std::to_string(channel) + "_" + channel_type + ".pcm";
123 fs::path filepath = fs::path(audio_output_dir_) / filename;
124
125 try {
126 // Append mode (equivalent to Python 'ab')
127 std::ofstream ofs(filepath, std::ios::binary | std::ios::app);
128 ofs.write(reinterpret_cast<const char *>(buf.data()), buf.size());
129 ofs.close();
130
131 RCLCPP_INFO(this->get_logger(),
132 "Audio segment saved: %s append: %zu bytes", filepath.c_str(),
133 buf.size());
134 buf.clear();
135 } catch (const std::exception &e) {
136 RCLCPP_ERROR(this->get_logger(), "Failed to save audio file: %s",
137 e.what());
138 }
139 }
140
141 bool inited_;
142 uint8_t mic_channels_;
143 uint8_t ref_channels_;
144 std::string audio_output_dir_;
145 std::unordered_map<int, std::vector<uint8_t>> audio_buffers_;
146 rclcpp::Subscription<aimdk_msgs::msg::AudioCapture>::SharedPtr subscription_;
147 rclcpp::TimerBase::SharedPtr timer_;
148};
149
150int main(int argc, char **argv) {
151 rclcpp::init(argc, argv);
152 auto node = std::make_shared<RawAudioSubscriber>();
153 RCLCPP_INFO(node->get_logger(),
154 "Listening to raw audio data, press Ctrl+C to exit...");
155 node->run();
156 rclcpp::shutdown();
157 return 0;
158}
使用说明:
ros2 run examples mic_raw_receiver
播放保存的PCM文件:
aplay -r 16000 -f S16_LE -c 1 audio_recordings/20250909_133650_123/channel_0_mic.pcm
6.2.22 原始流式音频播放
该示例中用到了play_audio_stream,先申请音频焦点,再将PCM文件以实时速率发布到/aima/hal/audio/playback话题进行播放;焦点被抢时自动退出。
1/**
2 * Raw audio stream playback example
3 *
4 * This example gets audio focus first and then publishes raw audio data at a
5 * fixed rate to the `/aima/hal/audio/playback` topic. It stops playing when
6 * audio focus is lost.
7 *
8 * Usage:
9 * ros2 run examples play_audio_stream --ros-args -p
10 * raw_audio_path:=/path/to/file.pcm
11 */
12
13#include <aimdk_msgs/msg/audio_data.hpp>
14#include <aimdk_msgs/msg/audio_info.hpp>
15#include <aimdk_msgs/msg/audio_playback.hpp>
16#include <aimdk_msgs/msg/common_state.hpp>
17#include <aimdk_msgs/msg/focus_requester.hpp>
18#include <aimdk_msgs/msg/focus_response.hpp>
19#include <aimdk_msgs/srv/abandon_audio_focus.hpp>
20#include <aimdk_msgs/srv/request_audio_focus.hpp>
21#include <chrono>
22#include <csignal>
23#include <fstream>
24#include <rclcpp/rclcpp.hpp>
25#include <string>
26#include <thread>
27#include <vector>
28
29// ---------------------------------------------------------------------------
30// FakeMicDevice: simulates a real-time microphone from a raw PCM file
31// ---------------------------------------------------------------------------
32class FakeMicDevice {
33public:
34 explicit FakeMicDevice(const std::string &raw_audio_file)
35 : channels_(1), sample_rate_(16000), bytes_per_sample_(2), index_(0),
36 start_time_(std::chrono::steady_clock::time_point{}) {
37 std::ifstream f(raw_audio_file, std::ios::binary);
38 if (!f.is_open()) {
39 throw std::runtime_error("Cannot open audio file: " + raw_audio_file);
40 }
41 audio_data_ = std::vector<uint8_t>(std::istreambuf_iterator<char>(f),
42 std::istreambuf_iterator<char>());
43 }
44
45 int get_channel_count() const { return channels_; }
46 int get_sample_rate() const { return sample_rate_; }
47
48 std::vector<uint8_t> get_cached_data() {
49 auto now = std::chrono::steady_clock::now();
50 if (start_time_ == std::chrono::steady_clock::time_point{}) {
51 // fake init: assume audio data started 1s ago
52 start_time_ = now - std::chrono::seconds(1);
53 }
54
55 double elapsed = std::chrono::duration<double>(now - start_time_).count();
56 size_t tail = static_cast<size_t>(elapsed * sample_rate_) * channels_ *
57 bytes_per_sample_;
58
59 std::vector<uint8_t> data;
60 if (tail < audio_data_.size()) {
61 data = std::vector<uint8_t>(audio_data_.begin() + index_,
62 audio_data_.begin() + tail);
63 index_ = tail;
64 } else {
65 // loopback
66 data =
67 std::vector<uint8_t>(audio_data_.begin() + index_, audio_data_.end());
68 index_ = 0;
69 start_time_ = now;
70 }
71 return data;
72 }
73
74private:
75 int channels_;
76 int sample_rate_;
77 int bytes_per_sample_;
78 size_t index_;
79 std::chrono::steady_clock::time_point start_time_;
80 std::vector<uint8_t> audio_data_;
81};
82
83// ---------------------------------------------------------------------------
84// AudioStreamPlayer node
85// ---------------------------------------------------------------------------
86using RequestAudioFocus = aimdk_msgs::srv::RequestAudioFocus;
87using AbandonAudioFocus = aimdk_msgs::srv::AbandonAudioFocus;
88
89class AudioStreamPlayer : public rclcpp::Node {
90public:
91 AudioStreamPlayer() : rclcpp::Node("audio_stream_player") {
92 this->declare_parameter<std::string>("raw_audio_path", "");
93 std::string raw_audio_path =
94 this->get_parameter("raw_audio_path").as_string();
95
96 mic_device_ = std::make_unique<FakeMicDevice>(raw_audio_path);
97
98 pkg_name_ = "audio_stream_player" + std::to_string(getpid());
99 focus_ = false;
100 focus_force_ = true;
101
102 RCLCPP_INFO(this->get_logger(), "local pkg name: %s", pkg_name_.c_str());
103
104 // Create focus request/release clients
105 request_client_ = this->create_client<RequestAudioFocus>(
106 "/aimdk_5Fmsgs/srv/RequestAudioFocus");
107 release_client_ = this->create_client<AbandonAudioFocus>(
108 "/aimdk_5Fmsgs/srv/AbandonAudioFocus");
109
110 // Wait for service to become available
111 while (!request_client_->wait_for_service(std::chrono::seconds(2))) {
112 RCLCPP_INFO(this->get_logger(), "Service unavailable, waiting...");
113 }
114 RCLCPP_INFO(this->get_logger(),
115 "Service available, ready to send request.");
116
117 // Subscriber: focus events
118 auto focus_qos = rclcpp::QoS(rclcpp::KeepLast(10)).reliable();
119 sub_ = this->create_subscription<aimdk_msgs::msg::FocusResponse>(
120 "/aima/hal/audio/focus_response", focus_qos,
121 std::bind(&AudioStreamPlayer::focus_event_callback, this,
122 std::placeholders::_1));
123
124 // Publisher: raw audio playback
125 pub_ = this->create_publisher<aimdk_msgs::msg::AudioPlayback>(
126 "/aima/hal/audio/playback", 10);
127 }
128
129 void send_request(bool focus) {
130 if (focus) {
131 send_focus_request();
132 } else {
133 send_focus_release();
134 }
135 }
136
137 bool is_holding_focus() const { return focus_ && focus_force_; }
138
139 void spin_until_focus() {
140 while (rclcpp::ok()) {
141 focus_force_ = true;
142 send_request(true);
143 if (is_holding_focus())
144 return;
145
146 RCLCPP_INFO(this->get_logger(), "need retry to get focus");
147 auto t0 = std::chrono::steady_clock::now();
148 while (rclcpp::ok()) {
149 auto t1 = std::chrono::steady_clock::now();
150 if (std::chrono::duration<double>(t1 - t0).count() >= 1.0)
151 break;
152 rclcpp::spin_some(shared_from_this());
153 std::this_thread::sleep_for(std::chrono::milliseconds(100));
154 }
155 }
156 }
157
158 void run() {
159 RCLCPP_INFO(this->get_logger(), "publishing audio data ...");
160 while (rclcpp::ok() && is_holding_focus()) {
161 run_once();
162 rclcpp::spin_some(shared_from_this());
163 std::this_thread::sleep_for(std::chrono::milliseconds(50));
164 }
165 if (!is_holding_focus()) {
166 RCLCPP_INFO(this->get_logger(), "focus out, exiting...");
167 }
168 }
169
170private:
171 void send_focus_request() {
172 auto req = std::make_shared<RequestAudioFocus::Request>();
173 aimdk_msgs::msg::FocusRequester requester;
174 requester.pkg_name = pkg_name_;
175 requester.priority = 6;
176 req->focus_requester = requester;
177
178 RCLCPP_INFO(this->get_logger(), "Sending RequestAudioFocus request");
179 std::shared_future<RequestAudioFocus::Response::SharedPtr> future;
180 for (int i = 0; i < 8; ++i) {
181 future = request_client_->async_send_request(req).future.share();
182 auto status = rclcpp::spin_until_future_complete(
183 shared_from_this(), future, std::chrono::milliseconds(250));
184 if (status == rclcpp::FutureReturnCode::SUCCESS)
185 break;
186 RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
187 }
188
189 auto response = future.get();
190 if (!response) {
191 RCLCPP_ERROR(this->get_logger(), "Service call failed or timed out.");
192 return;
193 }
194 if (response->reponse.status.value ==
195 aimdk_msgs::msg::CommonState::SUCCESS) {
196 focus_ = response->focus_response.focus_gain;
197 RCLCPP_INFO(this->get_logger(), "RequestAudioFocus done: focus %s",
198 focus_ ? "true" : "false");
199 } else {
200 RCLCPP_ERROR(this->get_logger(),
201 "Failed in response of RequestAudioFocus: %s",
202 response->reponse.message.c_str());
203 }
204 }
205
206 void send_focus_release() {
207 auto req = std::make_shared<AbandonAudioFocus::Request>();
208 aimdk_msgs::msg::FocusRequester requester;
209 requester.pkg_name = pkg_name_;
210 req->focus_requester = requester;
211
212 RCLCPP_INFO(this->get_logger(), "Sending AbandonAudioFocus request");
213 std::shared_future<AbandonAudioFocus::Response::SharedPtr> future;
214 for (int i = 0; i < 8; ++i) {
215 future = release_client_->async_send_request(req).future.share();
216 auto status = rclcpp::spin_until_future_complete(
217 shared_from_this(), future, std::chrono::milliseconds(250));
218 if (status == rclcpp::FutureReturnCode::SUCCESS)
219 break;
220 RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
221 }
222
223 auto response = future.get();
224 if (!response) {
225 RCLCPP_ERROR(this->get_logger(), "Service call failed or timed out.");
226 return;
227 }
228 if (response->reponse.status.value ==
229 aimdk_msgs::msg::CommonState::SUCCESS) {
230 // always focus False after abandon
231 focus_ = response->focus_response.focus_gain;
232 RCLCPP_INFO(this->get_logger(), "AbandonAudioFocus done: focus %s",
233 focus_ ? "true" : "false");
234 } else {
235 RCLCPP_ERROR(this->get_logger(),
236 "Failed in response of AbandonAudioFocus: %s",
237 response->reponse.message.c_str());
238 }
239 }
240
241 void
242 focus_event_callback(const aimdk_msgs::msg::FocusResponse::SharedPtr msg) {
243 if (msg->pkg_name == pkg_name_) {
244 RCLCPP_INFO(this->get_logger(),
245 "receive focus out event: focus state: %s",
246 msg->focus_gain ? "true" : "false");
247 focus_force_ = msg->focus_gain;
248 }
249 }
250
251 void run_once() {
252 aimdk_msgs::msg::AudioPlayback msg;
253 msg.pkg_name = pkg_name_;
254 msg.token_id = pkg_name_;
255
256 aimdk_msgs::msg::AudioInfo info;
257 info.channels = static_cast<uint8_t>(mic_device_->get_channel_count());
258 info.sample_rate = static_cast<uint32_t>(mic_device_->get_sample_rate());
259
260 aimdk_msgs::msg::AudioData audio_data;
261 audio_data.data = mic_device_->get_cached_data();
262
263 msg.info = info;
264 msg.data = audio_data;
265 pub_->publish(msg);
266 }
267
268 std::string pkg_name_;
269 bool focus_;
270 bool focus_force_;
271 std::unique_ptr<FakeMicDevice> mic_device_;
272 rclcpp::Client<RequestAudioFocus>::SharedPtr request_client_;
273 rclcpp::Client<AbandonAudioFocus>::SharedPtr release_client_;
274 rclcpp::Subscription<aimdk_msgs::msg::FocusResponse>::SharedPtr sub_;
275 rclcpp::Publisher<aimdk_msgs::msg::AudioPlayback>::SharedPtr pub_;
276};
277
278// ---------------------------------------------------------------------------
279// Signal handling
280// ---------------------------------------------------------------------------
281static std::shared_ptr<AudioStreamPlayer> g_node;
282
283void signal_handler(int sig) {
284 if (g_node) {
285 if (g_node->is_holding_focus()) {
286 g_node->send_request(false);
287 }
288 RCLCPP_INFO(g_node->get_logger(),
289 "Received signal %d, abandon audio focus and shutting down",
290 sig);
291 }
292 rclcpp::shutdown();
293}
294
295int main(int argc, char **argv) {
296 rclcpp::init(argc, argv);
297
298 try {
299 auto node = std::make_shared<AudioStreamPlayer>();
300 g_node = node;
301 std::signal(SIGINT, signal_handler);
302 std::signal(SIGTERM, signal_handler);
303
304 node->spin_until_focus();
305 node->run();
306
307 } catch (const std::exception &e) {
308 RCLCPP_ERROR(rclcpp::get_logger("main"),
309 "Program exited with exception: %s", e.what());
310 }
311
312 if (rclcpp::ok()) {
313 rclcpp::shutdown();
314 }
315 return 0;
316}
使用说明:
# 注意替换/path/to/your/raw_audio_file.pcm为原始音频文件路径,单通道,16kHz,S16LE
ros2 run examples play_audio_stream --ros-args -p raw_audio_path:=/path/to/your/raw_audio_file.pcm
6.2.23 表情控制
该示例中用到了play_emoji,通过该节点可实现表达指定的表情,用户可根据已有的表情列表来选择要变化的表情,具体表情列表可参考表情列表
1#include "aimdk_msgs/srv/play_emoji.hpp"
2#include "aimdk_msgs/msg/common_request.hpp"
3#include "rclcpp/rclcpp.hpp"
4#include <chrono>
5#include <memory>
6#include <signal.h>
7#include <string>
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 g_node.reset();
18 }
19 rclcpp::shutdown();
20 exit(signal);
21}
22
23class PlayEmojiClient : public rclcpp::Node {
24public:
25 PlayEmojiClient() : Node("play_emoji_client") {
26 client_ = this->create_client<aimdk_msgs::srv::PlayEmoji>(
27 "/face_ui_proxy/play_emoji");
28 RCLCPP_INFO(this->get_logger(), "✅ PlayEmoji client node started.");
29
30 // Wait for the service to become available
31 while (!client_->wait_for_service(std::chrono::seconds(2))) {
32 RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
33 }
34 RCLCPP_INFO(this->get_logger(),
35 "🟢 Service available, ready to send request.");
36 }
37
38 bool send_request(uint8_t emoji, uint8_t mode, int32_t priority) {
39 try {
40 auto request = std::make_shared<aimdk_msgs::srv::PlayEmoji::Request>();
41
42 request->emotion_id = emoji;
43 request->mode = mode;
44 request->priority = priority;
45
46 RCLCPP_INFO(
47 this->get_logger(),
48 "📨 Sending request to play emoji: id=%hhu, mode=%hhu, priority=%d",
49 emoji, mode, priority);
50
51 const std::chrono::milliseconds timeout(250);
52 for (int i = 0; i < 8; i++) {
53 request->header.header.stamp = this->now();
54 auto future = client_->async_send_request(request);
55 auto retcode = rclcpp::spin_until_future_complete(shared_from_this(),
56 future, timeout);
57 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
58 // retry as remote peer is NOT handled well by ROS
59 RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
60 continue;
61 }
62 // future.done
63 auto response = future.get();
64 if (response->success) {
65 RCLCPP_INFO(this->get_logger(),
66 "✅ Request to play emoji recorded successfully: %s",
67 response->message.c_str());
68 return true;
69 } else {
70 RCLCPP_ERROR(this->get_logger(),
71 "❌ Failed to record play-emoji request: %s",
72 response->message.c_str());
73 return false;
74 }
75 }
76 RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
77 return false;
78 } catch (const std::exception &e) {
79 RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
80 return false;
81 }
82 }
83
84private:
85 rclcpp::Client<aimdk_msgs::srv::PlayEmoji>::SharedPtr client_;
86};
87
88int main(int argc, char **argv) {
89 try {
90 rclcpp::init(argc, argv);
91
92 // Set up signal handlers
93 signal(SIGINT, signal_handler);
94 signal(SIGTERM, signal_handler);
95
96 int32_t priority = 10;
97
98 int emotion = 1; // Expression type, 1 means Blink
99 std::cout
100 << "Enter expression ID: 1-Blink, 60-Bored, 70-Abnormal, 80-Sleeping, "
101 "90-Happy, 190-Very Angry, 200-Adoration"
102 << std::endl;
103 std::cin >> emotion;
104
105 int mode = 1; // Playback mode, 1 means play once, 2 means loop
106 std::cout << "Enter play mode (1: once, 2: loop): ";
107 std::cin >> mode;
108 if (mode < 1 || mode > 2) {
109 RCLCPP_ERROR(rclcpp::get_logger("main"), "Invalid play mode: %d", mode);
110 rclcpp::shutdown();
111 return 1;
112 }
113
114 g_node = std::make_shared<PlayEmojiClient>();
115 auto client = std::dynamic_pointer_cast<PlayEmojiClient>(g_node);
116
117 if (client) {
118 client->send_request(emotion, mode, priority);
119 }
120
121 // Clean up resources
122 g_node.reset();
123 rclcpp::shutdown();
124
125 return 0;
126 } catch (const std::exception &e) {
127 RCLCPP_ERROR(rclcpp::get_logger("main"),
128 "Program exited with exception: %s", e.what());
129 return 1;
130 }
131}
6.2.24 地图构建
该示例中用到了slam,通过该节点给建图服务发送消息,实现建图功能
1#include <iostream>
2#include <rclcpp/rclcpp.hpp>
3#include <std_msgs/msg/string.hpp>
4#include <string>
5
6// Class definition for publishing commands to control SLAM operations
7class SlamCommandPublisher : public rclcpp::Node {
8public:
9 // Constructor for the SlamCommandPublisher class
10 SlamCommandPublisher()
11 : Node("slam_command_publisher") // Initialize the node with the name
12 // "slam_command_publisher"
13 {
14 // Match SLAM subscriber QoS: TRANSIENT_LOCAL + RELIABLE
15 rclcpp::QoS qos(10);
16 qos.transient_local().reliable();
17 publisher_ = this->create_publisher<std_msgs::msg::String>(
18 "/integrated_command", qos);
19 }
20
21 // Method to publish the "start_mapping" command
22 void publish_start_mapping() {
23 auto message = std_msgs::msg::String();
24 message.data = "start_mapping";
25 RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
26 publisher_->publish(message);
27 }
28
29 // Method to publish the "stop_mapping" command with a specified map name
30 void publish_stop_mapping(const std::string &map_name) {
31 auto message = std_msgs::msg::String();
32 message.data = "stop_mapping:" + map_name;
33 RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
34 publisher_->publish(message);
35 }
36
37private:
38 // Publisher object to publish messages to the "/integrated_command" topic
39 rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
40};
41
42int main(int argc, char *argv[]) {
43 // Initialize the ROS 2 communication system
44 rclcpp::init(argc, argv);
45 // Create an instance of the SlamCommandPublisher node
46 auto node = std::make_shared<SlamCommandPublisher>();
47
48 int input;
49 std::string map_name;
50
51 // Infinite loop to continuously accept user input
52 while (rclcpp::ok()) {
53 std::cout << "Enter 1 to start mapping, 2 to stop mapping: ";
54 std::cin >> input;
55
56 if (input == 1) {
57 // Publish the "start_mapping" command
58 node->publish_start_mapping();
59 } else if (input == 2) {
60 // Prompt the user to enter the map name
61 std::cout << "Enter map name: ";
62 std::cin >> map_name;
63 // Publish the "stop_mapping" command with the specified map name
64 node->publish_stop_mapping(map_name);
65 break;
66 } else {
67 // Handle invalid input
68 std::cout << "Invalid input. Please enter 1 or 2." << std::endl;
69 }
70 }
71
72 // Shutdown the ROS 2 communication system
73 rclcpp::shutdown();
74 return 0;
75}
使用说明:
ros2 run examples slam
Enter 1 to start mapping, 2 to stop mapping: 1
[INFO] [1772528788.353934160] [slam_command_publisher]: Publishing: 'start_mapping'
Enter 1 to start mapping, 2 to stop mapping: 2
Enter map name: testmap
[INFO] [1772528791.900924187] [slam_command_publisher]: Publishing: 'stop_mapping:testmap'
开始建图之后,推动机器,或者控制机器人移动,完成建图
6.2.25 使用地图进行重定位
该示例中用到地图构建步骤完成构建的地图进行定位
1#include <chrono>
2#include <functional>
3#include <geometry_msgs/msg/pose.hpp>
4#include <nav_msgs/msg/odometry.hpp>
5#include <rclcpp/rclcpp.hpp>
6#include <std_msgs/msg/string.hpp>
7
8using namespace std::chrono_literals;
9
10class RelocalizationNode : public rclcpp::Node {
11public:
12 RelocalizationNode() : Node("relocalization_node") {
13 // /integrated_command subscriber requires TRANSIENT_LOCAL durability
14 auto command_qos = rclcpp::QoS(rclcpp::KeepLast(10));
15 command_qos.transient_local();
16
17 integrated_command_pub_ = this->create_publisher<std_msgs::msg::String>(
18 "/integrated_command", command_qos);
19 relocalization_pose_pub_ = this->create_publisher<geometry_msgs::msg::Pose>(
20 "/relocalization_pose", 10);
21
22 // Create subscriber with BEST_EFFORT QoS
23 auto lidar_loc_qos = rclcpp::QoS(rclcpp::KeepLast(10));
24 lidar_loc_qos.best_effort();
25
26 odometry_sub_ = this->create_subscription<nav_msgs::msg::Odometry>(
27 "/slam/lidar_odom", lidar_loc_qos,
28 std::bind(&RelocalizationNode::odometryCallback, this,
29 std::placeholders::_1));
30
31 // Wait for subscribers before publishing
32 publish_timer_ = this->create_wall_timer(200ms, [this]() {
33 // Wait until both publishers have at least one subscriber
34 if (integrated_command_pub_->get_subscription_count() == 0 ||
35 relocalization_pose_pub_->get_subscription_count() == 0) {
36 RCLCPP_INFO_THROTTLE(
37 this->get_logger(), *this->get_clock(), 2000,
38 "Waiting for subscribers... (command: %zu, pose: %zu)",
39 integrated_command_pub_->get_subscription_count(),
40 relocalization_pose_pub_->get_subscription_count());
41 return;
42 }
43 publish_timer_->cancel();
44
45 // Publish integrated_command
46 auto integrated_command_msg = std_msgs::msg::String();
47 integrated_command_msg.data = "start_relocalization:1774430080403";
48 integrated_command_pub_->publish(integrated_command_msg);
49 RCLCPP_INFO(this->get_logger(), "Published integrated_command");
50
51 // Schedule relocalization_pose publication after delay
52 this->relocalization_pose_timer_ = this->create_wall_timer(1s, [this]() {
53 this->relocalization_pose_timer_->cancel();
54
55 auto relocalization_pose_msg = geometry_msgs::msg::Pose();
56 relocalization_pose_msg.position.x = 273;
57 relocalization_pose_msg.position.y = 200;
58 relocalization_pose_msg.position.z = 0.0;
59 relocalization_pose_msg.orientation.x = 0.0;
60 relocalization_pose_msg.orientation.y = 0.0;
61 relocalization_pose_msg.orientation.z = 0.0;
62 relocalization_pose_msg.orientation.w = 1.0;
63
64 relocalization_pose_pub_->publish(relocalization_pose_msg);
65 RCLCPP_INFO(this->get_logger(), "Published relocalization_pose");
66
67 // Start timeout timer (5 seconds)
68 this->timeout_timer_ = this->create_wall_timer(30s, [this]() {
69 if (!success_received_) {
70 RCLCPP_ERROR(this->get_logger(),
71 "Timeout reached - Relocalization failed!");
72 rclcpp::shutdown();
73 }
74 });
75
76 RCLCPP_INFO(this->get_logger(),
77 "Waiting for robot pose data (timeout: 30s)...");
78 });
79 });
80 }
81
82private:
83 // get robot pose here
84 void odometryCallback(const nav_msgs::msg::Odometry::SharedPtr msg) {
85 if (!success_received_) {
86 success_received_ = true;
87 if (timeout_timer_) {
88 timeout_timer_->cancel();
89 }
90 RCLCPP_INFO(this->get_logger(),
91 "Received odometry data - Relocalization successful!");
92 rclcpp::shutdown();
93 }
94 }
95
96 rclcpp::Publisher<std_msgs::msg::String>::SharedPtr integrated_command_pub_;
97 rclcpp::Publisher<geometry_msgs::msg::Pose>::SharedPtr
98 relocalization_pose_pub_;
99 rclcpp::Subscription<nav_msgs::msg::Odometry>::SharedPtr odometry_sub_;
100
101 // Timers
102 rclcpp::TimerBase::SharedPtr publish_timer_;
103 rclcpp::TimerBase::SharedPtr relocalization_pose_timer_;
104 rclcpp::TimerBase::SharedPtr timeout_timer_;
105
106 bool success_received_ = false;
107};
108
109int main(int argc, char *argv[]) {
110 rclcpp::init(argc, argv);
111 auto node = std::make_shared<RelocalizationNode>();
112 rclcpp::spin(node);
113 rclcpp::shutdown();
114 return 0;
115}
使用说明:
ros2 run examples relocate
[INFO] [1772528939.129777359] [relocalization_node]: Published integrated_command
[INFO] [1772528940.130067255] [relocalization_node]: Published relocalization_pose
[INFO] [1772528940.130129806] [relocalization_node]: Waiting for robot pose data (timeout: 5s)...
[INFO] [1772528945.130242770] [relocalization_node]: Received odometry data - Relocalization successful!
需要将机器人移动到地图构建的起始点,然后运行重定位程序
参数获取方式:map_id 与起点像素坐标(position.x / position.y)的获取见 地图标识与坐标的获取。
6.2.26 获取已存储的地图
该示例中用到了get_map,按地图名称获取机器人上已存储的地图数据,包括地图路径、分辨率、宽高及地图ID等信息。
1//
2// Created by agiuser on 2026/3/2.
3//
4
5#include "aimdk_msgs/srv/get_stored_map_by_name.hpp"
6#include "rclcpp/rclcpp.hpp"
7#include <memory>
8
9class MapClient : public rclcpp::Node {
10public:
11 MapClient() : Node("get_map_client") {
12 client_ = this->create_client<aimdk_msgs::srv::GetStoredMapByName>(
13 "/aimdk_5Fmsgs/srv/GetStoredMapByName");
14 }
15
16 void send_request(const std::string &map_name) {
17 while (!client_->wait_for_service(std::chrono::seconds(1))) {
18 if (!rclcpp::ok()) {
19 RCLCPP_ERROR(this->get_logger(),
20 "Interrupted while waiting for service.");
21 return;
22 }
23 RCLCPP_INFO(this->get_logger(),
24 "Service not available, waiting again...");
25 }
26
27 auto request =
28 std::make_shared<aimdk_msgs::srv::GetStoredMapByName::Request>();
29
30 request->header.stamp = this->now();
31 request->header.frame_id = "";
32
33 request->map_name = map_name;
34
35 auto result_future = client_->async_send_request(request);
36
37 // Use a fresh executor to spin, avoiding any pre-existing state.
38 rclcpp::executors::SingleThreadedExecutor exec;
39 exec.add_node(shared_from_this());
40 if (exec.spin_until_future_complete(result_future,
41 std::chrono::seconds(10)) !=
42 rclcpp::FutureReturnCode::SUCCESS) {
43 RCLCPP_ERROR(this->get_logger(), "Service call failed or timed out.");
44 return;
45 }
46
47 auto result = result_future.get();
48
49 if (result->code == 0) {
50 RCLCPP_INFO(this->get_logger(), "Service call succeeded");
51 RCLCPP_INFO(this->get_logger(), "Map path: %s", result->map_path.c_str());
52 RCLCPP_INFO(this->get_logger(), "Map resolution: %f",
53 result->map_info.resolution);
54 RCLCPP_INFO(this->get_logger(), "Map width: %u", result->map_info.width);
55 RCLCPP_INFO(this->get_logger(), "Map height: %u",
56 result->map_info.height);
57 RCLCPP_INFO(this->get_logger(), "Map id: %ld", result->map_id);
58 } else {
59 RCLCPP_ERROR(this->get_logger(), "Service call failed with code: %lu",
60 result->code);
61 }
62 }
63
64private:
65 rclcpp::Client<aimdk_msgs::srv::GetStoredMapByName>::SharedPtr client_;
66};
67
68int main(int argc, char **argv) {
69 rclcpp::init(argc, argv);
70
71 auto client = std::make_shared<MapClient>();
72 client->send_request("map_name");
73
74 rclcpp::shutdown();
75 return 0;
76}
使用说明:
ros2 run examples get_map
[INFO] [get_map_client]: Service call succeeded
[INFO] [get_map_client]: Map path: /path/to/test_map.png
[INFO] [get_map_client]: Map resolution: 0.050000
[INFO] [get_map_client]: Map width: 200
[INFO] [get_map_client]: Map height: 200
[INFO] [get_map_client]: Map id: 1
示例默认请求名称为 test_map 的地图,可在代码中修改 send_request 的参数以请求其他地图。
参数获取方式:输入的是地图名称(map_name),不是 map_id;map_name 的获取见 地图标识与坐标的获取。
6.2.27 播放灵创动作
该示例中用到了play_linkcraft,列出机器人上的灵创动作,并且选择一条进行播放
1//
2// Created by agiuser on 2026/3/3.
3//
4
5#include <aimdk_msgs/srv/execute_action_resource.hpp>
6#include <aimdk_msgs/srv/get_robot_resources.hpp>
7#include <iostream>
8#include <rclcpp/rclcpp.hpp>
9#include <string>
10#include <vector>
11
12using namespace std::chrono_literals;
13
14class RobotResourceClient : public rclcpp::Node {
15public:
16 RobotResourceClient() : Node("robot_resource_client") {
17 get_resource_client_ =
18 this->create_client<aimdk_msgs::srv::GetRobotResources>(
19 "/aimdk_5Fmsgs/srv/GetRobotResources");
20 play_resource_client_ =
21 this->create_client<aimdk_msgs::srv::ExecuteActionResource>(
22 "/aimdk_5Fmsgs/srv/ExecuteActionResource");
23 }
24
25 bool get_robot_resources() {
26 while (!get_resource_client_->wait_for_service(1s)) {
27 if (!rclcpp::ok()) {
28 return false;
29 }
30 RCLCPP_INFO(this->get_logger(),
31 "Service not available, waiting again...");
32 }
33
34 if (!rclcpp::ok()) {
35 return false;
36 }
37
38 auto request =
39 std::make_shared<aimdk_msgs::srv::GetRobotResources::Request>();
40
41 auto future_result = get_resource_client_->async_send_request(request);
42
43 if (rclcpp::spin_until_future_complete(this->get_node_base_interface(),
44 future_result) !=
45 rclcpp::FutureReturnCode::SUCCESS) {
46 if (rclcpp::ok()) {
47 RCLCPP_ERROR(this->get_logger(), "Failed to call GetRobotResources");
48 }
49 return false;
50 }
51
52 auto response = future_result.get();
53 resources_ = response->robot_resources;
54 RCLCPP_INFO(this->get_logger(), "Found %zu resources", resources_.size());
55 return true;
56 }
57
58 void print_resource_menu() {
59 if (resources_.empty()) {
60 std::cout << "(no resources)\n";
61 return;
62 }
63
64 std::cout << "\n=== Available Robot Resources ===\n";
65 for (size_t i = 0; i < resources_.size(); ++i) {
66 const auto &ver = resources_[i].current_version;
67 std::cout << "[" << i << "] "
68 << (ver.name.empty() ? "<no name>" : ver.name) << "\n";
69 std::cout << " key: " << resources_[i].resource_key << "\n";
70 std::cout << " version: " << ver.version << "\n";
71 }
72 std::cout << "=================================\n\n";
73 }
74
75 int pick_resource() {
76 if (resources_.empty()) {
77 return -1;
78 }
79
80 std::string line;
81 while (true) {
82 std::cout << "Select resource index [0-" << resources_.size() - 1
83 << "] (q to quit): ";
84 if (!std::getline(std::cin, line)) {
85 return -1;
86 }
87 if (line == "q" || line == "quit" || line == "exit") {
88 return -1;
89 }
90 try {
91 int idx = std::stoi(line);
92 if (idx >= 0 && idx < static_cast<int>(resources_.size())) {
93 return idx;
94 }
95 std::cout << "Index out of range (0-" << resources_.size() - 1
96 << ").\n";
97 } catch (...) {
98 std::cout << "Please enter a number.\n";
99 }
100 }
101 }
102
103 void play_resource(int idx) {
104 while (!play_resource_client_->wait_for_service(1s)) {
105 if (!rclcpp::ok()) {
106 RCLCPP_ERROR(this->get_logger(),
107 "Interrupted while waiting for service.");
108 return;
109 }
110 RCLCPP_INFO(this->get_logger(), "Service not available, waiting...");
111 }
112
113 if (!rclcpp::ok()) {
114 return;
115 }
116
117 const auto &resource = resources_[idx];
118 auto request =
119 std::make_shared<aimdk_msgs::srv::ExecuteActionResource::Request>();
120
121 request->resource_key = resource.resource_key;
122 request->resource_version = resource.current_version.version;
123
124 if (request->resource_key.find("onnx") != std::string::npos) {
125 request->meta = R"({"resource_type": "BODY_MONTION"})";
126 } else {
127 request->meta = R"({"resource_type": "ARM_MONTION"})";
128 }
129
130 RCLCPP_INFO(this->get_logger(), "Sending request:");
131 RCLCPP_INFO(this->get_logger(), " resource_key: %s",
132 request->resource_key.c_str());
133 RCLCPP_INFO(this->get_logger(), " resource_version: %s",
134 request->resource_version.c_str());
135 RCLCPP_INFO(this->get_logger(), " meta: %s", request->meta.c_str());
136
137 auto future = play_resource_client_->async_send_request(request);
138
139 if (rclcpp::spin_until_future_complete(this->get_node_base_interface(),
140 future) ==
141 rclcpp::FutureReturnCode::SUCCESS) {
142 handle_response(future.get());
143 } else {
144 if (rclcpp::ok()) {
145 RCLCPP_ERROR(this->get_logger(),
146 "Failed to call ExecuteActionResource");
147 }
148 }
149 }
150
151private:
152 rclcpp::Client<aimdk_msgs::srv::GetRobotResources>::SharedPtr
153 get_resource_client_;
154 rclcpp::Client<aimdk_msgs::srv::ExecuteActionResource>::SharedPtr
155 play_resource_client_;
156 std::vector<aimdk_msgs::msg::RobotResource> resources_;
157
158 void handle_response(
159 const aimdk_msgs::srv::ExecuteActionResource::Response::SharedPtr
160 response) {
161 RCLCPP_INFO(this->get_logger(), "Service response received:");
162 RCLCPP_INFO(this->get_logger(), "Response Header:");
163 RCLCPP_INFO(this->get_logger(), " stamp: %d.%09u",
164 response->header.header.stamp.sec,
165 response->header.header.stamp.nanosec);
166 RCLCPP_INFO(this->get_logger(), " code: %ld",
167 response->header.header.code);
168 RCLCPP_INFO(this->get_logger(), "Status:");
169 RCLCPP_INFO(this->get_logger(), " value: %d",
170 response->header.status.value);
171 RCLCPP_INFO(this->get_logger(), " message: %s",
172 response->header.message.c_str());
173 }
174};
175
176int main(int argc, char **argv) {
177 rclcpp::init(argc, argv);
178 auto client_node = std::make_shared<RobotResourceClient>();
179
180 if (!client_node->get_robot_resources()) {
181 rclcpp::shutdown();
182 return 1;
183 }
184
185 client_node->print_resource_menu();
186 int chosen = client_node->pick_resource();
187 if (chosen < 0) {
188 RCLCPP_INFO(client_node->get_logger(), "No resource selected, exiting.");
189 rclcpp::shutdown();
190 return 0;
191 }
192
193 client_node->play_resource(chosen);
194 rclcpp::shutdown();
195 return 0;
196}
使用说明:
ros2 run examples play_linkcraft
[INFO] [1772529153.517267476] [robot_resource_client]: Service call succeeded
[INFO] [1772529153.517416880] [robot_resource_client]: Robot Resources (0):
[INFO] [1772529153.517473238] [robot_resource_client]: Sending request:
[INFO] [1772529153.517488347] [robot_resource_client]: resource_key: linkcraft_resource_onnx_01KBM2BHNFM93Z0DK6ES9F6DJA
[INFO] [1772529153.517497834] [robot_resource_client]: resource_version: 0.0.1
[INFO] [1772529153.517505889] [robot_resource_client]: meta: {"resource_type": "BODY_MONTION"}
[INFO] [1772529153.553673433] [robot_resource_client]: Service response received:
[INFO] [1772529153.553697021] [robot_resource_client]: Response Header:
[INFO] [1772529153.553700410] [robot_resource_client]: stamp: 0.000000000
[INFO] [1772529153.553704130] [robot_resource_client]: code: 0
[INFO] [1772529153.553707001] [robot_resource_client]: Status:
[INFO] [1772529153.553709853] [robot_resource_client]: value: 1
[INFO] [1772529153.553712662] [robot_resource_client]: message: 1/1 task(s) failed. Details: localhost: failed (status code: 400) - Failed with status code: 400
6.2.28 LED灯带控制
功能说明:演示如何控制机器人的LED灯带,支持多种显示模式和自定义颜色。
注意
使用本功能前,请在运控计算单元(PC1)上执行以下命令关闭 task_manager 模块,避免其与二开程序争抢灯带控制权:
aima em stop-app task_manager
核心代码:
1#include <aimdk_msgs/msg/common_request.hpp>
2#include <aimdk_msgs/srv/set_pmu_led.hpp>
3#include <chrono>
4#include <memory>
5#include <rclcpp/rclcpp.hpp>
6#include <signal.h>
7#include <string>
8
9std::shared_ptr<rclcpp::Node> g_node = nullptr;
10
11void signal_handler(int signal) {
12 if (g_node) {
13 RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
14 signal);
15 g_node.reset();
16 }
17 rclcpp::shutdown();
18 exit(signal);
19}
20
21class PlayLightsClient : public rclcpp::Node {
22public:
23 PlayLightsClient() : Node("play_lights_client") {
24 client_ = this->create_client<aimdk_msgs::srv::SetPmuLed>(
25 "/aimdk_5Fmsgs/srv/SetPmuLed");
26 RCLCPP_INFO(this->get_logger(), "✅ PlayLights client node started.");
27
28 // Wait for the service to become available
29 while (!client_->wait_for_service(std::chrono::seconds(2))) {
30 RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
31 }
32 RCLCPP_INFO(this->get_logger(),
33 "🟢 Service available, ready to send request.");
34 }
35
36 bool send_request(uint8_t led_mode, uint8_t r, uint8_t g, uint8_t b,
37 int32_t priority = 6, bool reset_priority = false) {
38 try {
39 auto request = std::make_shared<aimdk_msgs::srv::SetPmuLed::Request>();
40
41 request->led_strip_mode = led_mode;
42 request->r = r;
43 request->g = g;
44 request->b = b;
45 request->priority = priority;
46 request->reset_priority = reset_priority;
47 request->trace_id = "play_lights";
48
49 RCLCPP_INFO(this->get_logger(),
50 "📨 Sending request to control led strip: mode=%hhu, "
51 "RGB=(%hhu, %hhu, %hhu), priority=%d, reset_priority=%s",
52 led_mode, r, g, b, priority,
53 reset_priority ? "true" : "false");
54
55 // LED strip is slow to response (up to ~5s)
56 const std::chrono::milliseconds timeout(5000);
57 for (int i = 0; i < 4; i++) {
58 request->request.header.stamp = this->now();
59 auto future = client_->async_send_request(request);
60 auto retcode = rclcpp::spin_until_future_complete(
61 this->shared_from_this(), future, timeout);
62
63 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
64 // retry as remote peer is NOT handled well by ROS
65 RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
66 continue;
67 }
68 // future.done
69 auto response = future.get();
70 if (response->status_code == 0) {
71 RCLCPP_INFO(this->get_logger(),
72 "✅ LED strip command sent successfully.");
73 return true;
74 } else {
75 RCLCPP_ERROR(this->get_logger(),
76 "❌ LED strip command failed with status: %d",
77 response->status_code);
78 return false;
79 }
80 }
81 RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
82 return false;
83 } catch (const std::exception &e) {
84 RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
85 return false;
86 }
87 }
88
89private:
90 rclcpp::Client<aimdk_msgs::srv::SetPmuLed>::SharedPtr client_;
91};
92
93int main(int argc, char **argv) {
94 try {
95 rclcpp::init(argc, argv);
96 signal(SIGINT, signal_handler);
97 signal(SIGTERM, signal_handler);
98
99 g_node = std::make_shared<PlayLightsClient>();
100 auto client_node = std::dynamic_pointer_cast<PlayLightsClient>(g_node);
101
102 int led_mode = 0; // LED Strip Mode
103 int r = 255, g = 0, b = 0; // RGB values
104 int priority = 6; // Priority
105 int reset_priority_input = 0; // Reset priority flag
106
107 std::cout << "=== LED Strip Control Example ===" << std::endl;
108 std::cout << "Select LED strip mode:" << std::endl;
109 std::cout << "0 - Steady On" << std::endl;
110 std::cout << "1 - Breathing (4s period, sinusoidal brightness)"
111 << std::endl;
112 std::cout << "2 - Blinking (1s period, 0.5s on, 0.5s off)" << std::endl;
113 std::cout << "3 - Flow (2s period, lights turn on left to right)"
114 << std::endl;
115 std::cout << "Enter mode (0-3): ";
116 std::cin >> led_mode;
117
118 std::cout << "\nSet RGB color values (0-255):" << std::endl;
119 std::cout << "Red component (R): ";
120 std::cin >> r;
121 std::cout << "Green component (G): ";
122 std::cin >> g;
123 std::cout << "Blue component (B): ";
124 std::cin >> b;
125
126 std::cout << "\nSet priority (higher value = higher priority, default=6): ";
127 std::cin >> priority;
128
129 std::cout << "Reset priority after command? (1=yes, 0=no, default=0): ";
130 std::cin >> reset_priority_input;
131
132 // clamp mode to range 0-3
133 led_mode = std::max(0, std::min(3, led_mode));
134 // clamp r/g/b to range 0-255
135 r = std::max(0, std::min(255, r));
136 g = std::max(0, std::min(255, g));
137 b = std::max(0, std::min(255, b));
138 // clamp priority to non-negative
139 priority = std::max(0, priority);
140
141 if (client_node) {
142 client_node->send_request(led_mode, r, g, b, priority,
143 reset_priority_input != 0);
144 }
145
146 g_node.reset();
147 rclcpp::shutdown();
148
149 return 0;
150 } catch (const std::exception &e) {
151 RCLCPP_ERROR(rclcpp::get_logger("main"),
152 "Program terminated with exception: %s", e.what());
153 return 1;
154 }
155}
使用说明:
# 构建
colcon build --packages-select examples
# 运行
ros2 run examples play_lights
输出示例:
=== LED Strip Control Example ===
Select LED strip mode:
0 - Steady On
1 - Breathing (4s period, sinusoidal brightness)
2 - Blinking (1s period, 0.5s on, 0.5s off)
3 - Flow (2s period, lights turn on left to right)
Enter mode (0-3): 1
Set RGB color values (0-255):
Red component (R): 255
Green component (G): 0
Blue component (B): 0
Set priority (higher value = higher priority, default=6): 6
Reset priority after command? (1=yes, 0=no, default=0): 0
[INFO] [play_lights_client]: 📨 Sending request to control led strip: mode=1, RGB=(255, 0, 0), priority=6, reset_priority=false
[INFO] [play_lights_client]: ✅ LED strip command sent successfully.
技术特点:
支持4种LED显示模式
RGB颜色自定义
优先级控制(值越大优先级越高)
优先级重置功能
异步服务调用
输入参数验证
友好的用户交互界面
6.2.29 获取当前麦克风
1#include "aimdk_msgs/msg/common_request.hpp"
2#include "aimdk_msgs/msg/common_state.hpp"
3#include "aimdk_msgs/srv/get_mic_source_request.hpp"
4#include "rclcpp/rclcpp.hpp"
5#include <chrono>
6#include <memory>
7#include <signal.h>
8
9std::shared_ptr<rclcpp::Node> g_node = nullptr;
10
11void signal_handler(int signal) {
12 if (g_node) {
13 RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
14 signal);
15 g_node.reset();
16 }
17 rclcpp::shutdown();
18 exit(signal);
19}
20
21class GetMicSourceRequestClient : public rclcpp::Node {
22public:
23 GetMicSourceRequestClient() : Node("get_mic_source_request_client") {
24 client_ = this->create_client<aimdk_msgs::srv::GetMicSourceRequest>(
25 "/aimdk_5Fmsgs/srv/GetMicSourceRequest");
26
27 RCLCPP_INFO(this->get_logger(),
28 "✅ GetMicSourceRequest client node created.");
29
30 // Wait for the service to become available
31 while (!client_->wait_for_service(std::chrono::seconds(2))) {
32 RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
33 }
34 RCLCPP_INFO(this->get_logger(),
35 "🟢 Service available, ready to send request.");
36 }
37
38 void send_request() {
39 try {
40 auto request =
41 std::make_shared<aimdk_msgs::srv::GetMicSourceRequest::Request>();
42 request->header = aimdk_msgs::msg::CommonRequest();
43
44 RCLCPP_INFO(this->get_logger(), "📨 Sending request to get MIC source");
45
46 auto timeout = std::chrono::milliseconds(250);
47 for (int i = 0; i < 8; i++) {
48 request->header.header.stamp = this->now();
49 auto future = client_->async_send_request(request);
50 auto retcode = rclcpp::spin_until_future_complete(
51 this->shared_from_this(), future, timeout);
52 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
53 // retry as remote peer is NOT handled well by ROS
54 RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
55 continue;
56 }
57 // future.done
58 auto response = future.get();
59 if (response->header.status.value ==
60 aimdk_msgs::msg::CommonState::SUCCESS) {
61 RCLCPP_INFO(this->get_logger(), "✅ MIC source get successfully.");
62 RCLCPP_INFO(this->get_logger(), "MIC id: %d", response->mic_source);
63 } else {
64 RCLCPP_ERROR(this->get_logger(), "❌ Failed to get MIC source: %s",
65 response->header.message.c_str());
66 }
67 return;
68 }
69 RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
70 } catch (const std::exception &e) {
71 RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
72 }
73 }
74
75private:
76 rclcpp::Client<aimdk_msgs::srv::GetMicSourceRequest>::SharedPtr client_;
77};
78
79int main(int argc, char *argv[]) {
80 try {
81 rclcpp::init(argc, argv);
82 signal(SIGINT, signal_handler);
83 signal(SIGTERM, signal_handler);
84
85 g_node = std::make_shared<GetMicSourceRequestClient>();
86 auto client = std::dynamic_pointer_cast<GetMicSourceRequestClient>(g_node);
87
88 if (client) {
89 client->send_request();
90 }
91
92 g_node.reset();
93 rclcpp::shutdown();
94 return 0;
95 } catch (const std::exception &e) {
96 RCLCPP_ERROR(rclcpp::get_logger("main"),
97 "Program exited with exception: %s", e.what());
98 return 1;
99 }
100}
6.2.30 切换麦克风
1#include "aimdk_msgs/msg/common_request.hpp"
2#include "aimdk_msgs/msg/common_state.hpp"
3#include "aimdk_msgs/srv/set_mic_source_request.hpp"
4#include "rclcpp/rclcpp.hpp"
5#include <chrono>
6#include <map>
7#include <memory>
8#include <signal.h>
9#include <string>
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 g_node.reset();
18 }
19 rclcpp::shutdown();
20 exit(signal);
21}
22
23class SetMicSourceRequestClient : public rclcpp::Node {
24public:
25 SetMicSourceRequestClient() : Node("set_mic_source_request_client") {
26 client_ = this->create_client<aimdk_msgs::srv::SetMicSourceRequest>(
27 "/aimdk_5Fmsgs/srv/SetMicSourceRequest");
28
29 RCLCPP_INFO(this->get_logger(),
30 "✅ SetMicSourceRequest client node created.");
31
32 // Wait for the service to become available
33 while (!client_->wait_for_service(std::chrono::seconds(2))) {
34 RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
35 }
36 RCLCPP_INFO(this->get_logger(),
37 "🟢 Service available, ready to send request.");
38 }
39
40 void send_request(int mic_source) {
41 try {
42 auto request =
43 std::make_shared<aimdk_msgs::srv::SetMicSourceRequest::Request>();
44 request->header = aimdk_msgs::msg::CommonRequest();
45 request->mic_source = mic_source;
46
47 RCLCPP_INFO(this->get_logger(), "📨 Sending request to set mic source: %d",
48 mic_source);
49
50 auto timeout = std::chrono::milliseconds(250);
51 for (int i = 0; i < 8; i++) {
52 request->header.header.stamp = this->now();
53 auto future = client_->async_send_request(request);
54 auto retcode = rclcpp::spin_until_future_complete(
55 this->shared_from_this(), future, timeout);
56 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
57 // retry as remote peer is NOT handled well by ROS
58 RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
59 continue;
60 }
61 // future.done
62 auto response = future.get();
63 if (response->header.status.value ==
64 aimdk_msgs::msg::CommonState::SUCCESS) {
65 RCLCPP_INFO(this->get_logger(), "✅ MIC source set successfully.");
66 } else {
67 RCLCPP_ERROR(this->get_logger(), "❌ Failed to set MIC source: %s",
68 response->header.message.c_str());
69 }
70 return;
71 }
72 RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
73 } catch (const std::exception &e) {
74 RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
75 }
76 }
77
78private:
79 rclcpp::Client<aimdk_msgs::srv::SetMicSourceRequest>::SharedPtr client_;
80};
81
82int main(int argc, char *argv[]) {
83 // abbr -> mic_id mapping
84 // 0: internal MIC, 1: external MIC
85 const std::map<std::string, int> choices = {{"int", 0}, {"ext", 1}};
86
87 try {
88 rclcpp::init(argc, argv);
89 signal(SIGINT, signal_handler);
90 signal(SIGTERM, signal_handler);
91
92 std::string mic_abbr;
93 if (argc > 1) {
94 mic_abbr = argv[1];
95 } else {
96 printf("%-4s - %-6s : %s\n", "abbr", "mic_id", "description");
97 printf("%-4s - %-6d : %s\n", "int", 0, "internal MIC");
98 printf("%-4s - %-6d : %s\n", "ext", 1, "external MIC");
99 printf("Enter abbr of MIC source: ");
100 std::cin >> mic_abbr;
101 }
102
103 auto it = choices.find(mic_abbr);
104 if (it == choices.end()) {
105 RCLCPP_ERROR(rclcpp::get_logger("main"), "Invalid abbr of MIC source: %s",
106 mic_abbr.c_str());
107 return 1;
108 }
109 int mic_id = it->second;
110
111 g_node = std::make_shared<SetMicSourceRequestClient>();
112 auto client = std::dynamic_pointer_cast<SetMicSourceRequestClient>(g_node);
113
114 if (client) {
115 client->send_request(mic_id);
116 }
117
118 g_node.reset();
119 rclcpp::shutdown();
120 return 0;
121 } catch (const std::exception &e) {
122 RCLCPP_ERROR(rclcpp::get_logger("main"),
123 "Program exited with exception: %s", e.what());
124 return 1;
125 }
126}
使用说明
# 构建
colcon build --packages-select examples
# 交互式运行
ros2 run examples set_mic_source
# 命令行参数运行
ros2 run examples set_mic_source ext # ext - 外置麦, int - 内置麦