6.2 C++ Interface Usage Example
This section will guide you through implementing the functions listed in the index
Build & Run Instructions
Enter the top-level AimDK directory (~/aimdk on the robot) and run the following commands
source /opt/ros/humble/setup.bash colcon build source install/local_setup.bash ros2 run examples 'function name, e.g., get_mc_action'
📝 Code Notes
The full implementation includes complete mechanisms for error handling, signal handling, timeout handling, and more, ensuring the robustness of the program. Please check/modify in the examples directory
Caution
As standard ROS DO NOT handle cross-host service (request-response) well, please refer to SDK examples to use open interfaces in a robust way (with protection mechanisms e.g. exception safety and retransmission)
While the robot is in Stable Standing Mode or Locomotion Mode, DO NOT launch ROS nodes in rapid bulk (no more than 2 nodes per second is recommended), as a large number of nodes joining DDS discovery within a short period causes communication congestion and degrades motion control real-time performance, which may cause the robot to lose balance and fall
6.2.1 Developer Mode
Invoke the 'GetSystemState' service to obtain the robot’s current system status, and use 'MigrateSystemState' to access the corresponding developer mode.
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) { // SUCCESS (1)
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 // Special case: Ready auto-transitions to Business, so also accept
263 // Business when the target is Ready
264 // 2. System status equals 1 (IN_READY), or the target is Ready and
265 // the system has already auto-transitioned to Business (IN_MOVE)
266 bool state_match =
267 (current_state_lower == target_state_lower) ||
268 (target_state_lower == "ready" && current_state_lower == "business");
269 bool ready_auto_transitioned =
270 (target_state_lower == "ready" && current_state_lower == "business");
271 bool status_match = ready_auto_transitioned ||
272 (response->curr_status.value ==
273 aimdk_msgs::msg::SystemStatus::IN_READY); // READY (1)
274
275 if (state_match && status_match) {
276 return true;
277 }
278
279 // Log detailed information if not complete
280 if (!state_match) {
281 RCLCPP_INFO(this->get_logger(),
282 "State mismatch: current='%s', expected='%s'",
283 response->cur_state.c_str(), target_state_.c_str());
284 }
285 if (!status_match) {
286 RCLCPP_INFO(this->get_logger(),
287 "Status mismatch: current= %u, expected=1 (IN_READY)",
288 response->curr_status.value);
289 }
290 RCLCPP_ERROR(this->get_logger(), "State Migrate Fail");
291
292 return false;
293 }
294
295 // Target state to migrate to
296 std::string target_state_;
297
298 // Service clients
299 rclcpp::Client<aimdk_msgs::srv::MigrateSystemState>::SharedPtr
300 migrate_client_;
301 rclcpp::Client<aimdk_msgs::srv::GetSystemState>::SharedPtr get_state_client_;
302
303 // Timers
304 rclcpp::TimerBase::SharedPtr check_timer_;
305 rclcpp::TimerBase::SharedPtr monitor_timer_;
306
307 // Retry counters and timeout timers for migration
308 int migrate_retry_count_ = 0;
309 const int max_migrate_retries_ = 5;
310 uint64_t migrate_request_id_ = 0;
311 bool migrate_pending_ = false;
312 rclcpp::TimerBase::SharedPtr migrate_timeout_timer_;
313
314 // Retry counters and timeout timers for get state
315 int get_state_retry_count_ = 0;
316 const int max_get_state_retries_ = 3;
317 uint64_t get_state_request_id_ = 0;
318 bool get_state_pending_ = false;
319 rclcpp::TimerBase::SharedPtr get_state_timeout_timer_;
320};
321
322/**
323 * @brief Print usage information with detailed mode descriptions
324 */
325void print_usage(const char *program_name) {
326 std::cout << "Usage: " << program_name << " <target_state>" << std::endl;
327 std::cout << std::endl;
328 std::cout << "Available target states:" << std::endl;
329 std::cout << " " << program_name << " Ready" << std::endl;
330 std::cout << " " << program_name << " Develop_Nav" << std::endl;
331 std::cout << " " << program_name << " Develop_Audio_Linux" << std::endl;
332 std::cout << " " << program_name << " Develop_Audio_ROS" << std::endl;
333 std::cout << " " << program_name << " Develop_MC" << std::endl;
334 std::cout << std::endl;
335
336 std::cout << "Mode Descriptions:" << std::endl;
337 std::cout << "==================" << std::endl;
338
339 // Ready mode description
340 std::cout << "1. Ready - System Default Mode" << std::endl;
341 std::cout
342 << " • Description: System enters this mode by default after startup"
343 << std::endl;
344 std::cout << " • Purpose: Normal operation mode, ready for general tasks"
345 << std::endl;
346 std::cout
347 << " • Use case: Return to normal operation after development work"
348 << std::endl;
349 std::cout << std::endl;
350
351 // Develop_Nav mode description
352 std::cout << "2. Develop_Nav - Navigation Development Mode" << std::endl;
353 std::cout
354 << " • Description: Special mode for navigation system development"
355 << std::endl;
356 std::cout << " • Purpose: Enables navigation-related debugging and testing"
357 << std::endl;
358 std::cout << " • Use case: When developing or testing navigation "
359 "algorithms, SLAM, path planning"
360 << std::endl;
361 std::cout << " • Features: May provide access to raw sensor data, "
362 "navigation debug topics"
363 << std::endl;
364 std::cout << std::endl;
365
366 // Develop_Audio_Linux mode description
367 std::cout << "3. Develop_Audio_Linux - System-Level Audio Development Mode"
368 << std::endl;
369 std::cout << " • Description: Mode for low-level audio system development"
370 << std::endl;
371 std::cout << " • Purpose: Access to system audio streams at Linux level"
372 << std::endl;
373 std::cout << " • Use case: When developing audio drivers, audio processing "
374 "at system level"
375 << std::endl;
376 std::cout << " • Features: Direct access to audio hardware, "
377 "ALSA/PulseAudio interfaces"
378 << std::endl;
379 std::cout << std::endl;
380
381 // Develop_Audio_ROS mode description
382 std::cout << "4. Develop_Audio_ROS - ROS Audio Development Mode" << std::endl;
383 std::cout
384 << " • Description: Mode for ROS-based audio application development"
385 << std::endl;
386 std::cout << " • Purpose: Access to audio data through ROS topics"
387 << std::endl;
388 std::cout << " • Use case: When developing ROS nodes that process audio, "
389 "speech recognition"
390 << std::endl;
391 std::cout << " • Features: Audio data published as ROS topics, ROS message "
392 "interfaces"
393 << std::endl;
394 std::cout << std::endl;
395
396 // Develop_MC mode description
397 std::cout << "5. Develop_MC - Motion Control Development Mode" << std::endl;
398 std::cout << " • Description: Mode for motion control system development"
399 << std::endl;
400 std::cout
401 << " • Purpose: Enables direct control and testing of motion systems"
402 << std::endl;
403 std::cout << " • Use case: When developing motor control algorithms, "
404 "testing motion hardware"
405 << std::endl;
406 std::cout << " • Features: Low-level motor control access, motion system "
407 "debugging tools"
408 << std::endl;
409 std::cout << std::endl;
410
411 std::cout << "Program Workflow:" << std::endl;
412 std::cout << "=================" << std::endl;
413 std::cout
414 << "1. Calls MigrateSystemState service with the specified target state"
415 << std::endl;
416 std::cout << "2. Continuously monitors GetSystemState service every second"
417 << std::endl;
418 std::cout << "3. Exits when both conditions are met:" << std::endl;
419 std::cout << " • Current State equals target state (case-insensitive)"
420 << std::endl;
421 std::cout << " • System Status equals 1 (IN_READY)" << std::endl;
422 std::cout << std::endl;
423 std::cout << "Note: The program will automatically exit when migration is "
424 "complete or on error."
425 << std::endl;
426}
427
428/**
429 * @brief Validate if the target state is a known development mode
430 * @param state The state to validate
431 * @return true if state is valid, false otherwise
432 */
433bool is_valid_state(const std::string &state) {
434 // List of known valid states
435 const std::vector<std::string> valid_states = {
436 "Ready", "Develop_Nav", "Develop_Audio_Linux", "Develop_Audio_ROS",
437 "Develop_MC"};
438
439 // Convert input to lowercase for case-insensitive comparison
440 std::string state_lower = state;
441 std::transform(state_lower.begin(), state_lower.end(), state_lower.begin(),
442 ::tolower);
443
444 // Check if state is in the list of valid states
445 for (const auto &valid_state : valid_states) {
446 std::string valid_state_lower = valid_state;
447 std::transform(valid_state_lower.begin(), valid_state_lower.end(),
448 valid_state_lower.begin(), ::tolower);
449
450 if (state_lower == valid_state_lower) {
451 return true;
452 }
453 }
454
455 return false;
456}
457
458/**
459 * @brief Main function
460 * @param argc Argument count
461 * @param argv Argument vector
462 * @return Exit code
463 */
464int main(int argc, char **argv) {
465 // Check command line arguments
466 if (argc != 2) {
467 print_usage(argv[0]);
468 return 1;
469 }
470
471 // Get target state from command line argument
472 std::string target_state = argv[1];
473
474 // Validate target state
475 if (target_state.empty()) {
476 std::cerr << "Error: Target state cannot be empty" << std::endl;
477 print_usage(argv[0]);
478 return 1;
479 }
480
481 // Optional: Validate against known states
482 if (!is_valid_state(target_state)) {
483 std::cout << "Warning: '" << target_state
484 << "' is not in the list of known states." << std::endl;
485 std::cout << "The program will still attempt to migrate to this state."
486 << std::endl;
487 std::cout << "Continue? (y/n): ";
488
489 std::string response;
490 std::getline(std::cin, response);
491
492 if (response != "y" && response != "Y") {
493 std::cout << "Operation cancelled." << std::endl;
494 return 0;
495 }
496 }
497
498 // Initialize ROS2
499 rclcpp::init(argc, argv);
500
501 // Create migrator node with target state
502 auto node = std::make_shared<SystemStateMigrator>(target_state);
503
504 // Create single-threaded executor
505 auto executor = std::make_shared<rclcpp::executors::SingleThreadedExecutor>();
506
507 // Add node to executor
508 executor->add_node(node);
509
510 // Run executor (blocking call)
511 executor->spin();
512
513 // Cleanup
514 executor->remove_node(node);
515 rclcpp::shutdown();
516
517 return 0;
518}
Usage Instructions
ros2 run examples migrate_system_state Develop_MC
Output Example
[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 (attempt 1/5)...
[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 Get Robot Mode
Retrieve the robot’s current operating mode by calling the GetMcAction service, including the description, and status information.
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}
Usage Instructions
ros2 run examples get_mc_action
Output Example
[INFO] [1779173975.378396584] [get_mc_action_client]: ✅ GetMcAction client node created.
[INFO] [1779173975.961720731] [get_mc_action_client]: 🟢 Service available, ready to send request.
[INFO] [1779173975.961868725] [get_mc_action_client]: 📨 Sending request to get robot mode
[INFO] [1779173975.965180630] [get_mc_action_client]: ✅ Robot mode get successfully.
[INFO] [1779173975.965282450] [get_mc_action_client]: Mode name: PASSIVE_DEFAULT
[INFO] [1779173975.965307345] [get_mc_action_client]: Mode status: 100
Interface Reference
Service:
/aimdk_5Fmsgs/srv/GetMcActionMessage:
aimdk_msgs/srv/GetMcAction
6.2.3 Set Robot Mode
This example uses the SetMcAction service. After running the node, enter the corresponding field value of the mode in the terminal, and the robot will immediately switch to the appropriate motion mode.
Before switching to the Stable Standing mode (STAND_DEFAULT), ensure the robot is standing and its feet are already on the ground.
The motion mode switching must follow its state transition diagram, other transitions will be rejected
Locomotion Mode (LOCOMOTION_DEFAULT) and Stable Standing Mode (STAND_DEFAULT) are unified and will auto switch internally, so switching manually to the nearer one is enough
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 <iostream>
11#include <memory>
12#include <signal.h>
13#include <unordered_map>
14#include <vector>
15
16// Global variable used for signal handling
17std::shared_ptr<rclcpp::Node> g_node = nullptr;
18
19// Signal handler function
20void signal_handler(int signal) {
21 if (g_node) {
22 RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
23 signal);
24 g_node.reset();
25 }
26 rclcpp::shutdown();
27 exit(signal);
28}
29
30class SetMcActionClient : public rclcpp::Node {
31public:
32 SetMcActionClient() : Node("set_mc_action_client") {
33
34 client_ = this->create_client<aimdk_msgs::srv::SetMcAction>(
35 "/aimdk_5Fmsgs/srv/SetMcAction");
36 RCLCPP_INFO(this->get_logger(), "✅ SetMcAction client node created.");
37
38 // Wait for the service to become available
39 while (!client_->wait_for_service(std::chrono::seconds(2))) {
40 RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
41 }
42 RCLCPP_INFO(this->get_logger(),
43 "🟢 Service available, ready to send request.");
44 }
45
46 bool send_request(std::string &action_name) {
47 try {
48 auto request = std::make_shared<aimdk_msgs::srv::SetMcAction::Request>();
49 request->header = aimdk_msgs::msg::RequestHeader();
50
51 request->source = "node.set_mc_action";
52
53 // Set robot mode
54 aimdk_msgs::msg::McActionCommand command;
55 command.action_desc = action_name;
56 request->command = command;
57
58 RCLCPP_INFO(this->get_logger(), "📨 Sending request to set robot mode: %s",
59 action_name.c_str());
60
61 // Set Service Call Timeout
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.status.value ==
76 aimdk_msgs::msg::CommonState::SUCCESS) { // SUCCESS (1)
77 RCLCPP_INFO(this->get_logger(), "✅ Robot mode set successfully.");
78 return true;
79 } else {
80 RCLCPP_ERROR(this->get_logger(), "❌ Failed (code=%ld): %s",
81 static_cast<long>(response->response.header.code),
82 response->response.message.c_str());
83 return false;
84 }
85 }
86 RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
87 return false;
88 } catch (const std::exception &e) {
89 RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
90 return false;
91 }
92 }
93
94private:
95 rclcpp::Client<aimdk_msgs::srv::SetMcAction>::SharedPtr client_;
96};
97
98static std::unordered_map<std::string, std::vector<std::string>> g_action_info =
99 {
100 {"PASSIVE_DEFAULT", {"PD", "joints with zero torque"}},
101 {"DAMPING_DEFAULT", {"DD", "joints in damping mode"}},
102 {"JOINT_DEFAULT", {"JD", "Position Control Stand (joints locked)"}},
103 {"STAND_DEFAULT", {"SD", "Stable Stand (auto-balance)"}},
104 {"LOCOMOTION_DEFAULT", {"LD", "locomotion mode (walk or run)"}},
105 {"HEAD_ONLY", {"HO", "head-only control"}},
106 {"UPPERBODY_REMOTE_SPLIT",
107 {"URS", "upper body remote split (head + arms + hands)"}},
108};
109
110int main(int argc, char *argv[]) {
111 try {
112 rclcpp::init(argc, argv);
113
114 // Set up signal handlers
115 signal(SIGINT, signal_handler);
116 signal(SIGTERM, signal_handler);
117
118 // Create node
119 g_node = std::make_shared<SetMcActionClient>();
120 auto client = std::dynamic_pointer_cast<SetMcActionClient>(g_node);
121
122 if (client) {
123 std::unordered_map<std::string, std::string> choices;
124 std::string motion;
125
126 // Prefer command-line argument; otherwise prompt user
127 if (argc > 1) {
128 motion = argv[1];
129 RCLCPP_INFO(g_node->get_logger(),
130 "Using abbr of motion mode from cmdline: %s", argv[1]);
131 } else {
132 std::cout << std::left << std::setw(4) << "abbr"
133 << " - " << std::setw(20) << "robot mode"
134 << " : "
135 << "description" << std::endl;
136 for (auto &it : g_action_info) {
137 std::cout << std::left << std::setw(4) << it.second[0] << " - "
138 << std::setw(20) << it.first << " : " << it.second[1]
139 << std::endl;
140 }
141 std::cout << "Enter abbr of motion mode:";
142 std::cin >> motion;
143 }
144 for (auto &it : g_action_info) {
145 choices[it.second[0]] = it.first;
146 }
147
148 auto m = choices.find(motion);
149 if (m != choices.end()) {
150 auto &action_name = m->second;
151 client->send_request(action_name);
152 } else {
153 RCLCPP_ERROR(g_node->get_logger(), "Invalid abbr of robot mode: %s",
154 motion.c_str());
155 }
156 }
157
158 // Clean up resources
159 g_node.reset();
160 rclcpp::shutdown();
161
162 return 0;
163 } catch (const std::exception &e) {
164 RCLCPP_ERROR(rclcpp::get_logger("main"),
165 "Program exited with exception: %s", e.what());
166 return 1;
167 }
168}
Usage Instructions
# Use command-line arguments to set the mode (recommended)
ros2 run py_examples set_mc_action JD # Zero-Torque >> Position-Control Standing
ros2 run py_examples set_mc_action SD # Ensure your robot's feet on the ground, Position-Control Standing >> Stable Standing
# Stable Standing >> Locomotion Mode. auto done internally, don't switch manually
# Or run without arguments and the program will prompt for input
ros2 run py_examples set_mc_action
Output Example
...
[INFO] [1764066567.502968540] [set_mc_action_client]: ✅ Robot mode set successfully.
Notes
Ensure the robot is standing and its feet are on the ground before switching to
STAND_DEFAULTmodeMode switching may take several seconds to complete
Interface Reference
Service:
/aimdk_5Fmsgs/srv/SetMcActionMessage:
aimdk_msgs/srv/SetMcAction
6.2.4 Set Robot Motion
This example uses preset_motion_client; after switching to Stable Stand Mode and starting the node, enter the corresponding field values to perform preset actions with the left (or right) hand such as handshake, raise hand, wave, or air kiss.
Available parameters can be found in the Preset Motion Table
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_input_action.hpp"
6#include "aimdk_msgs/msg/mc_preset_motion.hpp"
7#include "aimdk_msgs/msg/request_header.hpp"
8#include "aimdk_msgs/srv/set_mc_input_source.hpp"
9#include "aimdk_msgs/srv/set_mc_preset_motion.hpp"
10#include "rclcpp/rclcpp.hpp"
11#include <chrono>
12#include <cinttypes>
13#include <iostream>
14#include <memory>
15#include <signal.h>
16
17namespace {
18constexpr const char *kInputSourceName = "node.preset_motion";
19constexpr int32_t kInputSourcePriority = 40;
20constexpr int32_t kInputSourceTimeout = 1000;
21} // namespace
22
23std::shared_ptr<rclcpp::Node> g_node = nullptr;
24
25void signal_handler(int signal) {
26 if (g_node) {
27 RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
28 signal);
29 g_node.reset();
30 }
31 rclcpp::shutdown();
32 exit(signal);
33}
34
35class PresetMotionClient : public rclcpp::Node {
36public:
37 PresetMotionClient() : Node("preset_motion_client") {
38 client_ = this->create_client<aimdk_msgs::srv::SetMcPresetMotion>(
39 "/aimdk_5Fmsgs/srv/SetMcPresetMotion");
40 input_source_client_ =
41 this->create_client<aimdk_msgs::srv::SetMcInputSource>(
42 "/aimdk_5Fmsgs/srv/SetMcInputSource");
43
44 RCLCPP_INFO(this->get_logger(), "✅ SetMcPresetMotion client node created.");
45
46 // Wait for the service to become available
47 while (!client_->wait_for_service(std::chrono::seconds(2))) {
48 RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
49 }
50 RCLCPP_INFO(this->get_logger(),
51 "🟢 Service available, ready to send request.");
52 }
53
54 bool register_input_source() {
55 if (!input_source_client_->wait_for_service(std::chrono::seconds(8))) {
56 RCLCPP_ERROR(this->get_logger(),
57 "❌ SetMcInputSource service unavailable.");
58 return false;
59 }
60
61 // Try ADD first; if it fails (likely already registered), fall back to
62 // ENABLE
63 if (call_input_source( // ADD (1001)
64 aimdk_msgs::msg::McInputAction::INPUTACTION_ADD)) {
65 RCLCPP_INFO(this->get_logger(), "✅ Input source \"%s\" registered (ADD).",
66 kInputSourceName);
67 return true;
68 }
69 RCLCPP_INFO(this->get_logger(),
70 "ADD failed (likely already registered), trying ENABLE...");
71 if (call_input_source( // ENABLE (2001)
72 aimdk_msgs::msg::McInputAction::INPUTACTION_ENABLE)) {
73 RCLCPP_INFO(this->get_logger(), "✅ Input source \"%s\" enabled.",
74 kInputSourceName);
75 return true;
76 }
77 RCLCPP_ERROR(this->get_logger(),
78 "❌ Failed to register input source via ADD or ENABLE.");
79 return false;
80 }
81
82 bool send_request(int area_id, int motion_id) {
83 try {
84 auto request =
85 std::make_shared<aimdk_msgs::srv::SetMcPresetMotion::Request>();
86 request->header = aimdk_msgs::msg::RequestHeader();
87
88 request->input_source.name = kInputSourceName;
89 request->input_source.priority = kInputSourcePriority;
90 request->input_source.timeout = kInputSourceTimeout;
91
92 aimdk_msgs::msg::McPresetMotion motion;
93 aimdk_msgs::msg::McControlArea area;
94
95 motion.value = motion_id; // Preset motion ID
96 area.value = area_id; // Control area ID
97 request->motion = motion;
98 request->area = area;
99 request->interrupt = false; // Not interrupt current motion
100
101 RCLCPP_INFO(this->get_logger(),
102 "📨 Sending request to set preset motion: motion=%d, area=%d",
103 motion_id, area_id);
104
105 const std::chrono::milliseconds timeout(250);
106 for (int i = 0; i < 8; i++) {
107 request->header.stamp = this->now();
108 auto future = client_->async_send_request(request);
109 auto retcode = rclcpp::spin_until_future_complete(shared_from_this(),
110 future, timeout);
111 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
112 // retry as remote peer is NOT handled well by ROS
113 RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
114 continue;
115 }
116 // future.done
117 auto response = future.get();
118 if (response->response.header.code == 0) {
119 RCLCPP_INFO(this->get_logger(),
120 "✅ Preset motion set successfully: %" PRIu64,
121 response->response.task_id);
122 return true;
123 } else if (response->response.state.value ==
124 aimdk_msgs::msg::CommonState::RUNNING) { // RUNNING (400)
125 RCLCPP_INFO(this->get_logger(), "⏳ Preset motion executing: %" PRIu64,
126 response->response.task_id);
127 return true;
128 } else {
129 RCLCPP_WARN(this->get_logger(),
130 "❌ Failed to set preset motion: %" PRIu64,
131 response->response.task_id);
132 return false;
133 }
134 }
135 RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
136 return false;
137 } catch (const std::exception &e) {
138 RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
139 return false;
140 }
141 }
142
143private:
144 bool call_input_source(int32_t action_value) {
145 auto req = std::make_shared<aimdk_msgs::srv::SetMcInputSource::Request>();
146 req->action.value = action_value;
147 req->input_source.name = kInputSourceName;
148 req->input_source.priority = kInputSourcePriority;
149 req->input_source.timeout = kInputSourceTimeout;
150
151 const std::chrono::milliseconds timeout(250);
152 for (int i = 0; i < 8; i++) {
153 req->request.header.stamp = this->now();
154 auto future = input_source_client_->async_send_request(req);
155 auto retcode = rclcpp::spin_until_future_complete(shared_from_this(),
156 future, timeout);
157 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
158 // retry as remote peer is NOT handled well by ROS
159 RCLCPP_INFO(
160 this->get_logger(), "trying input source action %s (%d)... [%d]",
161 action_value == aimdk_msgs::msg::McInputAction::INPUTACTION_ADD
162 ? "ADD"
163 : "ENABLE",
164 action_value, i);
165 continue;
166 }
167 auto response = future.get();
168 return response->response.header.code == 0;
169 }
170 return false;
171 }
172
173 rclcpp::Client<aimdk_msgs::srv::SetMcPresetMotion>::SharedPtr client_;
174 rclcpp::Client<aimdk_msgs::srv::SetMcInputSource>::SharedPtr
175 input_source_client_;
176};
177
178int main(int argc, char *argv[]) {
179 try {
180 rclcpp::init(argc, argv);
181 signal(SIGINT, signal_handler);
182 signal(SIGTERM, signal_handler);
183
184 g_node = std::make_shared<PresetMotionClient>();
185 // Cast g_node (std::shared_ptr<rclcpp::Node>) to a derived
186 // PresetMotionClient pointer (std::shared_ptr<PresetMotionClient>)
187 auto client = std::dynamic_pointer_cast<PresetMotionClient>(g_node);
188
189 int area = 1;
190 int motion = 1003;
191 std::cout << "Enter arm area ID (1-left, 2-right): ";
192 std::cin >> area;
193 std::cout
194 << "Enter preset motion ID (1001-raise, 1002-wave, 1003-handshake, "
195 "1004-airkiss): ";
196 std::cin >> motion;
197 if (client) {
198 if (!client->register_input_source()) {
199 RCLCPP_ERROR(g_node->get_logger(),
200 "❌ Input source registration failed, exiting");
201 } else {
202 client->send_request(area, motion);
203 }
204 }
205
206 // Clean up resources
207 g_node.reset();
208 rclcpp::shutdown();
209
210 return 0;
211 } catch (const std::exception &e) {
212 RCLCPP_ERROR(rclcpp::get_logger("main"),
213 "Program exited with exception: %s", e.what());
214 return 1;
215 }
216}
6.2.5 Gripper Control
This example uses hand_control. By publishing messages to the topic /aima/hal/joint/hand/command, you can control the movement of the gripper.
Attention
Warning ⚠️ : Before running this example, you must stop the native motion control module using aima em stop-app mc on the robot’s Motion Control Computing Unit (PC1) to obtain control authority. Ensure robot safety at all times.
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 <memory>
8#include <vector>
9
10class HandControl : public rclcpp::Node {
11public:
12 HandControl()
13 : Node("hand_control"), position_pairs_({
14 {1.0, 1.0},
15 {0.0, 0.0},
16 {0.5, 0.5},
17 {0.2, 0.8},
18 {0.7, 0.3},
19 }),
20 current_index_(0) {
21 publisher_ = this->create_publisher<aimdk_msgs::msg::HandCommandArray>(
22 "/aima/hal/joint/hand/command", 10);
23
24 timer_ = this->create_wall_timer(
25 std::chrono::milliseconds(20), // 50Hz
26 std::bind(&HandControl::publish_hand_commands, this));
27
28 last_switch_time_ = now();
29 RCLCPP_INFO(this->get_logger(), "The hand control node has been started!");
30 }
31
32 void publish_hand_commands() {
33 // 1. Determine if it's time to switch parameters.
34 auto now_time = this->now();
35 if ((now_time - last_switch_time_).seconds() >= 2.0) {
36 current_index_ = (current_index_ + 1) % position_pairs_.size();
37 last_switch_time_ = now_time;
38 RCLCPP_INFO(this->get_logger(),
39 "Switched to the next parameter group, index=%zu (left=%.2f, "
40 "right=%.2f)",
41 current_index_, position_pairs_[current_index_].first,
42 position_pairs_[current_index_].second);
43 }
44
45 auto msg = std::make_unique<aimdk_msgs::msg::HandCommandArray>();
46 msg->header = aimdk_msgs::msg::MessageHeader();
47
48 float left_position = position_pairs_[current_index_].first;
49 float right_position = position_pairs_[current_index_].second;
50
51 aimdk_msgs::msg::HandCommand left_hands;
52 left_hands.name = "left_hand";
53 left_hands.position = left_position;
54 left_hands.velocity = 1.0;
55 left_hands.acceleration = 1.0;
56 left_hands.deceleration = 1.0;
57 left_hands.effort = 1.0;
58
59 aimdk_msgs::msg::HandCommand right_hands;
60 right_hands.name = "right_hand";
61 right_hands.position = right_position;
62 right_hands.velocity = 1.0;
63 right_hands.acceleration = 1.0;
64 right_hands.deceleration = 1.0;
65 right_hands.effort = 1.0;
66
67 msg->left_hands.push_back(left_hands);
68 msg->right_hands.push_back(right_hands);
69 msg->left_hand_type.value = aimdk_msgs::msg::HandType::CLAW; // grippers (2)
70 msg->right_hand_type.value =
71 aimdk_msgs::msg::HandType::CLAW; // grippers (2)
72
73 publisher_->publish(std::move(msg));
74 }
75
76private:
77 rclcpp::Publisher<aimdk_msgs::msg::HandCommandArray>::SharedPtr publisher_;
78 rclcpp::TimerBase::SharedPtr timer_;
79
80 std::vector<std::pair<float, float>> position_pairs_;
81 size_t current_index_;
82
83 rclcpp::Time last_switch_time_;
84};
85
86int main(int argc, char *argv[]) {
87 rclcpp::init(argc, argv);
88 auto hand_control_node = std::make_shared<HandControl>();
89 rclcpp::spin(hand_control_node);
90 rclcpp::shutdown();
91 return 0;
92}
6.2.6 Dexterous Hand Control
This example uses omnihand_control. By publishing messages to the topic /aima/hal/joint/hand/command, you can control the movement of the omnihand.
Attention
Warning ⚠️ : Before running this example, you must stop the native motion control module using aima em stop-app mc on the robot’s Motion Control Computing Unit (PC1) to obtain control authority. Ensure robot safety at all times.
1#include <aimdk_msgs/msg/hand_command_array.hpp>
2#include <aimdk_msgs/msg/hand_type.hpp>
3#include <array>
4#include <chrono>
5#include <memory>
6#include <rclcpp/rclcpp.hpp>
7
8using namespace std::chrono_literals;
9
10class HandCommandPublisher : public rclcpp::Node {
11public:
12 HandCommandPublisher() : Node("hand_command_publisher") {
13 publisher_ = this->create_publisher<aimdk_msgs::msg::HandCommandArray>(
14 "/aima/hal/joint/hand/command", 10);
15
16 // Create a timer to publish once per second
17 timer_ = this->create_wall_timer(
18 1s, std::bind(&HandCommandPublisher::publish_command, this));
19
20 RCLCPP_INFO(this->get_logger(), "Hand control node started!");
21 }
22
23private:
24 void publish_command() {
25 auto message = aimdk_msgs::msg::HandCommandArray();
26
27 // Set header
28 message.header.stamp = this->now();
29 message.header.frame_id = "hand_command";
30
31 // Set the hand type
32 message.left_hand_type.value =
33 aimdk_msgs::msg::HandType::NIMBLE_HANDS; // dexterous hands (1)
34 message.right_hand_type.value =
35 aimdk_msgs::msg::HandType::NIMBLE_HANDS; // dexterous hands (1)
36
37 const char *left_names[10] = {"L_thumb_roll_joint", "L_thumb_abad_joint",
38 "L_thumb_mcp_joint", "L_index_abad_joint",
39 "L_index_pip_joint", "L_middle_pip_joint",
40 "L_ring_abad_joint", "L_ring_pip_joint",
41 "L_pinky_abad_joint", "L_pinky_pip_joint"};
42 const char *right_names[10] = {"R_thumb_roll_joint", "R_thumb_abad_joint",
43 "R_thumb_mcp_joint", "R_index_abad_joint",
44 "R_index_pip_joint", "R_middle_pip_joint",
45 "R_ring_abad_joint", "R_ring_pip_joint",
46 "R_pinky_abad_joint", "R_pinky_pip_joint"};
47
48 // Create left hand command array
49 message.left_hands.resize(10);
50 for (int i = 0; i < 10; i++) {
51 message.left_hands[i].name = left_names[i];
52 message.left_hands[i].position = 0.0;
53 message.left_hands[i].velocity = 0.1;
54 message.left_hands[i].acceleration = 0.0;
55 message.left_hands[i].deceleration = 0.0;
56 message.left_hands[i].effort = 0.0;
57 }
58
59 // Create right hand command array
60 message.right_hands.resize(10);
61 for (int i = 0; i < 10; i++) {
62 message.right_hands[i].name = right_names[i];
63 message.right_hands[i].position = 0.0;
64 message.right_hands[i].velocity = 0.1;
65 message.right_hands[i].acceleration = 0.0;
66 message.right_hands[i].deceleration = 0.0;
67 message.right_hands[i].effort = 0.0;
68 }
69
70 // Absolute joint targets in rad = half-grip
71 // Joint order: [thumb_roll, thumb_abad, thumb_mcp, index_abad, index_pip,
72 // middle_pip, ring_abad, ring_pip, pinky_abad, pinky_pip]
73 const std::array<double, 10> left_hand_target = {
74 -0.6, 0.8, -0.4, 0.1, 0.7, 0.7, -0.1, 0.7, -0.1, 0.7};
75 const std::array<double, 10> right_hand_target = {
76 0.6, -0.8, 0.4, -0.1, 0.7, 0.7, 0.1, 0.7, 0.1, 0.7};
77
78 if (target_finger < 10) {
79 message.right_hands[target_finger].position =
80 right_hand_target[target_finger];
81 } else {
82 int target_finger_ = target_finger - 10;
83 message.left_hands[target_finger_].position =
84 left_hand_target[target_finger_];
85 }
86
87 // Publish the message
88 publisher_->publish(message);
89
90 RCLCPP_INFO(this->get_logger(),
91 "Published hand command with target_finger: %d", target_finger);
92
93 update_target_finger();
94 }
95
96 void update_target_finger() {
97 if (increasing_) {
98 target_finger += step_;
99 if (target_finger >= 19) {
100 target_finger = 19;
101 increasing_ = false;
102 }
103 } else {
104 target_finger -= step_;
105 if (target_finger <= 0) {
106 target_finger = 0;
107 increasing_ = true;
108 }
109 }
110 }
111
112 rclcpp::Publisher<aimdk_msgs::msg::HandCommandArray>::SharedPtr publisher_;
113 rclcpp::TimerBase::SharedPtr timer_;
114
115 int target_finger = 0;
116 int step_ = 1;
117 bool increasing_ = true;
118};
119
120int main(int argc, char **argv) {
121 rclcpp::init(argc, argv);
122 auto node = std::make_shared<HandCommandPublisher>();
123 rclcpp::spin(node);
124 rclcpp::shutdown();
125 return 0;
126}
6.2.7 Dexterous Hand Tactile Data (T2.1 Support)
This example uses omnihand_touch. By subscribing to the /aima/hal/joint/hand/state, you can monitor the pressure of the omnihand finger touch sensor.
Attention
Warning ⚠️ : Before running this example, you must stop the native motion control module using aima em stop-app mc on the robot’s Motion Control Computing Unit (PC1) to obtain control authority. Ensure robot safety at all times.
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 <aimdk_msgs/msg/hand_type.hpp>
7#include <chrono>
8#include <iomanip>
9#include <iostream>
10#include <rclcpp/rclcpp.hpp>
11
12using namespace std::chrono_literals;
13
14class HandStateSubscriber : public rclcpp::Node {
15public:
16 HandStateSubscriber() : Node("hand_state_subscriber") {
17 publisher_ = this->create_publisher<aimdk_msgs::msg::HandCommandArray>(
18 "/aima/hal/joint/hand/command", 10);
19 rclcpp::QoS qos_profile(rclcpp::KeepLast(10));
20 qos_profile.best_effort(); // Set reliability to BEST_EFFORT
21 // Create subscriber
22 subscription_ = this->create_subscription<aimdk_msgs::msg::HandStateArray>(
23 "/aima/hal/joint/hand/state", qos_profile,
24 std::bind(&HandStateSubscriber::topic_callback, this,
25 std::placeholders::_1));
26 // Create a timer to publish once per second
27 timer_ = this->create_wall_timer(
28 1s, std::bind(&HandStateSubscriber::publish_command, this));
29 RCLCPP_INFO(
30 this->get_logger(),
31 "Subscriber started, listening to /aima/hal/joint/hand/state topic...");
32 }
33
34private:
35 void topic_callback(const aimdk_msgs::msg::HandStateArray::SharedPtr msg) {
36 // Print message header information
37 RCLCPP_INFO(this->get_logger(),
38 "Message received - Sequence: %u, Timestamp: %d.%09d",
39 msg->header.sequence, msg->header.stamp.sec,
40 msg->header.stamp.nanosec);
41
42 // Print left hand touch sensor data
43 print_touch_sensor_data("Left Hand", msg->left_touch_sensors);
44
45 // Print right hand touch sensor data
46 print_touch_sensor_data("Right Hand", msg->right_touch_sensors);
47
48 std::cout << std::endl;
49 }
50
51 /**
52 * @brief Print touch sensor data for a specific hand
53 * @param hand_name Name of the hand (Left Hand/Right Hand)
54 * @param sensor_data Touch sensor data structure
55 */
56 void print_touch_sensor_data(
57 const std::string &hand_name,
58 const aimdk_msgs::msg::HandTouchSensorData &sensor_data) {
59 std::cout << "=== " << hand_name << " Touch Sensor Data ===" << std::endl;
60
61 // Print palm touch data
62 std::cout << "Palm Touch Data (36 elements): ";
63 print_array(sensor_data.palm_touch_data);
64
65 // Print back of hand touch data
66 std::cout << "Back of Hand Touch Data (36 elements): ";
67 print_array(sensor_data.back_of_hand_touch_data);
68
69 // Print finger touch data
70 std::cout << "Thumb Touch Data (16 elements): ";
71 print_array(sensor_data.thumb_touch_data);
72
73 std::cout << "Index Finger Touch Data (16 elements): ";
74 print_array(sensor_data.index_finger_touch_data);
75
76 std::cout << "Middle Finger Touch Data (16 elements): ";
77 print_array(sensor_data.middle_finger_touch_data);
78
79 std::cout << "Ring Finger Touch Data (16 elements): ";
80 print_array(sensor_data.ring_finger_touch_data);
81
82 std::cout << "Little Finger Touch Data (16 elements): ";
83 print_array(sensor_data.little_finger_touch_data);
84 }
85
86 /**
87 * @brief Print array of 36 uint8_t elements
88 * @param arr Array to print
89 */
90 void print_array(const std::array<uint8_t, 36> &arr) {
91 std::cout << "[";
92 for (size_t i = 0; i < arr.size(); ++i) {
93 std::cout << std::setw(3) << static_cast<int>(arr[i]);
94 if (i < arr.size() - 1)
95 std::cout << " ";
96 }
97 std::cout << "]" << std::endl;
98 }
99
100 /**
101 * @brief Print array of 16 uint8_t elements
102 * @param arr Array to print
103 */
104 void print_array(const std::array<uint8_t, 16> &arr) {
105 std::cout << "[";
106 for (size_t i = 0; i < arr.size(); ++i) {
107 std::cout << std::setw(3) << static_cast<int>(arr[i]);
108 if (i < arr.size() - 1)
109 std::cout << " ";
110 }
111 std::cout << "]" << std::endl;
112 }
113
114 void publish_command() {
115 auto message = aimdk_msgs::msg::HandCommandArray();
116
117 // Set header
118 message.header.stamp = this->now();
119 message.header.frame_id = "hand_command";
120
121 // Set the hand type
122 message.left_hand_type.value =
123 aimdk_msgs::msg::HandType::NIMBLE_HANDS; // dexterous hands (1)
124 message.right_hand_type.value =
125 aimdk_msgs::msg::HandType::NIMBLE_HANDS; // dexterous hands (1)
126
127 // Joint names for readability, mirroring the /aima/hal/joint/hand/state
128 // feedback (URDF names). The driver routes by array index/order (see
129 // endeffector.md), not by these name strings.
130 const char *left_names[10] = {"L_thumb_roll_joint", "L_thumb_abad_joint",
131 "L_thumb_mcp_joint", "L_index_abad_joint",
132 "L_index_pip_joint", "L_middle_pip_joint",
133 "L_ring_abad_joint", "L_ring_pip_joint",
134 "L_pinky_abad_joint", "L_pinky_pip_joint"};
135 const char *right_names[10] = {"R_thumb_roll_joint", "R_thumb_abad_joint",
136 "R_thumb_mcp_joint", "R_index_abad_joint",
137 "R_index_pip_joint", "R_middle_pip_joint",
138 "R_ring_abad_joint", "R_ring_pip_joint",
139 "R_pinky_abad_joint", "R_pinky_pip_joint"};
140
141 // Create left hand command array
142 message.left_hands.resize(10);
143 for (int i = 0; i < 10; i++) {
144 message.left_hands[i].name = left_names[i];
145 message.left_hands[i].position = 0.0;
146 message.left_hands[i].velocity = 0.1;
147 message.left_hands[i].acceleration = 0.0;
148 message.left_hands[i].deceleration = 0.0;
149 message.left_hands[i].effort = 0.0;
150 }
151
152 // Create right hand command array
153 message.right_hands.resize(10);
154 for (int i = 0; i < 10; i++) {
155 message.right_hands[i].name = right_names[i];
156 message.right_hands[i].position = 0.0;
157 message.right_hands[i].velocity = 0.1;
158 message.right_hands[i].acceleration = 0.0;
159 message.right_hands[i].deceleration = 0.0;
160 message.right_hands[i].effort = 0.0;
161 }
162
163 // Publish the message
164 publisher_->publish(message);
165
166 RCLCPP_INFO(this->get_logger(), "Published hand command");
167 }
168
169 rclcpp::Publisher<aimdk_msgs::msg::HandCommandArray>::SharedPtr publisher_;
170 rclcpp::Subscription<aimdk_msgs::msg::HandStateArray>::SharedPtr
171 subscription_;
172 rclcpp::TimerBase::SharedPtr timer_;
173};
174
175int main(int argc, char *argv[]) {
176 rclcpp::init(argc, argv);
177 auto node = std::make_shared<HandStateSubscriber>();
178 rclcpp::spin(node);
179 rclcpp::shutdown();
180 return 0;
181}
The pressure sensors are located at the fingertips of all five fingers, as well as on the palm and the back of the hand. At least one motion command needs to be sent to the dexterous hand in order to receive the corresponding pressure data report.
6.2.8 Register Secondary Development Input Source
For versions after v0.7, an input source must be registered before controlling the MC. In this example, the /aimdk_5Fmsgs/srv/SetMcInputSource service is used to register the secondary development input source, so that the MC can recognize it. Only after registration can robot velocity control be performed.
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 <cinttypes>
10#include <memory>
11#include <signal.h>
12
13namespace {
14const char *action_name(int32_t value) {
15 using A = aimdk_msgs::msg::McInputAction;
16 switch (value) {
17 case A::INPUTACTION_ADD:
18 return "ADD";
19 case A::INPUTACTION_MODIFY:
20 return "MODIFY";
21 case A::INPUTACTION_DELETE:
22 return "DELETE";
23 case A::INPUTACTION_ENABLE:
24 return "ENABLE";
25 case A::INPUTACTION_DISABLE:
26 return "DISABLE";
27 default:
28 return "UNKNOWN";
29 }
30}
31} // namespace
32
33std::shared_ptr<rclcpp::Node> g_node = nullptr;
34
35void signal_handler(int signal) {
36 if (g_node) {
37 RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
38 signal);
39 g_node.reset();
40 }
41 rclcpp::shutdown();
42 exit(signal);
43}
44
45class McInputClient : public rclcpp::Node {
46public:
47 McInputClient() : Node("set_mc_input_source_client") {
48 client_ = this->create_client<aimdk_msgs::srv::SetMcInputSource>(
49 "/aimdk_5Fmsgs/srv/SetMcInputSource");
50
51 RCLCPP_INFO(this->get_logger(), "✅ SetMcInputSource client node created.");
52
53 // Wait for the service to become available
54 while (!client_->wait_for_service(std::chrono::seconds(2))) {
55 RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
56 }
57 RCLCPP_INFO(this->get_logger(),
58 "🟢 Service available, ready to send request.");
59 }
60
61 bool send_request() {
62 try {
63 auto request =
64 std::make_shared<aimdk_msgs::srv::SetMcInputSource::Request>();
65
66 // Set request data
67 request->action.value =
68 aimdk_msgs::msg::McInputAction::INPUTACTION_ADD; // ADD (1001)
69 request->input_source.name = "node.set_mc_input_source";
70 request->input_source.priority = 40;
71 request->input_source.timeout = 1000;
72
73 RCLCPP_INFO(this->get_logger(), "📨 Sending input source request: %s (%d)",
74 action_name(request->action.value), request->action.value);
75
76 auto timeout = std::chrono::milliseconds(250);
77 for (int i = 0; i < 8; i++) {
78 // Set header timestamp
79 request->request.header.stamp = this->now(); // use Node::now()
80 auto future = client_->async_send_request(request);
81 auto retcode = rclcpp::spin_until_future_complete(
82 this->shared_from_this(), future, timeout);
83 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
84 // retry as remote peer is NOT handled well by ROS
85 RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
86 continue;
87 }
88 // future.done
89 auto response = future.get();
90 auto code = response->response.header.code;
91 if (code == 0) {
92 RCLCPP_INFO(this->get_logger(),
93 "✅ Input source set successfully. task_id=%" PRIu64,
94 response->response.task_id);
95 return true;
96 } else {
97 RCLCPP_ERROR(
98 this->get_logger(),
99 "❌ Input source set failed. ret_code=%" PRId64
100 ", task_id=%" PRIu64 " "
101 "(duplicated ADD? or MODIFY/ENABLE/DISABLE for unknown source?)",
102 code, response->response.task_id);
103 return false;
104 }
105 }
106 RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
107 return false;
108 } catch (const std::exception &e) {
109 RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
110 return false;
111 }
112 }
113
114private:
115 rclcpp::Client<aimdk_msgs::srv::SetMcInputSource>::SharedPtr client_;
116};
117
118int main(int argc, char *argv[]) {
119 try {
120 rclcpp::init(argc, argv);
121 signal(SIGINT, signal_handler);
122 signal(SIGTERM, signal_handler);
123
124 g_node = std::make_shared<McInputClient>();
125 auto client = std::dynamic_pointer_cast<McInputClient>(g_node);
126
127 if (client) {
128 client->send_request();
129 }
130
131 g_node.reset();
132 rclcpp::shutdown();
133
134 return 0;
135 } catch (const std::exception &e) {
136 RCLCPP_ERROR(rclcpp::get_logger("main"),
137 "Program terminated with exception: %s", e.what());
138 return 1;
139 }
140}
6.2.9 Get Current Input Source
This example uses the GetCurrentInputSource service, which is used to obtain information about the currently registered input source, including the input source name, priority, and timeout settings.
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 <cinttypes>
7#include <memory>
8#include <signal.h>
9
10// Global node object
11std::shared_ptr<rclcpp::Node> g_node = nullptr;
12
13// Signal handler
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
24// Client Class
25class GetCurrentInputSourceClient : public rclcpp::Node {
26public:
27 GetCurrentInputSourceClient() : Node("get_current_input_source_client") {
28
29 client_ = this->create_client<aimdk_msgs::srv::GetCurrentInputSource>(
30 "/aimdk_5Fmsgs/srv/GetCurrentInputSource");
31
32 RCLCPP_INFO(this->get_logger(),
33 "✅ GetCurrentInputSource 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 void send_request() {
44 try {
45 auto request =
46 std::make_shared<aimdk_msgs::srv::GetCurrentInputSource::Request>();
47 request->request = aimdk_msgs::msg::CommonRequest();
48
49 RCLCPP_INFO(this->get_logger(),
50 "📨 Sending request to get current input source");
51
52 auto timeout = std::chrono::milliseconds(250);
53
54 for (int i = 0; i < 8; i++) {
55 request->request.header.stamp = this->now();
56 auto future = client_->async_send_request(request);
57 auto retcode = rclcpp::spin_until_future_complete(
58 this->shared_from_this(), future, timeout);
59 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
60 // retry as remote peer is NOT handled well by ROS
61 RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
62 continue;
63 }
64 // future.done
65 auto response = future.get();
66 if (response->response.header.code == 0) {
67 RCLCPP_INFO(this->get_logger(),
68 "✅ Current input source get successfully:");
69 RCLCPP_INFO(this->get_logger(), "Name: %s",
70 response->input_source.name.c_str());
71 RCLCPP_INFO(this->get_logger(), "Priority: %d",
72 response->input_source.priority);
73 RCLCPP_INFO(this->get_logger(), "Timeout: %d",
74 response->input_source.timeout);
75 } else {
76 RCLCPP_WARN(
77 this->get_logger(),
78 "❌ Current input source get failed, return code: %" PRId64,
79 response->response.header.code);
80 }
81 return;
82 }
83 RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
84 } catch (const std::exception &e) {
85 RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
86 }
87 }
88
89private:
90 rclcpp::Client<aimdk_msgs::srv::GetCurrentInputSource>::SharedPtr client_;
91};
92
93int main(int argc, char *argv[]) {
94 try {
95 rclcpp::init(argc, argv);
96
97 signal(SIGINT, signal_handler);
98 signal(SIGTERM, signal_handler);
99
100 g_node = std::make_shared<GetCurrentInputSourceClient>();
101 auto client =
102 std::dynamic_pointer_cast<GetCurrentInputSourceClient>(g_node);
103
104 if (client) {
105 client->send_request();
106 }
107
108 g_node.reset();
109 rclcpp::shutdown();
110 return 0;
111 } catch (const std::exception &e) {
112 RCLCPP_ERROR(rclcpp::get_logger("main"),
113 "Program exited with exception: %s", e.what());
114 return 1;
115 }
116}
Usage Instructions
# Get current input source information
ros2 run examples get_current_input_source
Output Example
[INFO] [1779174540.534817548] [get_current_input_source_client]: ✅ GetCurrentInputSource client node created.
[INFO] [1779174541.371095227] [get_current_input_source_client]: 🟢 Service available, ready to send request.
[INFO] [1779174541.371216694] [get_current_input_source_client]: 📨 Sending request to get current input source
[INFO] [1779174541.382602338] [get_current_input_source_client]: ✅ Current input source get successfully:
[INFO] [1779174541.382692894] [get_current_input_source_client]: Name: node
[INFO] [1779174541.382713533] [get_current_input_source_client]: Priority: 40
[INFO] [1779174541.382726205] [get_current_input_source_client]: Timeout: 1000
Notes
Ensure the GetCurrentInputSource service is running properly
Valid information can only be obtained after an input source has been registered
A status code of 0 indicates a successful query
6.2.10 Robot Locomotion Control
This example uses mc_locomotion_velocity. The following example controls the robot’s walking by publishing to the /aima/mc/locomotion/velocity topic. For versions after v0.7, an input source must be registered before enabling velocity control (this example already includes input source registration). Refer to the code for detailed registration steps.
Start the node after switching to Stable Standing Mode:
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 <cmath>
13#include <iostream>
14#include <memory>
15#include <signal.h>
16#include <thread>
17
18class DirectVelocityControl : public rclcpp::Node {
19public:
20 DirectVelocityControl() : Node("direct_velocity_control") {
21 // Create publisher
22 publisher_ = this->create_publisher<aimdk_msgs::msg::McLocomotionVelocity>(
23 "/aima/mc/locomotion/velocity", 10);
24 // Create service client
25 client_ = this->create_client<aimdk_msgs::srv::SetMcInputSource>(
26 "/aimdk_5Fmsgs/srv/SetMcInputSource");
27
28 // Maximum speed limits
29 max_forward_speed_ = 1.0; // m/s
30 max_lateral_speed_ = 1.0; // m/s
31 max_angular_speed_ = 1.0; // rad/s
32 // Minimum speed limits (0 is also OK)
33 min_forward_speed_ = 0.2; // m/s
34 min_lateral_speed_ = 0.2; // m/s
35 min_angular_speed_ = 0.1; // rad/s
36
37 RCLCPP_INFO(this->get_logger(), "Direct velocity control node started.");
38 }
39
40 void start_publish() {
41 if (timer_ != nullptr) {
42 return;
43 }
44 // Set timer to periodically publish velocity messages (50Hz)
45 timer_ = this->create_wall_timer(
46 std::chrono::milliseconds(20),
47 std::bind(&DirectVelocityControl::publish_velocity, this));
48 }
49
50 bool register_input_source() {
51 const std::chrono::seconds timeout(8);
52 auto start_time = std::chrono::steady_clock::now();
53 while (!client_->wait_for_service(std::chrono::seconds(2))) {
54 if (std::chrono::steady_clock::now() - start_time > timeout) {
55 RCLCPP_ERROR(this->get_logger(), "Waiting for service timed out");
56 return false;
57 }
58 RCLCPP_INFO(this->get_logger(), "Waiting for input source service...");
59 }
60
61 // Try ADD first; if it fails (likely already registered), fall back to
62 // ENABLE
63 if (call_input_source( // ADD (1001)
64 aimdk_msgs::msg::McInputAction::INPUTACTION_ADD)) {
65 RCLCPP_INFO(this->get_logger(), "✅ Input source registered (ADD).");
66 return true;
67 }
68 RCLCPP_INFO(this->get_logger(),
69 "ADD failed (likely already registered), trying ENABLE...");
70 if (call_input_source( // ENABLE (2001)
71 aimdk_msgs::msg::McInputAction::INPUTACTION_ENABLE)) {
72 RCLCPP_INFO(this->get_logger(), "✅ Input source enabled.");
73 return true;
74 }
75 RCLCPP_ERROR(this->get_logger(),
76 "❌ Failed to register input source via ADD or ENABLE.");
77 return false;
78 }
79
80 bool call_input_source(int32_t action_value) {
81 auto request =
82 std::make_shared<aimdk_msgs::srv::SetMcInputSource::Request>();
83 request->action.value = action_value;
84 request->input_source.name = "node.locomotion_velocity";
85 request->input_source.priority = 40;
86 request->input_source.timeout = 1000;
87
88 auto timeout2 = std::chrono::milliseconds(250);
89
90 for (int i = 0; i < 8; i++) {
91 request->request.header.stamp = this->now();
92 auto future = client_->async_send_request(request);
93 auto retcode = rclcpp::spin_until_future_complete(
94 this->shared_from_this(), future, timeout2);
95 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
96 // retry as remote peer is NOT handled well by ROS
97 RCLCPP_INFO(
98 this->get_logger(), "trying input source action %s (%d)... [%d]",
99 action_value == aimdk_msgs::msg::McInputAction::INPUTACTION_ADD
100 ? "ADD"
101 : "ENABLE",
102 action_value, i);
103 continue;
104 }
105 auto response = future.get();
106 return response->response.header.code == 0;
107 }
108 return false;
109 }
110
111 void publish_velocity() {
112 auto msg = std::make_unique<aimdk_msgs::msg::McLocomotionVelocity>();
113 msg->header = aimdk_msgs::msg::MessageHeader();
114 msg->header.stamp = this->now();
115 msg->source = "node.locomotion_velocity"; // Set message source
116 msg->forward_velocity = forward_velocity_;
117 msg->lateral_velocity = lateral_velocity_;
118 msg->angular_velocity = angular_velocity_;
119
120 publisher_->publish(std::move(msg));
121 RCLCPP_INFO(this->get_logger(),
122 "Published velocity: Forward %.2f m/s, Lateral %.2f m/s, "
123 "Angular %.2f rad/s",
124 forward_velocity_, lateral_velocity_, angular_velocity_);
125 }
126
127 void clear_velocity() {
128 forward_velocity_ = 0.0;
129 lateral_velocity_ = 0.0;
130 angular_velocity_ = 0.0;
131 }
132
133 bool set_forward(double forward) {
134 // check value range, mc has thresholds to start movement
135 if (std::abs(forward) < 0.005) {
136 forward_velocity_ = 0.0;
137 return true;
138 } else if ((std::abs(forward) > max_forward_speed_) ||
139 (std::abs(forward) < min_forward_speed_)) {
140 RCLCPP_ERROR(this->get_logger(), "input value out of range, exiting");
141 return false;
142 } else {
143 forward_velocity_ = forward;
144 return true;
145 }
146 }
147
148 bool set_lateral(double lateral) {
149 // check value range, mc has thresholds to start movement
150 if (std::abs(lateral) < 0.005) {
151 lateral_velocity_ = 0.0;
152 return true;
153 } else if ((std::abs(lateral) > max_lateral_speed_) ||
154 (std::abs(lateral) < min_lateral_speed_)) {
155 RCLCPP_ERROR(this->get_logger(), "input value out of range, exiting");
156 return false;
157 } else {
158 lateral_velocity_ = lateral;
159 return true;
160 }
161 }
162
163 bool set_angular(double angular) {
164 // check value range, mc has thresholds to start movement
165 if (std::abs(angular) < 0.005) {
166 angular_velocity_ = 0.0;
167 return true;
168 } else if ((std::abs(angular) > max_angular_speed_) ||
169 (std::abs(angular) < min_angular_speed_)) {
170 RCLCPP_ERROR(this->get_logger(), "input value out of range, exiting");
171 return false;
172 } else {
173 angular_velocity_ = angular;
174 return true;
175 }
176 }
177
178private:
179 rclcpp::Publisher<aimdk_msgs::msg::McLocomotionVelocity>::SharedPtr
180 publisher_;
181 rclcpp::Client<aimdk_msgs::srv::SetMcInputSource>::SharedPtr client_;
182 rclcpp::TimerBase::SharedPtr timer_;
183
184 double forward_velocity_;
185 double lateral_velocity_;
186 double angular_velocity_;
187
188 double max_forward_speed_;
189 double max_lateral_speed_;
190 double max_angular_speed_;
191
192 double min_forward_speed_;
193 double min_lateral_speed_;
194 double min_angular_speed_;
195};
196
197// Signal Processing
198std::shared_ptr<DirectVelocityControl> global_node = nullptr;
199void signal_handler(int sig) {
200 if (global_node) {
201 global_node->clear_velocity();
202 RCLCPP_INFO(global_node->get_logger(),
203 "Received signal %d: clearing velocity and shutting down", sig);
204 }
205 rclcpp::shutdown();
206 exit(sig);
207}
208
209int main(int argc, char *argv[]) {
210 rclcpp::init(argc, argv);
211 signal(SIGINT, signal_handler);
212 signal(SIGTERM, signal_handler);
213
214 global_node = std::make_shared<DirectVelocityControl>();
215 auto node = global_node;
216
217 if (!node->register_input_source()) {
218 RCLCPP_ERROR(node->get_logger(),
219 "❌ Input source registration failed, exiting");
220 global_node.reset();
221 rclcpp::shutdown();
222 return 1;
223 }
224
225 // get and check control values
226 // notice that mc has thresholds to start movement
227 double forward, lateral, angular;
228 std::cout << "Enter forward speed 0 or ±(0.2 ~ 1.0) m/s: ";
229 std::cin >> forward;
230 if (!node->set_forward(forward)) {
231 return 2;
232 }
233 std::cout << "Enter lateral speed 0 or ±(0.2 ~ 1.0) m/s: ";
234 std::cin >> lateral;
235 if (!node->set_lateral(lateral)) {
236 return 2;
237 }
238 std::cout << "Enter angular speed 0 or ±(0.1 ~ 1.0) rad/s: ";
239 std::cin >> angular;
240 if (!node->set_angular(angular)) {
241 return 2;
242 }
243
244 RCLCPP_INFO(node->get_logger(), "Setting velocity; moving for 5 seconds");
245
246 node->start_publish();
247
248 auto start_time = node->now();
249 while ((node->now() - start_time).seconds() < 5.0) {
250 rclcpp::spin_some(node);
251 std::this_thread::sleep_for(std::chrono::milliseconds(1));
252 }
253
254 node->clear_velocity();
255 RCLCPP_INFO(node->get_logger(), "5 seconds elapsed; robot stopped");
256
257 rclcpp::spin(node);
258 rclcpp::shutdown();
259 return 0;
260}
6.2.11 Joint Motor Control
This example demonstrates how to use ROS2 and the Ruckig library to control the robot’s joint movements.
Attention
Warning ⚠️ : Before running this example, you must stop the native motion control module using aima em stop-app mc on the robot’s Motion Control Computing Unit (PC1) to obtain control authority. Ensure robot safety at all times.
! This example directly controls the underlying motors (i.e. the HAL layer). Before running the program, please verify that the joint safety limits in the code match the actual robot model, and ensure safety!
Robot Joint Control Example
This example demonstrates how to control the robot’s joints using ROS2 and the Ruckig library. It includes the following features:
Robot joint model definition
Trajectory interpolation using Ruckig
Multi-joint coordinated control
Real-time position, velocity, and acceleration control
Dependencies
ROS2
Ruckig library
aimdk_msgs package
Build Instructions
Place the code in the
srcdirectory of your ROS2 workspaceAdd the following to your 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
)
Add dependencies in package.xml:
<depend>rclcpp</depend>
<depend>aimdk_msgs</depend>
<depend>ruckig</depend>
Example Function Overview
Four controller nodes are created to control:
Legs × 2 (12 joints)
Waist × 1 (3 joints)
Arms × 2 (14 joints)
Head × 1 (2 joints)
Demonstrated features:
Make a designated joint oscillate between ±0.5 radians every 10 seconds
Generate smooth motion trajectories using the Ruckig library
Publish joint control commands in real time
Customization
Add new control logic:
Modify the
SetTargetPositionfunctionAdd new control callback functions
Adjust control frequency:
Modify the
timerperiod (currently 3s) and the ruckigdt(currently 0.002s)
1#include <aimdk_msgs/msg/joint_command_array.hpp>
2#include <aimdk_msgs/msg/joint_state_array.hpp>
3#include <atomic>
4#include <cmath>
5#include <cstdlib>
6#include <map>
7#include <memory>
8#include <rclcpp/rclcpp.hpp>
9#include <ruckig/ruckig.hpp>
10#include <signal.h>
11#include <string>
12#include <thread>
13#include <vector>
14
15/**
16 * @brief Global variables and signal handling
17 */
18// Global variables to control program state
19std::atomic<bool> g_running(true);
20std::atomic<bool> g_emergency_stop(false);
21
22// Signal handler function
23void signal_handler(int) {
24 g_running = false;
25 RCLCPP_INFO(rclcpp::get_logger("main"),
26 "Received termination signal, shutting down...");
27}
28
29/**
30 * @brief Robot model definition
31 */
32enum class JointArea {
33 HEAD, // Head joints
34 ARM, // Arm joints
35 WAIST, // Waist joints
36 LEG, // Leg joints
37};
38
39/**
40 * @brief Joint information structure
41 */
42struct JointInfo {
43 std::string name; // Joint name
44 double lower_limit; // Joint angle lower limit
45 double upper_limit; // Joint angle upper limit
46 double kp; // Position control gain
47 double kd; // Velocity control gain
48};
49
50/**
51 * @brief Robot model configuration
52 * Contains parameters for all joints, enabling or disabling specific joints as
53 * needed
54 */
55std::map<JointArea, std::vector<JointInfo>> robot_model = {
56 {JointArea::LEG,
57 {
58 // Left leg joint configuration
59 {"left_hip_pitch_joint", -2.704, 2.556, 40.0, 4.0},
60 {"left_hip_roll_joint", -0.235, 2.906, 40.0, 4.0},
61 {"left_hip_yaw_joint", -1.684, 3.430, 30.0, 3.0},
62 {"left_knee_joint", 0.0000, 2.4073, 80.0, 8.0},
63 {"left_ankle_pitch_joint", -0.803, 0.453, 40.0, 4.0},
64 {"left_ankle_roll_joint", -0.2625, 0.2625, 20.0, 2.0},
65 // Right leg joint configuration
66 {"right_hip_pitch_joint", -2.704, 2.556, 40.0, 4.0},
67 {"right_hip_roll_joint", -2.906, 0.235, 40.0, 4.0},
68 {"right_hip_yaw_joint", -3.430, 1.684, 30.0, 3.0},
69 {"right_knee_joint", 0.0000, 2.4073, 80.0, 8.0},
70 {"right_ankle_pitch_joint", -0.803, 0.453, 40.0, 4.0},
71 {"right_ankle_roll_joint", -0.2625, 0.2625, 20.0, 2.0},
72 }},
73
74 {JointArea::WAIST,
75 {
76 // Waist joint configuration
77 {"waist_yaw_joint", -3.43, 2.382, 20.0, 4.0},
78 {"waist_pitch_joint", -0.314, 0.314, 20.0, 4.0},
79 {"waist_roll_joint", -0.488, 0.488, 20.0, 4.0},
80 }},
81 {JointArea::ARM,
82 {
83 // Left arm joint configuration
84 {"left_shoulder_pitch_joint", -3.08, 2.04, 20.0, 2.0},
85 {"left_shoulder_roll_joint", -0.061, 2.993, 20.0, 2.0},
86 {"left_shoulder_yaw_joint", -2.556, 2.556, 20.0, 2.0},
87 {"left_elbow_joint", -2.3556, 0.0, 20.0, 2.0},
88 {"left_wrist_yaw_joint", -2.556, 2.556, 20.0, 2.0},
89 {"left_wrist_pitch_joint", -0.558, 0.558, 20.0, 2.0},
90 {"left_wrist_roll_joint", -1.571, 0.724, 20.0, 2.0},
91 // Right arm joint configuration
92 {"right_shoulder_pitch_joint", -3.08, 2.04, 20.0, 2.0},
93 {"right_shoulder_roll_joint", -2.993, 0.061, 20.0, 2.0},
94 {"right_shoulder_yaw_joint", -2.556, 2.556, 20.0, 2.0},
95 {"right_elbow_joint", -2.3556, 0.0000, 20.0, 2.0},
96 {"right_wrist_yaw_joint", -2.556, 2.556, 20.0, 2.0},
97 {"right_wrist_pitch_joint", -0.558, 0.558, 20.0, 2.0},
98 {"right_wrist_roll_joint", -0.724, 1.571, 20.0, 2.0},
99 }},
100 {JointArea::HEAD,
101 {
102 // Head joint configuration
103 {"head_yaw_joint", -0.366, 0.366, 20.0, 2.0},
104 {"head_pitch_joint", -0.3838, 0.3838, 20.0, 2.0},
105 }},
106};
107
108/**
109 * @brief Joint controller node class
110 * @tparam DOFs Degrees of freedom
111 * @tparam Area Joint area
112 */
113template <int DOFs, JointArea Area>
114class JointControllerNode : public rclcpp::Node {
115public:
116 /**
117 * @brief Constructor
118 * @param node_name Node name
119 * @param sub_topic Subscription topic name
120 * @param pub_topic Publication topic name
121 * @param qos QoS configuration
122 */
123 JointControllerNode(std::string node_name, std::string sub_topic,
124 std::string pub_topic,
125 rclcpp::QoS qos = rclcpp::SensorDataQoS())
126 : Node(node_name), ruckig(0.002) {
127 joint_info_ = robot_model[Area];
128 if (joint_info_.size() != DOFs) {
129 RCLCPP_ERROR(this->get_logger(), "Joint count mismatch.");
130 exit(1);
131 }
132
133 // Set motion constraints for Ruckig trajectory planner
134 for (int i = 0; i < DOFs; ++i) {
135 input.max_velocity[i] = 1.0; // Max velocity limit
136 input.max_acceleration[i] = 1.0; // Max acceleration limit
137 input.max_jerk[i] = 25.0; // Max jerk (change of acceleration) limit
138 }
139
140 // Create joint state subscriber
141 sub_ = this->create_subscription<aimdk_msgs::msg::JointStateArray>(
142 sub_topic, qos,
143 std::bind(&JointControllerNode::JointStateCallback, this,
144 std::placeholders::_1));
145
146 // Create joint command publisher
147 pub_ = this->create_publisher<aimdk_msgs::msg::JointCommandArray>(pub_topic,
148 qos);
149 }
150
151private:
152 // Ruckig trajectory planner variables
153 ruckig::Ruckig<DOFs> ruckig; // Trajectory planner instance
154 ruckig::InputParameter<DOFs> input; // Input parameters
155 ruckig::OutputParameter<DOFs> output; // Output parameters
156 bool ruckig_initialized_ = false; // Trajectory planner initialization flag
157 std::vector<JointInfo> joint_info_; // Joint information list
158
159 // ROS communication variables
160 rclcpp::Subscription<aimdk_msgs::msg::JointStateArray>::SharedPtr
161 sub_; // State subscriber
162 rclcpp::Publisher<aimdk_msgs::msg::JointCommandArray>::SharedPtr
163 pub_; // Command publisher
164
165 /**
166 * @brief Joint state callback function
167 * @param msg Joint state message
168 */
169 void
170 JointStateCallback(const aimdk_msgs::msg::JointStateArray::SharedPtr msg) {
171 // Initialize trajectory planner on first state reception
172 if (!ruckig_initialized_) {
173 for (int i = 0; i < DOFs; ++i) {
174 input.current_position[i] = msg->joints[i].position;
175 input.current_velocity[i] = msg->joints[i].velocity;
176 input.current_acceleration[i] = 0.0;
177 }
178 ruckig_initialized_ = true;
179 RCLCPP_INFO(this->get_logger(),
180 "Ruckig trajectory planner initialization complete");
181 }
182 }
183
184public:
185 /**
186 * @brief Set target joint position
187 * @param joint_name Joint name
188 * @param target_position Target position
189 * @return Whether the target position was successfully set
190 */
191 bool SetTargetPosition(std::string joint_name, double target_position) {
192 if (!ruckig_initialized_) {
193 RCLCPP_WARN(this->get_logger(),
194 "Ruckig trajectory planner not initialized");
195 return false;
196 }
197
198 // Find target joint and set its position
199 int target_joint = -1;
200 for (int i = 0; i < DOFs; ++i) {
201 if (joint_info_[i].name == joint_name) {
202 // Check if target position is within limits
203 if (target_position < joint_info_[i].lower_limit ||
204 target_position > joint_info_[i].upper_limit) {
205 RCLCPP_ERROR(
206 this->get_logger(),
207 "Target position %.3f exceeds limit for joint %s [%.3f, %.3f]",
208 target_position, joint_name.c_str(), joint_info_[i].lower_limit,
209 joint_info_[i].upper_limit);
210 return false;
211 }
212 input.target_position[i] = target_position;
213 input.target_velocity[i] = 0.0;
214 input.target_acceleration[i] = 0.0;
215 target_joint = i;
216 } else {
217 input.target_position[i] = input.current_position[i];
218 input.target_velocity[i] = 0.0;
219 input.target_acceleration[i] = 0.0;
220 }
221 }
222
223 if (target_joint == -1) {
224 RCLCPP_ERROR(this->get_logger(), "Joint %s not found",
225 joint_name.c_str());
226 return false;
227 }
228
229 // Perform trajectory planning and send command using Ruckig
230 const double tolerance = 1e-6;
231 while (g_running && rclcpp::ok() && !g_emergency_stop) {
232 auto result = ruckig.update(input, output);
233 if (result != ruckig::Result::Working &&
234 result != ruckig::Result::Finished) {
235 RCLCPP_WARN(this->get_logger(), "Trajectory planning failed");
236 break;
237 }
238
239 // Update current state
240 for (int i = 0; i < DOFs; ++i) {
241 input.current_position[i] = output.new_position[i];
242 input.current_velocity[i] = output.new_velocity[i];
243 input.current_acceleration[i] = output.new_acceleration[i];
244 }
245
246 // Check if target position is reached
247 if (std::abs(output.new_position[target_joint] - target_position) <
248 tolerance) {
249 RCLCPP_INFO(this->get_logger(), "Joint %s reached target position",
250 joint_name.c_str());
251 break;
252 }
253
254 // Create and send joint command
255 aimdk_msgs::msg::JointCommandArray cmd;
256 cmd.joints.resize(DOFs);
257 for (int i = 0; i < DOFs; ++i) {
258 auto &joint = joint_info_[i];
259 cmd.joints[i].name = joint.name;
260 cmd.joints[i].position = output.new_position[i];
261 cmd.joints[i].velocity = output.new_velocity[i];
262 cmd.joints[i].stiffness = joint.kp;
263 cmd.joints[i].damping = joint.kd;
264 }
265 pub_->publish(cmd);
266
267 // Short delay to avoid excessive CPU usage
268 std::this_thread::sleep_for(std::chrono::milliseconds(2));
269 }
270
271 return true;
272 }
273
274 /**
275 * @brief Safely stop all joints
276 */
277 void safe_stop() {
278 if (!ruckig_initialized_) {
279 RCLCPP_WARN(this->get_logger(), "Ruckig trajectory planner not "
280 "initialized, cannot perform safe stop");
281 return;
282 }
283
284 RCLCPP_INFO(this->get_logger(), "Performing safe stop...");
285
286 // Set all joint target positions to current positions
287 for (int i = 0; i < DOFs; ++i) {
288 input.target_position[i] = input.current_position[i];
289 input.target_velocity[i] = 0.0;
290 input.target_acceleration[i] = 0.0;
291 }
292
293 // Send final command to ensure joints stop
294 aimdk_msgs::msg::JointCommandArray cmd;
295 cmd.joints.resize(DOFs);
296 for (int i = 0; i < DOFs; ++i) {
297 auto &joint = joint_info_[i];
298 cmd.joints[i].name = joint.name;
299 cmd.joints[i].position = input.current_position[i];
300 cmd.joints[i].velocity = 0.0;
301 cmd.joints[i].stiffness = joint.kp;
302 cmd.joints[i].damping = joint.kd;
303 }
304 pub_->publish(cmd);
305
306 RCLCPP_INFO(this->get_logger(), "Safe stop complete");
307 }
308
309 /**
310 * @brief Emergency stop for all joints
311 */
312 void emergency_stop() {
313 g_emergency_stop = true;
314 safe_stop();
315 RCLCPP_ERROR(this->get_logger(), "Emergency stop triggered");
316 }
317};
318
319/**
320 * @brief Main function
321 */
322int main(int argc, char *argv[]) {
323 rclcpp::init(argc, argv);
324
325 // Set up signal handling
326 signal(SIGINT, signal_handler);
327 signal(SIGTERM, signal_handler);
328
329 try {
330 // Create leg controller node
331 auto leg_node = std::make_shared<JointControllerNode<12, JointArea::LEG>>(
332 "leg_node", "/aima/hal/joint/leg/state", "/aima/hal/joint/leg/command");
333
334 // Create timer node
335 rclcpp::Node::SharedPtr timer_node =
336 rclcpp::Node::make_shared("timer_node");
337 double position = 0.8;
338
339 // Create timer callback function
340 auto timer = timer_node->create_wall_timer(std::chrono::seconds(3), [&]() {
341 if (!g_running || g_emergency_stop)
342 return; // If the program is shutting down or emergency stopped, do not
343 // execute new actions
344 position = -position;
345 position = 1.3 + position;
346 if (!leg_node->SetTargetPosition("left_knee_joint", position)) {
347 RCLCPP_ERROR(rclcpp::get_logger("main"),
348 "Failed to set target position");
349 }
350 });
351
352 // Create executor
353 rclcpp::executors::MultiThreadedExecutor executor;
354 executor.add_node(leg_node);
355 executor.add_node(timer_node);
356
357 // Main loop
358 while (g_running && rclcpp::ok() && !g_emergency_stop) {
359 executor.spin_once(std::chrono::milliseconds(100));
360 }
361
362 // Safely stop all joints
363 RCLCPP_INFO(rclcpp::get_logger("main"), "Safely stopping all joints...");
364 leg_node->safe_stop();
365
366 // Wait a short time to ensure command transmission is complete
367 std::this_thread::sleep_for(std::chrono::milliseconds(100));
368
369 // Clean up resources
370 RCLCPP_INFO(rclcpp::get_logger("main"), "Cleaning up resources...");
371 leg_node.reset();
372 timer_node.reset();
373
374 } catch (const std::exception &e) {
375 RCLCPP_ERROR(rclcpp::get_logger("main"), "Exception occurred: %s",
376 e.what());
377 g_emergency_stop = true;
378 } catch (...) {
379 RCLCPP_ERROR(rclcpp::get_logger("main"), "Unknown exception occurred");
380 g_emergency_stop = true;
381 }
382
383 RCLCPP_INFO(rclcpp::get_logger("main"), "Program exited safely");
384 rclcpp::shutdown();
385 return 0;
386}
6.2.12 Keyboard Robot Control
This example enables controlling the robot’s forward, backward, and turning movements using PC keyboard input.
Use W A S D to control the walking direction, increase/decrease linear velocity (±0.2 m/s), use Q / E to increase/decrease angular velocity (±0.1 rad/s), ESC exits the program and releases terminal resources, and Space immediately resets the velocity to zero to perform an emergency stop.
Caution
Note: Before running this example, use the controller to switch the robot to Stable Standing Mode. (Standing Preparation Mode (Position-Control Standing Mode) / Locomotion Mode, press R2 + X; for other modes, refer to the mode routing diagram). Then, in the robot’s terminal, run aima em stop-app rc to disable the remote controller and prevent channel occupation.
Before enabling keyboard control, an input source must be registered (already implemented in this example).
The curse module needs to be installed before running:
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 <curses.h>
13#include <rclcpp/rclcpp.hpp>
14
15using aimdk_msgs::msg::McLocomotionVelocity;
16using std::placeholders::_1;
17
18class KeyboardVelocityController : public rclcpp::Node {
19public:
20 KeyboardVelocityController()
21 : Node("keyboard_velocity_controller"), forward_velocity_(0.0),
22 lateral_velocity_(0.0), angular_velocity_(0.0), step_(0.2),
23 angular_step_(0.1) {
24 pub_ = this->create_publisher<McLocomotionVelocity>(
25 "/aima/mc/locomotion/velocity", 10);
26 client_ = this->create_client<aimdk_msgs::srv::SetMcInputSource>(
27 "/aimdk_5Fmsgs/srv/SetMcInputSource");
28 // Register input source
29 if (!register_input_source()) {
30 RCLCPP_ERROR(this->get_logger(),
31 "❌ Input source registration failed, exiting");
32 throw std::runtime_error("Input source registration failed");
33 }
34 // Initialize ncurses
35 initscr();
36 cbreak();
37 noecho();
38 keypad(stdscr, TRUE);
39 nodelay(stdscr, TRUE);
40
41 timer_ = this->create_wall_timer(
42 std::chrono::milliseconds(50),
43 std::bind(&KeyboardVelocityController::checkKeyAndPublish, this));
44
45 RCLCPP_INFO(this->get_logger(),
46 "Control started: W/S Forward/Backward | A/D Strafe Left/Right "
47 "| Q/E Turn Left/Right | Space Stop | ESC Exit");
48 }
49
50 ~KeyboardVelocityController() {
51 endwin(); // Restore terminal
52 }
53
54private:
55 rclcpp::Publisher<McLocomotionVelocity>::SharedPtr pub_;
56 rclcpp::Client<aimdk_msgs::srv::SetMcInputSource>::SharedPtr client_;
57 rclcpp::TimerBase::SharedPtr timer_;
58
59 float forward_velocity_, lateral_velocity_, angular_velocity_;
60 const float step_, angular_step_;
61
62 bool register_input_source() {
63 const std::chrono::seconds srv_timeout(8);
64 auto start_time = std::chrono::steady_clock::now();
65 while (!client_->wait_for_service(std::chrono::seconds(2))) {
66 if (std::chrono::steady_clock::now() - start_time > srv_timeout) {
67 RCLCPP_ERROR(this->get_logger(), "Waiting for service timed out");
68 return false;
69 }
70 RCLCPP_INFO(this->get_logger(), "Waiting for input source service...");
71 }
72
73 // Try ADD first; if it fails (likely already registered), fall back to
74 // ENABLE
75 if (call_input_source( // ADD (1001)
76 aimdk_msgs::msg::McInputAction::INPUTACTION_ADD)) {
77 RCLCPP_INFO(this->get_logger(), "✅ Input source registered (ADD).");
78 return true;
79 }
80 RCLCPP_INFO(this->get_logger(),
81 "ADD failed (likely already registered), trying ENABLE...");
82 if (call_input_source( // ENABLE (2001)
83 aimdk_msgs::msg::McInputAction::INPUTACTION_ENABLE)) {
84 RCLCPP_INFO(this->get_logger(), "✅ Input source enabled.");
85 return true;
86 }
87 RCLCPP_ERROR(this->get_logger(),
88 "❌ Failed to register input source via ADD or ENABLE.");
89 return false;
90 }
91
92 bool call_input_source(int32_t action_value) {
93 auto request =
94 std::make_shared<aimdk_msgs::srv::SetMcInputSource::Request>();
95 request->action.value = action_value;
96 request->input_source.name = "node.keyboard";
97 request->input_source.priority = 40;
98 request->input_source.timeout = 1000;
99
100 auto timeout = std::chrono::milliseconds(250);
101
102 for (int i = 0; i < 8; i++) {
103 request->request.header.stamp = this->now();
104 auto future = client_->async_send_request(request);
105 auto retcode = rclcpp::spin_until_future_complete(
106 this->get_node_base_interface(), future, timeout);
107 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
108 // retry as remote peer is NOT handled well by ROS
109 RCLCPP_INFO(
110 this->get_logger(), "trying input source action %s (%d)... [%d]",
111 action_value == aimdk_msgs::msg::McInputAction::INPUTACTION_ADD
112 ? "ADD"
113 : "ENABLE",
114 action_value, i);
115 continue;
116 }
117 auto response = future.get();
118 return response->response.header.code == 0;
119 }
120 return false;
121 }
122
123 void checkKeyAndPublish() {
124 int ch = getch(); // non-blocking read
125
126 switch (ch) {
127 case ' ': // Space key
128 forward_velocity_ = 0.0;
129 lateral_velocity_ = 0.0;
130 angular_velocity_ = 0.0;
131 break;
132 case 'w':
133 forward_velocity_ = std::min(forward_velocity_ + step_, 1.0f);
134 break;
135 case 's':
136 forward_velocity_ = std::max(forward_velocity_ - step_, -1.0f);
137 break;
138 case 'a':
139 lateral_velocity_ = std::min(lateral_velocity_ + step_, 1.0f);
140 break;
141 case 'd':
142 lateral_velocity_ = std::max(lateral_velocity_ - step_, -1.0f);
143 break;
144 case 'q':
145 angular_velocity_ = std::min(angular_velocity_ + angular_step_, 1.0f);
146 break;
147 case 'e':
148 angular_velocity_ = std::max(angular_velocity_ - angular_step_, -1.0f);
149 break;
150 case 27: // ESC Key
151 RCLCPP_INFO(this->get_logger(), "Exiting control");
152 rclcpp::shutdown();
153 return;
154 }
155
156 auto msg = std::make_unique<McLocomotionVelocity>();
157 msg->header = aimdk_msgs::msg::MessageHeader();
158 msg->header.stamp = this->now();
159 msg->source = "node.keyboard";
160 msg->forward_velocity = forward_velocity_;
161 msg->lateral_velocity = lateral_velocity_;
162 msg->angular_velocity = angular_velocity_;
163
164 float fwd = forward_velocity_;
165 float lat = lateral_velocity_;
166 float ang = angular_velocity_;
167
168 pub_->publish(std::move(msg));
169
170 // Screen Output
171 clear();
172 mvprintw(0, 0,
173 "W/S: Forward/Backward | A/D: Left/Right Strafe | Q/E: Turn "
174 "Left/Right | Space: Stop | ESC: Exit");
175 mvprintw(2, 0,
176 "Speed Status: Forward: %.2f m/s | Lateral: %.2f m/s | Angular: "
177 "%.2f rad/s",
178 fwd, lat, ang);
179 refresh();
180 }
181};
182
183int main(int argc, char *argv[]) {
184 rclcpp::init(argc, argv);
185 try {
186 auto node = std::make_shared<KeyboardVelocityController>();
187 rclcpp::spin(node);
188 } catch (const std::exception &e) {
189 RCLCPP_FATAL(rclcpp::get_logger("main"),
190 "Program exited with exception: %s", e.what());
191 }
192 rclcpp::shutdown();
193 return 0;
194}
6.2.13 Take Photo
This example uses take_photo. Before running the node, modify the camera topic from which to capture the image. After starting the node, an /images/ directory will be created, and the current frame will be saved into this directory.
1#include <chrono>
2#include <cv_bridge/cv_bridge.h>
3#include <filesystem>
4#include <opencv2/core.hpp>
5#include <opencv2/imgcodecs.hpp>
6#include <opencv2/imgproc.hpp>
7#include <rclcpp/rclcpp.hpp>
8#include <sensor_msgs/msg/image.hpp>
9#include <string>
10
11class SaveOneRaw : public rclcpp::Node {
12public:
13 SaveOneRaw() : Node("save_one_image"), saved_(false) {
14 topic_ = declare_parameter<std::string>(
15 "image_topic", "/aima/hal/sensor/stereo_head_front_left/rgb_image");
16
17 std::filesystem::create_directories("images");
18
19 auto qos = rclcpp::SensorDataQoS(); // BestEffort/Volatile
20 sub_ = create_subscription<sensor_msgs::msg::Image>(
21 topic_, qos, std::bind(&SaveOneRaw::cb, this, std::placeholders::_1));
22
23 RCLCPP_INFO(get_logger(), "Subscribing (raw): %s", topic_.c_str());
24 }
25
26private:
27 void cb(const sensor_msgs::msg::Image::SharedPtr msg) {
28 if (saved_)
29 return;
30
31 try {
32 // Obtain the Mat without copying by not specifying encoding
33 cv_bridge::CvImageConstPtr cvp = cv_bridge::toCvShare(msg);
34 cv::Mat img = cvp->image;
35
36 // Convert to BGR for uniform saving
37 if (msg->encoding == "rgb8") {
38 cv::cvtColor(img, img, cv::COLOR_RGB2BGR);
39 } else if (msg->encoding == "mono8") {
40 cv::cvtColor(img, img, cv::COLOR_GRAY2BGR);
41 } // bgr8 Use this directly; add more branches as needed to support
42 // additional encodings.
43
44 auto now = std::chrono::system_clock::now();
45 auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
46 now.time_since_epoch())
47 .count();
48 std::string path = "images/frame_" + std::to_string(ms) + ".png";
49
50 if (cv::imwrite(path, img)) {
51 RCLCPP_INFO(get_logger(), "Saved: %s (%dx%d)", path.c_str(), img.cols,
52 img.rows);
53 saved_ = true;
54 rclcpp::shutdown();
55 } else {
56 RCLCPP_ERROR(get_logger(), "cv::imwrite failed: %s", path.c_str());
57 }
58 } catch (const std::exception &e) {
59 RCLCPP_ERROR(get_logger(), "raw decode failed: %s", e.what());
60 // Do not set the saved flag; wait for the next frame
61 }
62 }
63
64 std::string topic_;
65 bool saved_;
66 rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr sub_;
67};
68
69int main(int argc, char **argv) {
70 rclcpp::init(argc, argv);
71 rclcpp::spin(std::make_shared<SaveOneRaw>());
72 return 0;
73}
6.2.14 Camera Streaming Example Collection
This example set provides multiple camera data subscription and processing functions, supporting data streams from depth cameras, stereo cameras, and monocular cameras.
These camera subscription examples do not provide actual application-level logic; they only print basic camera data information. If you are familiar with ROS2, you may notice that ros2 topic echo + ros2 topic hz can also achieve similar functionality. You may directly check the topic list in the SDK interface manual to start developing your own module, or you may use these camera examples as scaffolding to integrate your own business logic. All published sensor data is raw and unprocessed (e.g., without undistortion). If you need detailed sensor information (such as resolution, focal length, etc.), please refer to the camera_info topic.
Depth Camera Data Subscription
This example uses echo_camera_rgbd, subscribing to the /aima/hal/sensor/rgbd_head_front/ topic to receive depth camera data, supporting multiple data types including depth images, RGB images, compressed RGB images, and camera intrinsic parameters.
Features:
Supports multiple data types (depth image, RGB image, compressed image, camera intrinsics)
Real-time FPS statistics and data display
Supports RGB video recording
Configurable topic type selection
Supported Data Types:
depth_image: Depth image (sensor_msgs/Image)rgb_image: RGB image (sensor_msgs/Image)rgb_image_compressed: Compressed RGB image (sensor_msgs/CompressedImage)camera_info: Camera intrinsic parameters (sensor_msgs/CameraInfo)
1#include <algorithm>
2#include <deque>
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/core.hpp>
16#include <opencv2/imgproc.hpp>
17#include <opencv2/videoio.hpp>
18
19/**
20 * @brief Example of subscribing to multiple topics for the head depth camera
21 *
22 * You can select which topic type to subscribe to via the startup argument
23 * --ros-args -p topic_type:=<type>:
24 * - depth_image: Depth image (sensor_msgs/Image)
25 * - rgb_image: RGB image (sensor_msgs/Image)
26 * - rgb_image_compressed: RGB compressed image (sensor_msgs/CompressedImage)
27 * - rgb_camera_info: Camera intrinsic parameters (sensor_msgs/CameraInfo)
28 * - depth_camera_info: Camera intrinsic parameters (sensor_msgs/CameraInfo)
29 *
30 * Examples:
31 * ros2 run examples echo_camera_rgbd --ros-args -p
32 * topic_type:=rgb_image ros2
33 * run examples echo_camera_rgbd --ros-args -p
34 * topic_type:=rgb_camera_info
35 *
36 * topic_type defaults to "rgb_image"
37 *
38 * See individual callbacks for more detailed comments
39 */
40class CameraTopicEcho : public rclcpp::Node {
41public:
42 CameraTopicEcho() : Node("camera_topic_echo") {
43 // Select which topic type to subscribe to
44 topic_type_ = declare_parameter<std::string>("topic_type", "rgb_image");
45 dump_video_path_ = declare_parameter<std::string>("dump_video_path", "");
46 dump_video_fps_ = declare_parameter<double>("dump_video_fps", 30.0);
47
48 // Subscribed topics and their message layouts
49 // 1. /aima/hal/sensor/rgbd_head_front/depth_image
50 // - topic_type: depth_image
51 // - message type: sensor_msgs::msg::Image
52 // - frame_id: rgbd_head_front
53 // - contents: depth image
54 // 2. /aima/hal/sensor/rgbd_head_front/rgb_image
55 // - topic_type: rgb_image
56 // - message type: sensor_msgs::msg::Image
57 // - frame_id: rgbd_head_front
58 // - contents: RGB image
59 // 3. /aima/hal/sensor/rgbd_head_front/rgb_image/compressed
60 // - topic_type: rgb_image_compressed
61 // - message type: sensor_msgs::msg::CompressedImage
62 // - frame_id: rgbd_head_front
63 // - contents: RGB compressed image
64 // 4. /aima/hal/sensor/rgbd_head_front/rgb_camera_info
65 // - topic_type: camera_info
66 // - message type: sensor_msgs::msg::CameraInfo
67 // - frame_id: rgbd_head_front
68 // - contents: RGB camera intrinsic parameters
69 // 5. /aima/hal/sensor/rgbd_head_front/depth_camera_info
70 // - topic_type: camera_info
71 // - message type: sensor_msgs::msg::CameraInfo
72 // - frame_id: rgbd_head_front
73 // - contents: RGB camera intrinsic parameters
74
75 auto qos = rclcpp::SensorDataQoS();
76
77 // Enable depth image subscription
78 if (topic_type_ == "depth_image") {
79 topic_name_ = "/aima/hal/sensor/rgbd_head_front/depth_image";
80 sub_image_ = create_subscription<sensor_msgs::msg::Image>(
81 topic_name_, qos,
82 std::bind(&CameraTopicEcho::cb_image, this, std::placeholders::_1));
83 RCLCPP_INFO(get_logger(), "✅ Subscribing Depth Image: %s",
84 topic_name_.c_str());
85
86 // Enable RGB image subscription
87 } else if (topic_type_ == "rgb_image") {
88 topic_name_ = "/aima/hal/sensor/rgbd_head_front/rgb_image";
89 sub_image_ = create_subscription<sensor_msgs::msg::Image>(
90 topic_name_, qos,
91 std::bind(&CameraTopicEcho::cb_image, this, std::placeholders::_1));
92 RCLCPP_INFO(get_logger(), "✅ Subscribing RGB Image: %s",
93 topic_name_.c_str());
94 if (!dump_video_path_.empty()) {
95 RCLCPP_INFO(get_logger(), "📝 Will dump received images to video: %s",
96 dump_video_path_.c_str());
97 }
98
99 // Enable RGB compressed image subscription
100 } else if (topic_type_ == "rgb_image_compressed") {
101 topic_name_ = "/aima/hal/sensor/rgbd_head_front/rgb_image/compressed";
102 sub_compressed_ = create_subscription<sensor_msgs::msg::CompressedImage>(
103 topic_name_, qos,
104 std::bind(&CameraTopicEcho::cb_compressed, this,
105 std::placeholders::_1));
106 RCLCPP_INFO(get_logger(), "✅ Subscribing CompressedImage: %s",
107 topic_name_.c_str());
108
109 // Enable rgb camera info subscription
110 } else if (topic_type_ == "rgb_camera_info") {
111 topic_name_ = "/aima/hal/sensor/rgbd_head_front/rgb_camera_info";
112 // RGB-D CameraInfo subscriptions are different from other cameras.
113 // The messages arrive at about 30Hz and SensorDataQoS is sufficient.
114 sub_camerainfo_ = create_subscription<sensor_msgs::msg::CameraInfo>(
115 topic_name_, qos,
116 std::bind(&CameraTopicEcho::cb_camerainfo, this,
117 std::placeholders::_1));
118 RCLCPP_INFO(get_logger(), "✅ Subscribing RGB CameraInfo: %s",
119 topic_name_.c_str());
120
121 // Enable depth camera info subscription
122 } else if (topic_type_ == "depth_camera_info") {
123 topic_name_ = "/aima/hal/sensor/rgbd_head_front/depth_camera_info";
124 // RGB-D CameraInfo subscriptions are different from other cameras.
125 // The messages arrive at about 30Hz and SensorDataQoS is sufficient.
126 sub_camerainfo_ = create_subscription<sensor_msgs::msg::CameraInfo>(
127 topic_name_, qos,
128 std::bind(&CameraTopicEcho::cb_camerainfo, this,
129 std::placeholders::_1));
130 RCLCPP_INFO(get_logger(), "✅ Subscribing Depth CameraInfo: %s",
131 topic_name_.c_str());
132
133 // Unknown topic_type error
134 } else {
135 RCLCPP_ERROR(get_logger(), "Unknown topic_type: %s", topic_type_.c_str());
136 throw std::runtime_error("Unknown topic_type");
137 }
138 }
139
140 ~CameraTopicEcho() override {
141 if (video_writer_.isOpened()) {
142 video_writer_.release();
143 RCLCPP_INFO(get_logger(), "Video file closed.");
144 }
145 }
146
147private:
148 // Image callback (depth/RGB image)
149 void cb_image(const sensor_msgs::msg::Image::SharedPtr msg) {
150 update_arrivals();
151
152 if (should_print()) {
153 RCLCPP_INFO(get_logger(),
154 "📸 %s received\n"
155 " • frame_id: %s\n"
156 " • stamp (sec): %.6f\n"
157 " • encoding: %s\n"
158 " • size (WxH): %u x %u\n"
159 " • step (bytes/row):%u\n"
160 " • is_bigendian: %u\n"
161 " • recv FPS (1s): %.1f",
162 topic_type_.c_str(), msg->header.frame_id.c_str(),
163 rclcpp::Time(msg->header.stamp).seconds(),
164 msg->encoding.c_str(), msg->width, msg->height, msg->step,
165 msg->is_bigendian, get_fps());
166 }
167
168 // Video dump is supported only for RGB images
169 if (topic_type_ == "rgb_image" && !dump_video_path_.empty()) {
170 dump_image_to_video(msg);
171 }
172 }
173
174 // CompressedImage callback
175 void cb_compressed(const sensor_msgs::msg::CompressedImage::SharedPtr msg) {
176 update_arrivals();
177
178 if (should_print()) {
179 RCLCPP_INFO(get_logger(),
180 "🗜️ CompressedImage received\n"
181 " • frame_id: %s\n"
182 " • stamp (sec): %.6f\n"
183 " • format: %s\n"
184 " • data size: %zu\n"
185 " • recv FPS (1s): %.1f",
186 msg->header.frame_id.c_str(),
187 rclcpp::Time(msg->header.stamp).seconds(),
188 msg->format.c_str(), msg->data.size(), get_fps());
189 }
190 }
191
192 // CameraInfo callback (camera intrinsic parameters)
193 void cb_camerainfo(const sensor_msgs::msg::CameraInfo::SharedPtr msg) {
194 // CameraInfo is typically published once; print it once
195 std::ostringstream oss;
196 oss << "📷 " << topic_type_ << " received\n"
197 << " • frame_id: " << msg->header.frame_id << "\n"
198 << " • stamp (sec): " << rclcpp::Time(msg->header.stamp).seconds()
199 << "\n"
200 << " • width x height: " << msg->width << " x " << msg->height << "\n"
201 << " • distortion_model:" << msg->distortion_model << "\n"
202 << " • D: [";
203 for (size_t i = 0; i < msg->d.size(); ++i) {
204 oss << msg->d[i];
205 if (i + 1 < msg->d.size())
206 oss << ", ";
207 }
208 oss << "]\n • K: [";
209 for (int i = 0; i < 9; ++i) {
210 oss << msg->k[i];
211 if (i + 1 < 9)
212 oss << ", ";
213 }
214 oss << "]\n • R: [";
215 for (int i = 0; i < 9; ++i) {
216 oss << msg->r[i];
217 if (i + 1 < 9)
218 oss << ", ";
219 }
220 oss << "]\n • P: [";
221 for (int i = 0; i < 12; ++i) {
222 oss << msg->p[i];
223 if (i + 1 < 12)
224 oss << ", ";
225 }
226 oss << "]\n"
227 << " • binning_x: " << msg->binning_x << "\n"
228 << " • binning_y: " << msg->binning_y << "\n"
229 << " • roi: { x_offset: " << msg->roi.x_offset
230 << ", y_offset: " << msg->roi.y_offset
231 << ", height: " << msg->roi.height << ", width: " << msg->roi.width
232 << ", do_rectify: " << (msg->roi.do_rectify ? "true" : "false") << " }";
233 RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
234 }
235
236 // Track arrival timestamps to compute FPS
237 void update_arrivals() {
238 const rclcpp::Time now = this->get_clock()->now();
239 arrivals_.push_back(now);
240 while (!arrivals_.empty() && (now - arrivals_.front()).seconds() > 1.0) {
241 arrivals_.pop_front();
242 }
243 }
244 double get_fps() const { return static_cast<double>(arrivals_.size()); }
245
246 // Control printing frequency
247 bool should_print() {
248 const rclcpp::Time now = this->get_clock()->now();
249 if ((now - last_print_).seconds() >= 1.0) {
250 last_print_ = now;
251 return true;
252 }
253 return false;
254 }
255
256 // Dump received images to a video file (RGB images only)
257 void dump_image_to_video(const sensor_msgs::msg::Image::SharedPtr &msg) {
258 cv::Mat image;
259 try {
260 // Obtain the Mat without copying by not specifying encoding
261 cv_bridge::CvImageConstPtr cvp = cv_bridge::toCvShare(msg);
262 image = cvp->image;
263 // Convert to BGR for uniform saving
264 if (msg->encoding == "rgb8") {
265 cv::cvtColor(image, image, cv::COLOR_RGB2BGR);
266 } else {
267 RCLCPP_WARN(get_logger(), "image encoding not expected: %s",
268 msg->encoding.c_str());
269 return;
270 }
271 } catch (const std::exception &e) {
272 RCLCPP_WARN(get_logger(), "cv_bridge exception: %s", e.what());
273 return;
274 }
275
276 // Initialize VideoWriter
277 if (!video_writer_.isOpened()) {
278 int fourcc = cv::VideoWriter::fourcc('m', 'p', '4', 'v');
279 double fps = dump_video_fps_;
280 bool ok = video_writer_.open(dump_video_path_, fourcc, fps,
281 cv::Size(image.cols, image.rows), true);
282 if (!ok) {
283 RCLCPP_ERROR(get_logger(), "Failed to open video file: %s",
284 dump_video_path_.c_str());
285 dump_video_path_.clear(); // stop trying
286 return;
287 }
288 RCLCPP_INFO(get_logger(), "VideoWriter started: %s, size=%dx%d, fps=%.1f",
289 dump_video_path_.c_str(), image.cols, image.rows, fps);
290 }
291 video_writer_.write(image);
292 }
293
294 // Member variables
295 std::string topic_type_;
296 std::string topic_name_;
297 std::string dump_video_path_;
298 double dump_video_fps_ = 30.0;
299
300 // Subscriptions
301 rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr sub_image_;
302 rclcpp::Subscription<sensor_msgs::msg::CompressedImage>::SharedPtr
303 sub_compressed_;
304 rclcpp::Subscription<sensor_msgs::msg::CameraInfo>::SharedPtr sub_camerainfo_;
305
306 // FPS statistics
307 rclcpp::Time last_print_{0, 0, RCL_ROS_TIME};
308 std::deque<rclcpp::Time> arrivals_;
309
310 // Video writer
311 cv::VideoWriter video_writer_;
312};
313
314int main(int argc, char **argv) {
315 rclcpp::init(argc, argv);
316 auto node = std::make_shared<CameraTopicEcho>();
317 rclcpp::spin(node);
318 rclcpp::shutdown();
319 return 0;
320}
Usage Instructions:
Subscribe to RGB image data:
ros2 run examples echo_camera_rgbd --ros-args -p topic_type:=rgb_image
Subscribe to camera intrinsic parameters:
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
Record RGB video:
# The value of dump_video_path can be changed to another path; make sure the directory exists beforehand ros2 run examples echo_camera_rgbd --ros-args -p topic_type:=rgb_image -p dump_video_path:=$PWD/output.mp4
Stereo Camera Data Subscription
This example uses echo_camera_stereo, subscribing to the /aima/hal/sensor/stereo_head_front_*/ topics to receive stereo camera data, supporting RGB images, compressed images, and camera intrinsic parameters from both the left and right cameras.
Features:
Supports independent subscription for left and right cameras
Real-time FPS statistics and data display
Supports RGB video recording
Configurable camera selection (left/right)
Supported Data Types:
left_rgb_image: Left camera RGB image (sensor_msgs/Image)left_rgb_image_compressed: Left camera compressed RGB image (sensor_msgs/CompressedImage)left_camera_info: Left camera intrinsic parameters (sensor_msgs/CameraInfo)right_rgb_image: Right camera RGB image (sensor_msgs/Image)right_rgb_image_compressed: Right camera compressed RGB image (sensor_msgs/CompressedImage)right_camera_info: Right camera intrinsic parameters (sensor_msgs/CameraInfo)
1#include <algorithm>
2#include <deque>
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/core.hpp>
16#include <opencv2/imgproc.hpp>
17#include <opencv2/videoio.hpp>
18
19/**
20 * @brief Example of subscribing to multiple topics for the stereo head camera
21 *
22 * You can select which topic type to subscribe to via the startup argument
23 * --ros-args -p topic_type:=<type>:
24 * - left_rgb_image: left camera RGB image (sensor_msgs/Image)
25 * - left_rgb_image_compressed: left camera RGB compressed image
26 * (sensor_msgs/CompressedImage)
27 * - left_camera_info: left camera intrinsic parameters
28 * (sensor_msgs/CameraInfo)
29 * - right_rgb_image: right camera RGB image (sensor_msgs/Image)
30 * - right_rgb_image_compressed: right camera RGB compressed image
31 * (sensor_msgs/CompressedImage)
32 * - right_camera_info: right camera intrinsic parameters
33 * (sensor_msgs/CameraInfo)
34 *
35 * Examples:
36 * ros2 run examples echo_camera_stereo --ros-args -p
37 * topic_type:=left_rgb_image ros2 run examples echo_camera_stereo --ros-args -p
38 * topic_type:=right_rgb_image ros2 run examples echo_camera_stereo --ros-args
39 * -p topic_type:=left_camera_info
40 *
41 * topic_type defaults to "left_rgb_image"
42 *
43 * See individual callbacks for more detailed comments
44 */
45class StereoCameraTopicEcho : public rclcpp::Node {
46public:
47 StereoCameraTopicEcho() : Node("stereo_camera_topic_echo") {
48 // Select which topic type to subscribe to
49 topic_type_ =
50 declare_parameter<std::string>("topic_type", "left_rgb_image");
51 dump_video_path_ = declare_parameter<std::string>("dump_video_path", "");
52 dump_video_fps_ = declare_parameter<double>("dump_video_fps", 10.0);
53
54 // Subscribed topics and their message layouts
55 // 1. /aima/hal/sensor/stereo_head_front_left/rgb_image
56 // - topic_type: left_rgb_image
57 // - message type: sensor_msgs::msg::Image
58 // - frame_id: stereo_head_front
59 // - child_frame_id: /
60 // - contents: left camera raw image
61 // 2. /aima/hal/sensor/stereo_head_front_left/rgb_image/compressed
62 // - topic_type: left_rgb_image_compressed
63 // - message type: sensor_msgs::msg::CompressedImage
64 // - frame_id: stereo_head_front
65 // - contents: left camera compressed image
66 // 3. /aima/hal/sensor/stereo_head_front_left/camera_info
67 // - topic_type: left_camera_info
68 // - message type: sensor_msgs::msg::CameraInfo
69 // - frame_id: stereo_head_front
70 // - contents: left camera intrinsic parameters
71 // 4. /aima/hal/sensor/stereo_head_front_right/rgb_image
72 // - topic_type: right_rgb_image
73 // - message type: sensor_msgs::msg::Image
74 // - frame_id: stereo_head_front_right
75 // - child_frame_id: /
76 // - contents: right camera raw image
77 // 5. /aima/hal/sensor/stereo_head_front_right/rgb_image/compressed
78 // - topic_type: right_rgb_image_compressed
79 // - message type: sensor_msgs::msg::CompressedImage
80 // - frame_id: stereo_head_front_right
81 // - contents: right camera compressed image
82 // 6. /aima/hal/sensor/stereo_head_front_right/camera_info
83 // - topic_type: right_camera_info
84 // - message type: sensor_msgs::msg::CameraInfo
85 // - frame_id: stereo_head_front_right
86 // - contents: right camera intrinsic parameters
87
88 // Set QoS parameters - use SensorData QoS
89 auto qos = rclcpp::SensorDataQoS();
90
91 // Enable left camera RGB image subscription
92 if (topic_type_ == "left_rgb_image") {
93 topic_name_ = "/aima/hal/sensor/stereo_head_front_left/rgb_image";
94 sub_image_ = create_subscription<sensor_msgs::msg::Image>(
95 topic_name_, qos,
96 std::bind(&StereoCameraTopicEcho::cb_image, this,
97 std::placeholders::_1));
98 RCLCPP_INFO(get_logger(), "✅ Subscribing Left RGB Image: %s",
99 topic_name_.c_str());
100 if (!dump_video_path_.empty()) {
101 RCLCPP_INFO(get_logger(), "📝 Will dump received images to video: %s",
102 dump_video_path_.c_str());
103 }
104
105 // Enable left camera RGB compressed image subscription
106 } else if (topic_type_ == "left_rgb_image_compressed") {
107 topic_name_ =
108 "/aima/hal/sensor/stereo_head_front_left/rgb_image/compressed";
109 sub_compressed_ = create_subscription<sensor_msgs::msg::CompressedImage>(
110 topic_name_, qos,
111 std::bind(&StereoCameraTopicEcho::cb_compressed, this,
112 std::placeholders::_1));
113 RCLCPP_INFO(get_logger(), "✅ Subscribing Left CompressedImage: %s",
114 topic_name_.c_str());
115
116 // Enable left camera info subscription
117 } else if (topic_type_ == "left_camera_info") {
118
119 topic_name_ = "/aima/hal/sensor/stereo_head_front_left/camera_info";
120 // CameraInfo subscriptions must use reliable + transient_local
121 // QoS in order to receive latched/history messages (even if only one
122 // message was published). Here we use keep_last(1) + reliable
123 // + transient_local.
124 sub_camerainfo_ = create_subscription<sensor_msgs::msg::CameraInfo>(
125 topic_name_,
126 rclcpp::QoS(rclcpp::KeepLast(1)).reliable().transient_local(),
127 std::bind(&StereoCameraTopicEcho::cb_camerainfo, this,
128 std::placeholders::_1));
129 RCLCPP_INFO(get_logger(),
130 "✅ Subscribing Left CameraInfo (with transient_local): %s",
131 topic_name_.c_str());
132
133 // Enable right camera RGB image subscription
134 } else if (topic_type_ == "right_rgb_image") {
135 topic_name_ = "/aima/hal/sensor/stereo_head_front_right/rgb_image";
136 sub_image_ = create_subscription<sensor_msgs::msg::Image>(
137 topic_name_, qos,
138 std::bind(&StereoCameraTopicEcho::cb_image, this,
139 std::placeholders::_1));
140 RCLCPP_INFO(get_logger(), "✅ Subscribing Right RGB Image: %s",
141 topic_name_.c_str());
142 if (!dump_video_path_.empty()) {
143 RCLCPP_INFO(get_logger(), "📝 Will dump received images to video: %s",
144 dump_video_path_.c_str());
145 }
146
147 // Enable right camera RGB compressed image subscription
148 } else if (topic_type_ == "right_rgb_image_compressed") {
149 topic_name_ =
150 "/aima/hal/sensor/stereo_head_front_right/rgb_image/compressed";
151 sub_compressed_ = create_subscription<sensor_msgs::msg::CompressedImage>(
152 topic_name_, qos,
153 std::bind(&StereoCameraTopicEcho::cb_compressed, this,
154 std::placeholders::_1));
155 RCLCPP_INFO(get_logger(), "✅ Subscribing Right CompressedImage: %s",
156 topic_name_.c_str());
157
158 // Enable right camera info subscription
159 } else if (topic_type_ == "right_camera_info") {
160 topic_name_ = "/aima/hal/sensor/stereo_head_front_right/camera_info";
161 // CameraInfo subscriptions must use reliable + transient_local
162 // QoS in order to receive latched/history messages (even if only one
163 // message was published). Here we use keep_last(1) + reliable
164 // + transient_local.
165 sub_camerainfo_ = create_subscription<sensor_msgs::msg::CameraInfo>(
166 topic_name_,
167 rclcpp::QoS(rclcpp::KeepLast(1)).reliable().transient_local(),
168 std::bind(&StereoCameraTopicEcho::cb_camerainfo, this,
169 std::placeholders::_1));
170 RCLCPP_INFO(get_logger(),
171 "✅ Subscribing Right CameraInfo (with transient_local): %s",
172 topic_name_.c_str());
173
174 // Unknown topic_type error
175 } else {
176 RCLCPP_ERROR(get_logger(), "Unknown topic_type: %s", topic_type_.c_str());
177 throw std::runtime_error("Unknown topic_type");
178 }
179 }
180
181 ~StereoCameraTopicEcho() override {
182 if (video_writer_.isOpened()) {
183 video_writer_.release();
184 RCLCPP_INFO(get_logger(), "Video file closed.");
185 }
186 }
187
188private:
189 // Image callback (left/right RGB image)
190 void cb_image(const sensor_msgs::msg::Image::SharedPtr msg) {
191 update_arrivals();
192
193 if (should_print()) {
194 RCLCPP_INFO(get_logger(),
195 "📸 %s received\n"
196 " • frame_id: %s\n"
197 " • stamp (sec): %.6f\n"
198 " • encoding: %s\n"
199 " • size (WxH): %u x %u\n"
200 " • step (bytes/row):%u\n"
201 " • is_bigendian: %u\n"
202 " • recv FPS (1s): %.1f",
203 topic_type_.c_str(), msg->header.frame_id.c_str(),
204 rclcpp::Time(msg->header.stamp).seconds(),
205 msg->encoding.c_str(), msg->width, msg->height, msg->step,
206 msg->is_bigendian, get_fps());
207 }
208
209 // Video dump is supported only for RGB images
210 if ((topic_type_ == "left_rgb_image" || topic_type_ == "right_rgb_image") &&
211 !dump_video_path_.empty()) {
212 dump_image_to_video(msg);
213 }
214 }
215
216 // CompressedImage callback (left/right RGB compressed image)
217 void cb_compressed(const sensor_msgs::msg::CompressedImage::SharedPtr msg) {
218 update_arrivals();
219
220 if (should_print()) {
221 RCLCPP_INFO(get_logger(),
222 "🗜️ %s received\n"
223 " • frame_id: %s\n"
224 " • stamp (sec): %.6f\n"
225 " • format: %s\n"
226 " • data size: %zu\n"
227 " • recv FPS (1s): %.1f",
228 topic_type_.c_str(), msg->header.frame_id.c_str(),
229 rclcpp::Time(msg->header.stamp).seconds(),
230 msg->format.c_str(), msg->data.size(), get_fps());
231 }
232 }
233
234 // CameraInfo callback (left/right camera intrinsic parameters)
235 void cb_camerainfo(const sensor_msgs::msg::CameraInfo::SharedPtr msg) {
236 // CameraInfo is typically published once; print it once
237 std::ostringstream oss;
238 oss << "📷 " << topic_type_ << " received\n"
239 << " • frame_id: " << msg->header.frame_id << "\n"
240 << " • stamp (sec): " << rclcpp::Time(msg->header.stamp).seconds()
241 << "\n"
242 << " • width x height: " << msg->width << " x " << msg->height << "\n"
243 << " • distortion_model:" << msg->distortion_model << "\n"
244 << " • D: [";
245 for (size_t i = 0; i < msg->d.size(); ++i) {
246 oss << msg->d[i];
247 if (i + 1 < msg->d.size())
248 oss << ", ";
249 }
250 oss << "]\n • K: [";
251 for (int i = 0; i < 9; ++i) {
252 oss << msg->k[i];
253 if (i + 1 < 9)
254 oss << ", ";
255 }
256 oss << "]\n • R: [";
257 for (int i = 0; i < 9; ++i) {
258 oss << msg->r[i];
259 if (i + 1 < 9)
260 oss << ", ";
261 }
262 oss << "]\n • P: [";
263 for (int i = 0; i < 12; ++i) {
264 oss << msg->p[i];
265 if (i + 1 < 12)
266 oss << ", ";
267 }
268 oss << "]\n"
269 << " • binning_x: " << msg->binning_x << "\n"
270 << " • binning_y: " << msg->binning_y << "\n"
271 << " • roi: { x_offset: " << msg->roi.x_offset
272 << ", y_offset: " << msg->roi.y_offset
273 << ", height: " << msg->roi.height << ", width: " << msg->roi.width
274 << ", do_rectify: " << (msg->roi.do_rectify ? "true" : "false") << " }";
275 RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
276 }
277
278 // Track arrival timestamps to compute FPS
279 void update_arrivals() {
280 const rclcpp::Time now = this->get_clock()->now();
281 arrivals_.push_back(now);
282 while (!arrivals_.empty() && (now - arrivals_.front()).seconds() > 1.0) {
283 arrivals_.pop_front();
284 }
285 }
286 double get_fps() const { return static_cast<double>(arrivals_.size()); }
287
288 // Control printing frequency
289 bool should_print() {
290 const rclcpp::Time now = this->get_clock()->now();
291 if ((now - last_print_).seconds() >= 1.0) {
292 last_print_ = now;
293 return true;
294 }
295 return false;
296 }
297
298 // Dump received images to a video file (RGB images only)
299 void dump_image_to_video(const sensor_msgs::msg::Image::SharedPtr &msg) {
300 cv::Mat image;
301 try {
302 // Obtain the Mat without copying by not specifying encoding
303 cv_bridge::CvImageConstPtr cvp = cv_bridge::toCvShare(msg);
304 image = cvp->image;
305 // Convert to BGR for uniform saving
306 if (msg->encoding == "rgb8") {
307 cv::cvtColor(image, image, cv::COLOR_RGB2BGR);
308 } else {
309 RCLCPP_WARN(get_logger(), "image encoding not expected: %s",
310 msg->encoding.c_str());
311 return;
312 }
313 } catch (const std::exception &e) {
314 RCLCPP_WARN(get_logger(), "cv_bridge exception: %s", e.what());
315 return;
316 }
317
318 // Initialize VideoWriter
319 if (!video_writer_.isOpened()) {
320 int fourcc = cv::VideoWriter::fourcc('m', 'p', '4', 'v');
321 double fps = dump_video_fps_;
322 bool ok = video_writer_.open(dump_video_path_, fourcc, fps,
323 cv::Size(image.cols, image.rows), true);
324 if (!ok) {
325 RCLCPP_ERROR(get_logger(), "Failed to open video file: %s",
326 dump_video_path_.c_str());
327 dump_video_path_.clear(); // stop trying
328 return;
329 }
330 RCLCPP_INFO(get_logger(), "VideoWriter started: %s, size=%dx%d, fps=%.1f",
331 dump_video_path_.c_str(), image.cols, image.rows, fps);
332 }
333 video_writer_.write(image);
334 }
335
336 // Member variables
337 std::string topic_type_;
338 std::string topic_name_;
339 std::string dump_video_path_;
340 double dump_video_fps_ = 10.0;
341
342 // Subscriptions
343 rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr sub_image_;
344 rclcpp::Subscription<sensor_msgs::msg::CompressedImage>::SharedPtr
345 sub_compressed_;
346 rclcpp::Subscription<sensor_msgs::msg::CameraInfo>::SharedPtr sub_camerainfo_;
347
348 // FPS statistics
349 rclcpp::Time last_print_{0, 0, RCL_ROS_TIME};
350 std::deque<rclcpp::Time> arrivals_;
351
352 // Video writer
353 cv::VideoWriter video_writer_;
354};
355
356int main(int argc, char **argv) {
357 rclcpp::init(argc, argv);
358 auto node = std::make_shared<StereoCameraTopicEcho>();
359 rclcpp::spin(node);
360 rclcpp::shutdown();
361 return 0;
362}
Usage Instructions:
Subscribe to left camera RGB image:
ros2 run examples echo_camera_stereo --ros-args -p topic_type:=left_rgb_image
Subscribe to right camera RGB image:
ros2 run examples echo_camera_stereo --ros-args -p topic_type:=right_rgb_image
Subscribe to left camera intrinsic parameters:
ros2 run examples echo_camera_stereo --ros-args -p topic_type:=left_camera_info
Record left camera video:
# The value of dump_video_path can be changed to another path; make sure the directory exists beforehand ros2 run examples echo_camera_stereo --ros-args -p topic_type:=left_rgb_image -p dump_video_path:=$PWD/left_camera.mp4
Rear Head Monocular Camera Data Subscription
This example uses echo_camera_head_rear, subscribing to the /aima/hal/sensor/rgb_head_rear/ topic to receive data from the robot’s rear head monocular camera, supporting RGB images (with/without mask), compressed images, and camera intrinsic parameters.
Features:
Supports rear head camera data subscription
Real-time FPS statistics and data display
Supports RGB video recording with/without obstructed area masked
Configurable topic type selection
Detects installation orientation and applies rotation compensation
Supported Data Types:
rgb_image: RGB image (sensor_msgs/Image)rgb_image_compressed: Compressed RGB image (sensor_msgs/CompressedImage)camera_info: Camera intrinsic parameters (sensor_msgs/CameraInfo)
1#include <algorithm>
2#include <deque>
3#include <filesystem>
4#include <fstream>
5#include <memory>
6#include <rclcpp/rclcpp.hpp>
7#include <regex>
8#include <sensor_msgs/msg/camera_info.hpp>
9#include <sensor_msgs/msg/compressed_image.hpp>
10#include <sensor_msgs/msg/image.hpp>
11#include <sstream>
12#include <string>
13#include <vector>
14
15#include <cv_bridge/cv_bridge.h>
16#include <opencv2/core.hpp>
17#include <opencv2/imgcodecs.hpp>
18#include <opencv2/imgproc.hpp>
19#include <opencv2/videoio.hpp>
20
21/**
22 * @brief Example of subscribing to multiple topics for the rear head monocular
23 * camera
24 *
25 * You can select which topic type to subscribe to via the startup argument
26 * --ros-args -p topic_type:=<type>:
27 * - rgb_image: RGB image (sensor_msgs/Image)
28 * - rgb_image_compressed: RGB compressed image (sensor_msgs/CompressedImage)
29 * - camera_info: Camera intrinsic parameters (sensor_msgs/CameraInfo)
30 *
31 * Examples:
32 * ros2 run examples echo_camera_head_rear --ros-args -p topic_type:=rgb_image
33 * ros2 run examples echo_camera_head_rear --ros-args -p
34 * topic_type:=rgb_image_compressed ros2 run examples echo_camera_head_rear
35 * --ros-args -p topic_type:=camera_info
36 *
37 * topic_type defaults to "rgb_image"
38 *
39 * Note: Some rear cameras are rotated by 90 degrees. This example
40 * automatically detects them and applies rotation compensation
41 * when needed.
42 */
43class HeadRearCameraTopicEcho : public rclcpp::Node {
44public:
45 HeadRearCameraTopicEcho() : Node("head_rear_camera_topic_echo") {
46 topic_type_ = declare_parameter<std::string>("topic_type", "rgb_image");
47 dump_video_path_ = declare_parameter<std::string>("dump_video_path", "");
48 dump_video_fps_ = declare_parameter<double>("dump_video_fps", 10.0);
49 with_mask_ = declare_parameter<bool>("with_mask", false);
50 auto_rotate_ = declare_parameter<bool>("auto_rotate", true);
51
52 auto qos = rclcpp::SensorDataQoS();
53
54 if (auto_rotate_) {
55 detect_rotate_from_file();
56 }
57
58 if (with_mask_ && !dump_video_path_.empty()) {
59 std::string mask_file = need_rotation_ ? "rgb_head_rear_mask_t35.png"
60 : "rgb_head_rear_mask.png";
61 auto mask_path =
62 std::filesystem::read_symlink("/proc/self/exe").parent_path() /
63 "data" / mask_file;
64 mask_image_ = cv::imread(mask_path, cv::IMREAD_GRAYSCALE);
65 if (mask_image_.empty()) {
66 RCLCPP_ERROR(get_logger(), "Failed to load mask file from %s",
67 mask_path.c_str());
68 throw std::runtime_error("Failed to load mask file");
69 }
70 RCLCPP_INFO(get_logger(), "🎭 Loaded mask: %s", mask_file.c_str());
71 }
72
73 if (topic_type_ == "rgb_image") {
74 topic_name_ = "/aima/hal/sensor/rgb_head_rear/rgb_image";
75 sub_image_ = create_subscription<sensor_msgs::msg::Image>(
76 topic_name_, qos,
77 std::bind(&HeadRearCameraTopicEcho::cb_image, this,
78 std::placeholders::_1));
79 RCLCPP_INFO(get_logger(), "✅ Subscribing RGB Image: %s",
80 topic_name_.c_str());
81 if (!dump_video_path_.empty()) {
82 RCLCPP_INFO(get_logger(),
83 "📝 Will dump received images %s mask %s to video: %s",
84 (with_mask_ ? "with" : "without"),
85 (need_rotation_ ? "with rotation compensation"
86 : "without rotation"),
87 dump_video_path_.c_str());
88 }
89 }
90
91 else if (topic_type_ == "rgb_image_compressed") {
92 topic_name_ = "/aima/hal/sensor/rgb_head_rear/rgb_image/compressed";
93 sub_compressed_ = create_subscription<sensor_msgs::msg::CompressedImage>(
94 topic_name_, qos,
95 std::bind(&HeadRearCameraTopicEcho::cb_compressed, this,
96 std::placeholders::_1));
97 RCLCPP_INFO(get_logger(), "✅ Subscribing CompressedImage: %s",
98 topic_name_.c_str());
99
100 } else if (topic_type_ == "camera_info") {
101 topic_name_ = "/aima/hal/sensor/rgb_head_rear/camera_info";
102 sub_camerainfo_ = create_subscription<sensor_msgs::msg::CameraInfo>(
103 topic_name_,
104 rclcpp::QoS(rclcpp::KeepLast(1)).reliable().transient_local(),
105 std::bind(&HeadRearCameraTopicEcho::cb_camerainfo, this,
106 std::placeholders::_1));
107 RCLCPP_INFO(get_logger(),
108 "✅ Subscribing CameraInfo (with transient_local): %s",
109 topic_name_.c_str());
110
111 } else {
112 RCLCPP_ERROR(get_logger(), "Unknown topic_type: %s", topic_type_.c_str());
113 throw std::runtime_error("Unknown topic_type");
114 }
115 }
116
117 ~HeadRearCameraTopicEcho() override {
118 if (video_writer_.isOpened()) {
119 video_writer_.release();
120 RCLCPP_INFO(get_logger(), "Video file closed.");
121 }
122 }
123
124private:
125 void detect_rotate_from_file() {
126 std::string hw_file = "/agibot/data/info/hardware_version";
127 std::ifstream file(hw_file);
128
129 if (file.is_open()) {
130 std::getline(file, hw_version_);
131 hw_version_.erase(hw_version_.find_last_not_of(" \n\r\t") + 1);
132 RCLCPP_INFO(get_logger(), "📋 Hardware version from file: '%s'",
133 hw_version_.c_str());
134 check_rotation(hw_version_);
135 } else {
136 RCLCPP_WARN(get_logger(), "⚠️ Hardware version file not found: %s",
137 hw_file.c_str());
138 }
139 }
140
141 void check_rotation(const std::string &version) {
142 std::regex rear_rotation_pattern("t3[d.]5", std::regex::icase);
143 if (std::regex_search(version, rear_rotation_pattern)) {
144 need_rotation_ = true;
145 RCLCPP_INFO(get_logger(), "🔄 Rotation detected: Rear camera rotation "
146 "compensation enabled (90° counter-clockwise)");
147 } else {
148 RCLCPP_INFO(
149 get_logger(),
150 "✅ No rotation detected (%s): No rotation compensation needed",
151 version.c_str());
152 }
153 }
154
155 cv::Mat rotate_image_90_ccw(const cv::Mat &image) {
156 cv::Mat rotated;
157 cv::rotate(image, rotated, cv::ROTATE_90_COUNTERCLOCKWISE);
158 return rotated;
159 }
160
161 void cb_image(const sensor_msgs::msg::Image::SharedPtr msg) {
162 update_arrivals();
163
164 if (should_print()) {
165 std::string rotation_note = need_rotation_ ? " (rotated 90° CCW)" : "";
166 RCLCPP_INFO(get_logger(),
167 "📸 %s received%s\n"
168 " • frame_id: %s\n"
169 " • stamp (sec): %.6f\n"
170 " • encoding: %s\n"
171 " • size (WxH): %u x %u\n"
172 " • step (bytes/row):%u\n"
173 " • is_bigendian: %u\n"
174 " • recv FPS (1s): %.1f",
175 topic_type_.c_str(), rotation_note.c_str(),
176 msg->header.frame_id.c_str(),
177 rclcpp::Time(msg->header.stamp).seconds(),
178 msg->encoding.c_str(), msg->width, msg->height, msg->step,
179 msg->is_bigendian, get_fps());
180 }
181
182 if (topic_type_ == "rgb_image" && !dump_video_path_.empty()) {
183 dump_image_to_video(msg);
184 }
185 }
186
187 void cb_compressed(const sensor_msgs::msg::CompressedImage::SharedPtr msg) {
188 update_arrivals();
189
190 if (should_print()) {
191 RCLCPP_INFO(get_logger(),
192 "🗜️ %s received\n"
193 " • frame_id: %s\n"
194 " • stamp (sec): %.6f\n"
195 " • format: %s\n"
196 " • data size: %zu\n"
197 " • recv FPS (1s): %.1f",
198 topic_type_.c_str(), msg->header.frame_id.c_str(),
199 rclcpp::Time(msg->header.stamp).seconds(),
200 msg->format.c_str(), msg->data.size(), get_fps());
201 }
202 }
203
204 void cb_camerainfo(const sensor_msgs::msg::CameraInfo::SharedPtr msg) {
205 std::ostringstream oss;
206 oss << "📷 " << topic_type_ << " received\n"
207 << " • frame_id: " << msg->header.frame_id << "\n"
208 << " • stamp (sec): " << rclcpp::Time(msg->header.stamp).seconds()
209 << "\n"
210 << " • width x height: " << msg->width << " x " << msg->height << "\n"
211 << " • distortion_model:" << msg->distortion_model << "\n"
212 << " • D: [";
213 for (size_t i = 0; i < msg->d.size(); ++i) {
214 oss << msg->d[i];
215 if (i + 1 < msg->d.size())
216 oss << ", ";
217 }
218 oss << "]\n • K: [";
219 for (int i = 0; i < 9; ++i) {
220 oss << msg->k[i];
221 if (i + 1 < 9)
222 oss << ", ";
223 }
224 oss << "]\n • R: [";
225 for (int i = 0; i < 9; ++i) {
226 oss << msg->r[i];
227 if (i + 1 < 9)
228 oss << ", ";
229 }
230 oss << "]\n • P: [";
231 for (int i = 0; i < 12; ++i) {
232 oss << msg->p[i];
233 if (i + 1 < 12)
234 oss << ", ";
235 }
236 oss << "]\n"
237 << " • binning_x: " << msg->binning_x << "\n"
238 << " • binning_y: " << msg->binning_y << "\n"
239 << " • roi: { x_offset: " << msg->roi.x_offset
240 << ", y_offset: " << msg->roi.y_offset
241 << ", height: " << msg->roi.height << ", width: " << msg->roi.width
242 << ", do_rectify: " << (msg->roi.do_rectify ? "true" : "false") << " }";
243 RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
244 }
245
246 void update_arrivals() {
247 const rclcpp::Time now = this->get_clock()->now();
248 arrivals_.push_back(now);
249 while (!arrivals_.empty() && (now - arrivals_.front()).seconds() > 1.0) {
250 arrivals_.pop_front();
251 }
252 }
253 double get_fps() const { return static_cast<double>(arrivals_.size()); }
254
255 bool should_print() {
256 const rclcpp::Time now = this->get_clock()->now();
257 if ((now - last_print_).seconds() >= 1.0) {
258 last_print_ = now;
259 return true;
260 }
261 return false;
262 }
263
264 void dump_image_to_video(const sensor_msgs::msg::Image::SharedPtr &msg) {
265 cv::Mat image;
266 try {
267 cv_bridge::CvImageConstPtr cvp = cv_bridge::toCvShare(msg);
268 image = cvp->image;
269 if (msg->encoding == "rgb8") {
270 cv::cvtColor(image, image, cv::COLOR_RGB2BGR);
271 } else {
272 RCLCPP_WARN(get_logger(), "image encoding not expected: %s",
273 msg->encoding.c_str());
274 return;
275 }
276
277 if (need_rotation_) {
278 image = rotate_image_90_ccw(image);
279 }
280
281 if (with_mask_) {
282 image.setTo(cv::Scalar(0, 0, 0), mask_image_ == 0);
283 }
284 } catch (const std::exception &e) {
285 RCLCPP_WARN(get_logger(), "cv_bridge exception: %s", e.what());
286 return;
287 }
288
289 if (!video_writer_.isOpened()) {
290 int fourcc = cv::VideoWriter::fourcc('m', 'p', '4', 'v');
291 double fps = dump_video_fps_;
292 bool ok = video_writer_.open(dump_video_path_, fourcc, fps,
293 cv::Size(image.cols, image.rows), true);
294 if (!ok) {
295 RCLCPP_ERROR(get_logger(), "Failed to open video file: %s",
296 dump_video_path_.c_str());
297 dump_video_path_.clear();
298 return;
299 }
300 RCLCPP_INFO(get_logger(), "VideoWriter started: %s, size=%dx%d, fps=%.1f",
301 dump_video_path_.c_str(), image.cols, image.rows, fps);
302 }
303 video_writer_.write(image);
304 }
305
306 std::string topic_type_;
307 std::string topic_name_;
308 std::string dump_video_path_;
309 double dump_video_fps_ = 10.0;
310 bool with_mask_;
311 bool auto_rotate_;
312 bool need_rotation_ = false;
313 std::string hw_version_;
314 cv::Mat mask_image_;
315
316 rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr sub_image_;
317 rclcpp::Subscription<sensor_msgs::msg::CompressedImage>::SharedPtr
318 sub_compressed_;
319 rclcpp::Subscription<sensor_msgs::msg::CameraInfo>::SharedPtr sub_camerainfo_;
320
321 rclcpp::Time last_print_{0, 0, RCL_ROS_TIME};
322 std::deque<rclcpp::Time> arrivals_;
323
324 cv::VideoWriter video_writer_;
325};
326
327int main(int argc, char **argv) {
328 rclcpp::init(argc, argv);
329 auto node = std::make_shared<HeadRearCameraTopicEcho>();
330 rclcpp::spin(node);
331 rclcpp::shutdown();
332 return 0;
333}
Usage Instructions:
Subscribe to RGB image data:
ros2 run examples echo_camera_head_rear --ros-args -p topic_type:=rgb_image
Subscribe to compressed image data:
ros2 run examples echo_camera_head_rear --ros-args -p topic_type:=rgb_image_compressed
Subscribe to camera intrinsic parameters:
ros2 run examples echo_camera_head_rear --ros-args -p topic_type:=camera_info
Record video:
# The value of dump_video_path can be changed to another path; make sure the directory exists beforehand ros2 run examples echo_camera_head_rear --ros-args -p topic_type:=rgb_image -p dump_video_path:=$PWD/rear_camera.mp4
Record video and mask obstructed area:
# The value of dump_video_path can be changed to another path; make sure the directory exists beforehand 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.mp4
Disable auto rotation compensation (get original image):
ros2 run examples echo_camera_head_rear --ros-args -p topic_type:=rgb_image -p auto_rotate:=false
Application Scenarios:
Face recognition and tracking
Object detection and recognition
Visual SLAM
Image processing and computer vision algorithm development
Robot visual navigation
6.2.15 Head Touch Sensor Data Subscription
This example uses echo_head_touch_sensor, which subscribes to the /aima/hal/sensor/touch_head topic to receive the robot’s touch sensor data on the head.
Features:
The event data would change from “IDLE” to “TOUCH” when robot’s head touched
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#include <string>
8
9class TouchStateSubscriber : public rclcpp::Node {
10public:
11 TouchStateSubscriber() : Node("touch_state_subscriber") {
12 subscription_ = this->create_subscription<aimdk_msgs::msg::TouchState>(
13 "/aima/hal/sensor/touch_head", 10,
14 std::bind(&TouchStateSubscriber::touch_callback, this,
15 std::placeholders::_1));
16
17 RCLCPP_INFO(this->get_logger(), "TouchState subscriber started, listening "
18 "to /aima/hal/sensor/touch_head");
19 }
20
21private:
22 void touch_callback(const aimdk_msgs::msg::TouchState::SharedPtr msg) {
23 // print message info
24 std::string event_str = get_event_type_string(msg->event_type);
25 RCLCPP_INFO(this->get_logger(), "Timestamp: %d.%09d, Event: %s (%d)",
26 msg->header.stamp.sec, msg->header.stamp.nanosec,
27 event_str.c_str(), msg->event_type);
28 }
29
30 std::string get_event_type_string(uint8_t event_type) {
31 switch (event_type) {
32 case aimdk_msgs::msg::TouchState::UNKNOWN:
33 return "UNKNOWN";
34 case aimdk_msgs::msg::TouchState::IDLE:
35 return "IDLE";
36 case aimdk_msgs::msg::TouchState::TOUCH:
37 return "TOUCH";
38 default:
39 return "INVALID";
40 }
41 }
42 rclcpp::Subscription<aimdk_msgs::msg::TouchState>::SharedPtr subscription_;
43};
44
45int main(int argc, char *argv[]) {
46 rclcpp::init(argc, argv);
47 auto node = std::make_shared<TouchStateSubscriber>();
48 rclcpp::spin(node);
49 rclcpp::shutdown();
50 return 0;
51}
Usage Instructions:
ros2 run examples echo_head_touch_sensor
Output Example:
...
[INFO] [1779174857.614539691] [touch_state_subscriber]: Timestamp: 1779174857.613006485, Event: IDLE (1)
[INFO] [1779174857.623661452] [touch_state_subscriber]: Timestamp: 1779174857.623052293, Event: TOUCH (2)
6.2.16 LiDAR Data Subscription
This example uses echo_lidar_data, subscribing to the /aima/hal/sensor/lidar_chest_front/ topic to receive LiDAR data, supporting both point cloud data and IMU data types.
Features:
Supports LiDAR point cloud data subscription
Supports LiDAR IMU data subscription
Real-time FPS statistics and data display
Configurable topic type selection
Detailed output of data field information
Supported Data Types:
PointCloud2: LiDAR point cloud data (sensor_msgs/PointCloud2)Imu: LiDAR IMU data (sensor_msgs/Imu)
Technical Implementation:
Uses SensorDataQoS configuration (
BEST_EFFORT+VOLATILE)Supports parsing and displaying point cloud field information
Supports IMU quaternion, angular velocity, and linear acceleration data
Provides detailed debugging log output
Application Scenarios:
LiDAR data acquisition and analysis
Point cloud data processing and visualization
Robot navigation and localization
SLAM algorithm development
Environmental perception and mapping
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 // SensorDataQoS: BEST_EFFORT + VOLATILE
37 auto qos = rclcpp::SensorDataQoS();
38
39 if (topic_type_ == "pointcloud") {
40 topic_name_ = "/aima/hal/sensor/lidar_chest_front/lidar_pointcloud";
41 sub_pointcloud_ = create_subscription<sensor_msgs::msg::PointCloud2>(
42 topic_name_, qos,
43 std::bind(&LidarChestEcho::cb_pointcloud, this,
44 std::placeholders::_1));
45 RCLCPP_INFO(get_logger(), "✅ Subscribing LIDAR PointCloud2: %s",
46 topic_name_.c_str());
47 } else if (topic_type_ == "imu") {
48 topic_name_ = "/aima/hal/sensor/lidar_chest_front/imu";
49 sub_imu_ = create_subscription<sensor_msgs::msg::Imu>(
50 topic_name_, qos,
51 std::bind(&LidarChestEcho::cb_imu, this, std::placeholders::_1));
52 RCLCPP_INFO(get_logger(), "✅ Subscribing LIDAR IMU: %s",
53 topic_name_.c_str());
54 } else {
55 RCLCPP_ERROR(get_logger(), "Unknown topic_type: %s", topic_type_.c_str());
56 throw std::runtime_error("Unknown topic_type");
57 }
58 }
59
60private:
61 // PointCloud2 callback
62 void cb_pointcloud(const sensor_msgs::msg::PointCloud2::SharedPtr msg) {
63 update_arrivals();
64
65 if (should_print()) {
66 std::ostringstream oss;
67 oss << std::fixed << std::boolalpha << std::setfill('0');
68 oss << "🟢 LIDAR PointCloud2 received\n"
69 << " • frame_id: " << msg->header.frame_id << "\n"
70 << " • stamp (sec): " << msg->header.stamp.sec << "."
71 << std::setw(9) << msg->header.stamp.nanosec << "\n"
72 << " • width x height: " << msg->width << " x " << msg->height
73 << "\n"
74 << " • point_step: " << msg->point_step << "\n"
75 << " • row_step: " << msg->row_step << "\n"
76 << " • fields: ";
77 for (const auto &f : msg->fields)
78 oss << f.name << "(" << (int)f.datatype << ") ";
79 oss << "\n • is_bigendian: " << msg->is_bigendian
80 << "\n • is_dense: " << msg->is_dense
81 << "\n • data size: " << msg->data.size()
82 << std::setprecision(1) // fps precision .1f
83 << "\n • recv FPS (1s): " << get_fps();
84 RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
85 }
86 }
87
88 // IMU callback
89 void cb_imu(const sensor_msgs::msg::Imu::SharedPtr msg) {
90 update_arrivals();
91
92 if (should_print()) {
93 std::ostringstream oss;
94 oss << std::fixed << std::setfill('0');
95 oss << "🟢 LIDAR IMU received\n"
96 << " • frame_id: " << msg->header.frame_id << "\n"
97 << " • stamp (sec): " << msg->header.stamp.sec << "."
98 << std::setw(9) << msg->header.stamp.nanosec << "\n"
99 << std::setprecision(6) // data precision .6f
100 << " • orientation: [" << msg->orientation.x << ", "
101 << msg->orientation.y << ", " << msg->orientation.z << ", "
102 << msg->orientation.w << "]\n"
103 << " • angular_velocity:[" << msg->angular_velocity.x << ", "
104 << msg->angular_velocity.y << ", " << msg->angular_velocity.z << "]\n"
105 << " • linear_accel: [" << msg->linear_acceleration.x << ", "
106 << msg->linear_acceleration.y << ", " << msg->linear_acceleration.z
107 << "]\n"
108 << std::setprecision(1) // fps precision .1f
109 << " • recv FPS (1s): " << get_fps();
110 RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
111 }
112 }
113
114 // Update FPS statistics
115 void update_arrivals() {
116 const rclcpp::Time now = this->get_clock()->now();
117 arrivals_.push_back(now);
118 while (!arrivals_.empty() && (now - arrivals_.front()).seconds() > 1.0) {
119 arrivals_.pop_front();
120 }
121 }
122 double get_fps() const { return static_cast<double>(arrivals_.size()); }
123
124 // Control print frequency
125 bool should_print() {
126 const rclcpp::Time now = this->get_clock()->now();
127 if ((now - last_print_).seconds() >= 1.0) {
128 last_print_ = now;
129 return true;
130 }
131 return false;
132 }
133
134 // Member variables
135 std::string topic_type_;
136 std::string topic_name_;
137
138 rclcpp::Subscription<sensor_msgs::msg::PointCloud2>::SharedPtr
139 sub_pointcloud_;
140 rclcpp::Subscription<sensor_msgs::msg::Imu>::SharedPtr sub_imu_;
141
142 rclcpp::Time last_print_{0, 0, RCL_ROS_TIME};
143 std::deque<rclcpp::Time> arrivals_;
144};
145
146int main(int argc, char **argv) {
147 rclcpp::init(argc, argv);
148 auto node = std::make_shared<LidarChestEcho>();
149 rclcpp::spin(node);
150 rclcpp::shutdown();
151 return 0;
152}
Usage Instructions:
# Subscribe to LiDAR point cloud data
ros2 run examples echo_lidar_data --ros-args -p topic_type:=pointcloud
# Subscribe to LiDAR IMU data
ros2 run examples echo_lidar_data --ros-args -p topic_type:=imu
Output Example:
...
[INFO] [1779175640.420801950] [lidar_chest_echo]: 🟢 LIDAR PointCloud2 received
• frame_id: lidar_chest_front
• stamp (sec): 1779175640.315327000
• width x height: 24779 x 1
• point_step: 32
• row_step: 792928
• fields: x(7) y(7) z(7) intensity(7) ring(4) timestamp(8)
• is_bigendian: false
• is_dense: true
• data size: 792928
• recv FPS (1s): 11.0
6.2.17 IMU Data Subscription
This example uses echo_imu_data to subscribe to IMU topics for the robot’s orientation, angular velocity, and linear acceleration data, and prints the covariance diagonals of each field. It subscribes to the chest IMU by default and can be switched to the torso IMU, LiDAR IMU, or depth-camera IMU via a parameter.
Features:
Subscribes to
sensor_msgs/Imudata, outputting orientation quaternion, angular velocity, and linear accelerationPrints the covariance diagonals of orientation/angular velocity/acceleration (
-1means unknown)Measures the receive interval (
recv_dt) to observe the actual arrival rateSwitch between IMU sources via the
imu_topicparameter
Supported Topics:
/aima/hal/imu/chest/state: Chest IMU (default)/aima/hal/imu/torso/state: Torso IMU/aima/hal/sensor/lidar_chest_front/imu: LiDAR IMU/aima/hal/sensor/rgbd_head_front/imu: Depth-camera IMU
Technical Implementation:
Uses
SensorDataQoS(BEST_EFFORT+VOLATILE), consistent with the default QoS for sensor data
1#include <chrono>
2#include <iomanip>
3#include <rclcpp/rclcpp.hpp>
4#include <sensor_msgs/msg/imu.hpp>
5#include <string>
6
7class ImuPrinter : public rclcpp::Node {
8public:
9 ImuPrinter() : rclcpp::Node("imu_printer") {
10 // Allow overriding the topic name via a parameter
11 imu_topic_ = this->declare_parameter<std::string>(
12 "imu_topic",
13 "/aima/hal/imu/chest/state"); // Change to other IMU topics, e.g.
14 // waist IMU: /aima/hal/imu/torso/state
15 // lidar IMU:
16 // /aima/hal/sensor/lidar_chest_front/imu
17
18 auto qos = rclcpp::SensorDataQoS(); // BestEffort / Volatile
19 sub_ = this->create_subscription<sensor_msgs::msg::Imu>(
20 imu_topic_, qos,
21 std::bind(&ImuPrinter::cb, this, std::placeholders::_1));
22
23 RCLCPP_INFO(this->get_logger(), "Subscribing IMU: %s", imu_topic_.c_str());
24 }
25
26private:
27 void cb(const sensor_msgs::msg::Imu::SharedPtr msg) {
28 // Compute receive interval (based on wall time, for observation only)
29 auto now = std::chrono::steady_clock::now();
30 double dt_ms =
31 last_recv_.time_since_epoch().count() == 0
32 ? 0.0
33 : std::chrono::duration<double, std::milli>(now - last_recv_)
34 .count();
35 last_recv_ = now;
36
37 const auto &q = msg->orientation;
38 const auto &w = msg->angular_velocity;
39 const auto &a = msg->linear_acceleration;
40
41 // Print
42 std::ostringstream oss;
43 oss << std::fixed << std::setprecision(6);
44 oss << "stamp=" << rclcpp::Time(msg->header.stamp).seconds()
45 << "s frame=" << msg->header.frame_id
46 << " recv_dt=" << std::setprecision(3) << dt_ms << "ms\n"
47 << std::setprecision(6) << " orientation (x,y,z,w): [" << q.x << ", "
48 << q.y << ", " << q.z << ", " << q.w << "]\n"
49 << " angular_velocity (rad/s): [" << w.x << ", " << w.y << ", " << w.z
50 << "]\n"
51 << " linear_accel (m/s^2): [" << a.x << ", " << a.y << ", " << a.z
52 << "]";
53
54 // Optional: print diagonal covariances (-1 means unknown)
55 auto oc0 = msg->orientation_covariance[0];
56 auto oc4 = msg->orientation_covariance[4];
57 auto oc8 = msg->orientation_covariance[8];
58 auto wc0 = msg->angular_velocity_covariance[0];
59 auto wc4 = msg->angular_velocity_covariance[4];
60 auto wc8 = msg->angular_velocity_covariance[8];
61 auto ac0 = msg->linear_acceleration_covariance[0];
62 auto ac4 = msg->linear_acceleration_covariance[4];
63 auto ac8 = msg->linear_acceleration_covariance[8];
64
65 oss << "\n cov_diag(ori, ang, acc): [" << oc0 << "," << oc4 << "," << oc8
66 << " | " << wc0 << "," << wc4 << "," << wc8 << " | " << ac0 << ","
67 << ac4 << "," << ac8 << "]";
68
69 RCLCPP_INFO(this->get_logger(), "%s", oss.str().c_str());
70 }
71
72 std::string imu_topic_;
73 rclcpp::Subscription<sensor_msgs::msg::Imu>::SharedPtr sub_;
74 std::chrono::steady_clock::time_point last_recv_{};
75};
76
77int main(int argc, char **argv) {
78 rclcpp::init(argc, argv);
79 rclcpp::spin(std::make_shared<ImuPrinter>());
80 rclcpp::shutdown();
81 return 0;
82}
Usage Instructions:
# Subscribe to the chest IMU (default)
ros2 run examples echo_imu_data
# Subscribe to the torso IMU
ros2 run examples echo_imu_data --ros-args -p imu_topic:=/aima/hal/imu/torso/state
# Subscribe to the LiDAR IMU
ros2 run examples echo_imu_data --ros-args -p imu_topic:=/aima/hal/sensor/lidar_chest_front/imu
Output Example:
[INFO] [1781764206.320174965] [imu_printer]: Subscribing IMU: /aima/hal/imu/chest/state
[INFO] [1781764206.318604000] [imu_printer]: stamp=1781764206.318604s frame=base_link recv_dt=2.039ms
orientation (x,y,z,w): [0.087036, -0.264735, -0.019103, -0.960195]
angular_velocity (rad/s): [-0.006531, 0.002874, 0.002391]
linear_accel (m/s^2): [-5.013936, -1.548364, 8.280822]
cov_diag(ori, ang, acc): [0.000000,0.000000,0.000000 | 0.000000,0.000000,0.000000 | 0.000000,0.000000,0.000000]
6.2.18 Play Video
This example uses play_video. Before running the node, you must upload the video file to the robot’s Interaction Computing Unit (PC3) (you may create a directory there to store videos, e.g. /var/tmp/videos/). Then, modify the video_path in the node program to the path of the video you want to play.
Attention
⚠️ Warning! The Interaction Computing Unit (PC3) is independent from the Development Computing Unit (PC2) where secondary development programs run. Audio and video files must be stored on the Interaction Computing Unit (IP: 10.0.1.42).
Audio and video files (and all parent directories up to root) must be readable by all users (new subdirectory under /var/tmp/ is recommended)
Attention
Before using this feature, run the following command on the Motion Control Computing Unit (PC1) to stop the task_manager module and prevent it from competing with your program for display control:
aima em stop-app task_manager
Function Description: By calling the PlayVideo service, the robot can play a video file located at a specified path on its screen. Ensure the video file has been uploaded to the Interaction Computing Unit, otherwise playback will fail.
💡 Tip: How to prepare a video file
You can use the echo_camera_rgbd example to record the robot’s depth camera RGB stream as an
.mp4video file, then use it as input forplay_video:Step 1: Record a video on the Development Computing Unit (PC2)
ros2 run examples echo_camera_rgbd --ros-args -p topic_type:=rgb_image -p dump_video_path:=$PWD/output.mp4 # Press Ctrl+C to stop recording; the video is saved as output.mp4Step 2: Transfer the video to the Interaction Computing Unit (PC3, IP: 10.0.1.42)
scp output.mp4 user@10.0.1.42:/var/tmp/videos/output.mp4Step 3: Run play_video to play the video
ros2 run examples play_video # Set video_path in the code to /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 <algorithm>
5#include <cctype>
6#include <chrono>
7#include <iostream>
8#include <memory>
9#include <signal.h>
10#include <string>
11
12// Global variable used for signal handling
13std::shared_ptr<rclcpp::Node> g_node = nullptr;
14
15// Signal handler function
16void signal_handler(int signal) {
17 if (g_node) {
18 RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
19 signal);
20 g_node.reset();
21 }
22 rclcpp::shutdown();
23 exit(signal);
24}
25
26class PlayVideoClient : public rclcpp::Node {
27public:
28 PlayVideoClient() : Node("play_video_client") {
29 client_ = this->create_client<aimdk_msgs::srv::PlayVideo>(
30 "/aimdk_5Fmsgs/srv/PlayVideo");
31 RCLCPP_INFO(this->get_logger(), "✅ PlayVideo client node started.");
32
33 // Wait for the service to become available
34 while (!client_->wait_for_service(std::chrono::seconds(2))) {
35 RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
36 }
37 RCLCPP_INFO(this->get_logger(),
38 "🟢 Service available, ready to send request.");
39 }
40
41 bool send_request(const std::string &video_path, uint8_t mode,
42 int32_t priority) {
43 try {
44 auto request = std::make_shared<aimdk_msgs::srv::PlayVideo::Request>();
45
46 request->video_path = video_path;
47 request->mode = mode;
48 request->priority = priority;
49
50 RCLCPP_INFO(this->get_logger(),
51 "📨 Sending request to play video: mode=%hhu video=%s", mode,
52 video_path.c_str());
53
54 const std::chrono::milliseconds timeout(250);
55 for (int i = 0; i < 8; i++) {
56 request->header.header.stamp = this->now();
57 auto future = client_->async_send_request(request);
58 auto retcode = rclcpp::spin_until_future_complete(shared_from_this(),
59 future, timeout);
60 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
61 // retry as remote peer is NOT handled well by ROS
62 RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
63 continue;
64 }
65 // future.done
66 auto response = future.get();
67 if (response->success) {
68 RCLCPP_INFO(this->get_logger(),
69 "✅ Request to play video recorded successfully: %s",
70 response->message.c_str());
71 return true;
72 } else {
73 RCLCPP_ERROR(this->get_logger(),
74 "❌ Failed to record play-video request: %s",
75 response->message.c_str());
76 return false;
77 }
78 }
79 RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
80 return false;
81 } catch (const std::exception &e) {
82 RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
83 return false;
84 }
85 }
86
87private:
88 rclcpp::Client<aimdk_msgs::srv::PlayVideo>::SharedPtr client_;
89};
90
91int main(int argc, char **argv) {
92 try {
93 rclcpp::init(argc, argv);
94
95 // Set up signal handlers
96 signal(SIGINT, signal_handler);
97 signal(SIGTERM, signal_handler);
98
99 std::string default_video_path =
100 "/agibot/software/face_ui/bin/app/aarch64-generic/assets/mp4/"
101 "emoji_x2_sleepy.mp4";
102 std::string video_path;
103 std::cout << "Enter video path (default: " << default_video_path << "): ";
104 std::getline(std::cin, video_path);
105 // Trim leading/trailing whitespace (parity with Python's .strip())
106 auto not_space = [](int c) { return !std::isspace(c); };
107 video_path.erase(
108 video_path.begin(),
109 std::find_if(video_path.begin(), video_path.end(), not_space));
110 video_path.erase(
111 std::find_if(video_path.rbegin(), video_path.rend(), not_space).base(),
112 video_path.end());
113 if (video_path.empty()) {
114 video_path = default_video_path;
115 }
116 int32_t priority = 5;
117 int mode = 2; // Loop playback
118 std::cout << "Enter video play mode (1: once, 2: loop): ";
119 std::cin >> mode;
120 if (mode < 1 || mode > 2) {
121 RCLCPP_ERROR(rclcpp::get_logger("main"), "Invalid play mode: %d", mode);
122 rclcpp::shutdown();
123 return 1;
124 }
125
126 g_node = std::make_shared<PlayVideoClient>();
127 auto client = std::dynamic_pointer_cast<PlayVideoClient>(g_node);
128
129 if (client) {
130 client->send_request(video_path, mode, priority);
131 }
132
133 // Clean up resources
134 g_node.reset();
135 rclcpp::shutdown();
136
137 return 0;
138 } catch (const std::exception &e) {
139 RCLCPP_ERROR(rclcpp::get_logger("main"),
140 "Program exited with exception: %s", e.what());
141 return 1;
142 }
143}
6.2.19 Audio File Playback
This example uses play_audio, enabling playback of specified audio files, supporting WAV (PCM-encoded) and RAW PCM formats. Other formats such as MP3 are not supported.
Features:
Supports WAV (PCM-encoded) and RAW PCM playback; other formats such as MP3 are not supported
Supports priority control with configurable playback priority
Supports custom file paths and playback parameters
Provides comprehensive error handling and status feedback
Technical Implementation:
Uses the PlayAudioFile service for audio file playback
Supports priority settings (priority: 1–10, priority_weight: 1–100)
Provides detailed playback status feedback
Application Scenarios:
Audio file playback and media control
Voice prompts and sound effect playback
Multimedia application development
Robot interaction audio feedback
Attention
⚠️ Warning! The Interaction Computing Unit (PC3) is independent from the Development Computing Unit (PC2) where secondary development programs run. Audio and video files must be stored on the Interaction Computing Unit (IP: 10.0.1.42).
Audio and video files (and all parent directories up to root) must be readable by all users (new subdirectory under /var/tmp/ is recommended)
1#include <aimdk_msgs/msg/common_state.hpp>
2#include <aimdk_msgs/srv/play_audio_file.hpp>
3#include <algorithm>
4#include <cctype>
5#include <filesystem>
6#include <iostream>
7#include <rclcpp/rclcpp.hpp>
8#include <string>
9
10using PlayAudioFile = aimdk_msgs::srv::PlayAudioFile;
11
12int main(int argc, char **argv) {
13 rclcpp::init(argc, argv);
14 auto node = rclcpp::Node::make_shared("play_audio_client_min");
15
16 // 1) Service name
17 const std::string service_name = "/aimdk_5Fmsgs/srv/PlayAudioFile";
18 auto client = node->create_client<PlayAudioFile>(service_name);
19
20 RCLCPP_INFO(node->get_logger(), "✅ PlayAudio client node created.");
21
22 // 2) Input file path (prompt user if not provided as argument)
23 std::string default_file = "/agibot/data/var/hal_audio/file/bingo.wav";
24 std::string file_name;
25
26 if (argc > 1) {
27 file_name = argv[1];
28 } else {
29 std::cout << "Enter audio file path to play (default: " << default_file
30 << "): ";
31 if (!std::getline(std::cin, file_name) || !rclcpp::ok()) {
32 std::cout << std::endl;
33 rclcpp::shutdown();
34 return 1;
35 }
36 // Trim leading/trailing whitespace (parity with Python's .strip())
37 auto not_space = [](int c) { return !std::isspace(c); };
38 file_name.erase(
39 file_name.begin(),
40 std::find_if(file_name.begin(), file_name.end(), not_space));
41 file_name.erase(
42 std::find_if(file_name.rbegin(), file_name.rend(), not_space).base(),
43 file_name.end());
44 if (file_name.empty()) {
45 file_name = default_file;
46 }
47 }
48
49 // 3) Build the request
50 // Check if Ctrl+C was pressed during user input
51 if (!rclcpp::ok()) {
52 rclcpp::shutdown();
53 return 1;
54 }
55
56 auto req = std::make_shared<PlayAudioFile::Request>();
57 req->file.pkg_name = "demo_client";
58 req->file.file_name = std::filesystem::path(file_name).filename().string();
59 req->file.file_path =
60 std::filesystem::path(file_name).parent_path().string() + "/";
61 req->file.priority = 8;
62 req->file.priority_weight = 0;
63
64 // 4) Wait for service
65 while (!client->wait_for_service(std::chrono::seconds(2))) {
66 if (!rclcpp::ok()) {
67 RCLCPP_ERROR(node->get_logger(),
68 "Interrupted while waiting for the service. Exiting.");
69 rclcpp::shutdown();
70 return 1;
71 }
72 RCLCPP_INFO(node->get_logger(), "⏳ Service unavailable, waiting...");
73 }
74 RCLCPP_INFO(node->get_logger(),
75 "🟢 Service available, ready to send request.");
76
77 RCLCPP_INFO(node->get_logger(), "📨 Sending request to play audio: %s",
78 file_name.c_str());
79
80 rclcpp::FutureReturnCode rc = rclcpp::FutureReturnCode::TIMEOUT;
81 std::shared_future<PlayAudioFile::Response::SharedPtr> future;
82 for (int i = 0; i < 5; ++i) {
83 req->request.header.stamp = node->now();
84 future = client->async_send_request(req);
85 rc = rclcpp::spin_until_future_complete(node, future,
86 std::chrono::seconds(1));
87 if (rc == rclcpp::FutureReturnCode::SUCCESS) {
88 break;
89 }
90 if (rc == rclcpp::FutureReturnCode::INTERRUPTED) {
91 // Check if Ctrl+C was pressed during spin_until_future_complete
92 if (rclcpp::ok()) {
93 RCLCPP_WARN(node->get_logger(), "Interrupted while waiting");
94 }
95 rclcpp::shutdown();
96 return 1;
97 }
98 // retry as remote peer is NOT handled well by ROS
99 RCLCPP_INFO(node->get_logger(), "trying ... [%d]", i);
100 }
101
102 if (rc != rclcpp::FutureReturnCode::SUCCESS) {
103 // Check if Ctrl+C was pressed, avoid using node after shutdown
104 if (rclcpp::ok()) {
105 RCLCPP_ERROR(node->get_logger(),
106 "❌ Service call not completed or timed out.");
107 }
108 rclcpp::shutdown();
109 return 1;
110 }
111
112 // 5) Handle response (success is in reponse.status)
113 try {
114 const auto resp = future.get();
115 bool success = resp->reponse.status.value ==
116 aimdk_msgs::msg::CommonState::SUCCESS; // SUCCESS (1)
117
118 if (success) {
119 RCLCPP_INFO(node->get_logger(),
120 "✅ Request to play audio file recorded successfully.");
121 } else {
122 // FAILURE(2): focus-apply failed; INVALID(5): invalid params;
123 // other: see CommonState for details.
124 const char *reason = "unknown";
125 switch (resp->reponse.status.value) {
126 case aimdk_msgs::msg::CommonState::FAILURE: // FAILURE (2)
127 reason = "focus apply failed";
128 break;
129 case aimdk_msgs::msg::CommonState::INVALID: // INVALID (5)
130 reason = "invalid params";
131 break;
132 default:
133 break;
134 }
135 RCLCPP_ERROR(node->get_logger(),
136 "❌ Failed to record play-audio request (status=%d, %s): %s",
137 resp->reponse.status.value, reason,
138 resp->reponse.message.c_str());
139 }
140 } catch (const std::exception &e) {
141 RCLCPP_ERROR(node->get_logger(), "Call exception: %s", e.what());
142 }
143
144 rclcpp::shutdown();
145 return 0;
146}
Usage Instructions:
# Play default audio file
ros2 run examples play_audio
# Play a specified audio file
# Replace /path/to/your/audio_file.wav with the actual file path on the interaction board
ros2 run examples play_audio /path/to/your/audio_file.wav
# Play TTS cached audio file
ros2 run examples play_audio /agibot/data/var/hal_audio/file/bingo.wav
Output Example:
[INFO] [1779184008.888874978] [play_audio_client_min]: ✅ PlayAudio client node created.
[INFO] [1779184008.895467698] [play_audio_client_min]: 🟢 Service available, ready to send request.
[INFO] [1779184008.897450608] [play_audio_client_min]: 📨 Sending request to play audio: /agibot/data/var/hal_audio/file/bingo.wav
[INFO] [1779184008.962054291] [play_audio_client_min]: ✅ Request to play audio file recorded successfully.
Notes:
Ensure the audio file path is correct and the file exists
Supported file formats: WAV (PCM-encoded), RAW PCM. Other formats such as MP3 are not supported
Priority settings affect playback queue order
6.2.20 TTS (Text-to-Speech)
This example uses play_tts, enabling the robot to speak the provided text. Users can input any text depending on the scenario.
Features:
Supports command-line arguments and interactive input
Includes comprehensive service availability checks and error handling
Supports priority control and interruption mechanism
Provides detailed playback status feedback
Core Code
1#include <aimdk_msgs/msg/tts_priority_level.hpp>
2#include <aimdk_msgs/srv/play_tts.hpp>
3#include <chrono>
4#include <iostream>
5#include <rclcpp/rclcpp.hpp>
6#include <string>
7
8using PlayTTS = aimdk_msgs::srv::PlayTts;
9
10int main(int argc, char **argv) {
11 rclcpp::init(argc, argv);
12 auto node = rclcpp::Node::make_shared("play_tts_client_min");
13
14 const std::string service_name = "/aimdk_5Fmsgs/srv/PlayTts";
15 auto client = node->create_client<PlayTTS>(service_name);
16
17 RCLCPP_INFO(node->get_logger(), "✅ PlayTts client node created.");
18
19 // Wait for the service to become available
20 while (!client->wait_for_service(std::chrono::seconds(2))) {
21 if (!rclcpp::ok()) {
22 rclcpp::shutdown();
23 return 1;
24 }
25 RCLCPP_INFO(node->get_logger(), "⏳ Service unavailable, waiting...");
26 }
27 RCLCPP_INFO(node->get_logger(),
28 "🟢 Service available, ready to send request.");
29
30 // Get text to speak
31 std::string tts_text;
32 if (argc > 1) {
33 tts_text = argv[1];
34 } else {
35 std::cout << "Enter text to speak: ";
36 if (!std::getline(std::cin, tts_text) || !rclcpp::ok()) {
37 std::cout << std::endl;
38 rclcpp::shutdown();
39 return 1;
40 }
41 if (tts_text.empty()) {
42 tts_text = "Hello, I am AgiBot X2.";
43 }
44 }
45
46 if (!rclcpp::ok()) {
47 rclcpp::shutdown();
48 return 1;
49 }
50
51 auto req = std::make_shared<PlayTTS::Request>();
52 req->tts_req.text = tts_text;
53 req->tts_req.domain = "demo_client"; // Required: identifies the caller
54 req->tts_req.trace_id =
55 "demo"; // Optional: request identifier for the TTS request
56 req->tts_req.is_interrupted =
57 true; // Required: whether to interrupt same-priority playback
58 req->tts_req.priority_weight = 0;
59 req->tts_req.priority_level.value =
60 aimdk_msgs::msg::TtsPriorityLevel::INTERACTION_L6; // L6 (6)
61
62 RCLCPP_INFO(node->get_logger(), "📨 Sending request to play tts: text=%s",
63 tts_text.c_str());
64
65 std::shared_future<PlayTTS::Response::SharedPtr> future;
66 rclcpp::FutureReturnCode rc = rclcpp::FutureReturnCode::TIMEOUT;
67 for (int i = 0; i < 8; ++i) {
68 req->header.header.stamp = node->now();
69 future = client->async_send_request(req);
70 rc = rclcpp::spin_until_future_complete(node, future,
71 std::chrono::milliseconds(250));
72 if (rc == rclcpp::FutureReturnCode::SUCCESS) {
73 break;
74 }
75 if (!rclcpp::ok()) {
76 rclcpp::shutdown();
77 return 1;
78 }
79 // retry as remote peer is NOT handled well by ROS
80 RCLCPP_INFO(node->get_logger(), "trying ... [%d]", i);
81 }
82
83 if (rc != rclcpp::FutureReturnCode::SUCCESS) {
84 if (rclcpp::ok()) {
85 RCLCPP_ERROR(node->get_logger(),
86 "❌ Service call not completed or timed out.");
87 }
88 rclcpp::shutdown();
89 return 1;
90 }
91
92 const auto resp = future.get();
93 if (resp->tts_resp.is_success) {
94 RCLCPP_INFO(node->get_logger(), "✅ TTS sent successfully.");
95 } else {
96 RCLCPP_ERROR(node->get_logger(),
97 "❌ Failed to send TTS: error_code=%u, error_message=%s",
98 resp->tts_resp.error_code,
99 resp->tts_resp.error_message.c_str());
100 }
101
102 rclcpp::shutdown();
103 return 0;
104}
Usage Instructions
# Use command-line arguments to speak text (recommended)
ros2 run examples play_tts "Hello, I am the AgiBot X2 robot"
# Or run without arguments; the program will prompt for input
ros2 run examples play_tts
Output Example
[INFO] [1779181543.136645281] [play_tts_client_min]: ✅ PlayTts client node created.
[INFO] [1779181543.137467698] [play_tts_client_min]: 🟢 Service available, ready to send request.
[INFO] [1779181543.138450608] [play_tts_client_min]: 📨 Sending request to play tts: text=Hello
[INFO] [1779181543.146077262] [play_tts_client_min]: ✅ TTS sent successfully.
Notes
Ensure the TTS service is running properly
Supports both Chinese and English text-to-speech
Priority settings affect playback queue order
Interruption feature can stop the currently playing speech
Interface Reference
Service:
/aimdk_5Fmsgs/srv/PlayTtsMessage:
aimdk_msgs/srv/PlayTts
6.2.21 Microphone Audio Reception
This example uses mic_receiver, subscribing to the /agent/process_audio_output topic to receive the robot’s noise-reduced audio stream. It supports both internal and external microphone audio streams, and automatically saves complete speech segments as PCM files based on VAD (Voice Activity Detection) states.
Features:
Automatically detects speech start, processing, and end based on VAD state
Automatically saves complete speech segments as PCM files (mono, 16 kHz sample rate, S16LE format)
Stores recordings categorized by timestamp and audio stream
Supports audio duration calculation and statistical output
Application Scenarios:
Speech recognition and speech processing
Audio data acquisition and analysis
Real-time speech monitoring
Audio quality evaluation
Multi-microphone array data processing
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 "Start subscribing to noise-suppressed 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 using V = aimdk_msgs::msg::AudioVadStateType;
51 static const std::unordered_map<uint8_t, std::string> vad_state_names = {
52 {V::AUDIO_VAD_STATE_BEGIN, "Speech start"},
53 {V::AUDIO_VAD_STATE_PROCESSING, "Speech in progress"},
54 {V::AUDIO_VAD_STATE_END, "Speech end"}};
55 static const std::unordered_map<uint32_t, std::string> stream_names = {
56 {1, "Built-in microphone"}, {2, "External microphone"}};
57
58 RCLCPP_INFO(this->get_logger(),
59 "Received audio data: 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 using V = aimdk_msgs::msg::AudioVadStateType;
83 static const std::unordered_map<uint8_t, std::string> vad_state_names = {
84 {V::AUDIO_VAD_STATE_BEGIN, "Speech start"},
85 {V::AUDIO_VAD_STATE_PROCESSING, "Speech in progress"},
86 {V::AUDIO_VAD_STATE_END, "Speech end"}};
87 static const std::unordered_map<uint32_t, std::string> stream_names = {
88 {1, "Built-in microphone"}, {2, "External microphone"}};
89
90 RCLCPP_INFO(this->get_logger(), "[%s] VAD state: %s audio: %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 if (vad_state == V::AUDIO_VAD_STATE_BEGIN) { // BEGIN (1)
100 RCLCPP_INFO(this->get_logger(), "🎤 Speech start detected");
101 if (recording_state_[stream_id] == false) {
102 audio_buffers_[stream_id].clear();
103 recording_state_[stream_id] = true;
104 }
105 if (!audio_data.empty()) {
106 audio_buffers_[stream_id].insert(audio_buffers_[stream_id].end(),
107 audio_data.begin(), audio_data.end());
108 }
109
110 } else if (vad_state == V::AUDIO_VAD_STATE_PROCESSING) { // PROCESSING (2)
111 RCLCPP_INFO(this->get_logger(), "🔄 Speech in progress...");
112 if (recording_state_[stream_id] && !audio_data.empty()) {
113 audio_buffers_[stream_id].insert(audio_buffers_[stream_id].end(),
114 audio_data.begin(), audio_data.end());
115 }
116
117 } else if (vad_state == V::AUDIO_VAD_STATE_END) { // END (3)
118 RCLCPP_INFO(this->get_logger(), "✅ Speech end");
119 if (recording_state_[stream_id] && !audio_data.empty()) {
120 audio_buffers_[stream_id].insert(audio_buffers_[stream_id].end(),
121 audio_data.begin(), audio_data.end());
122 }
123 if (recording_state_[stream_id] && !audio_buffers_[stream_id].empty()) {
124 save_audio_segment(audio_buffers_[stream_id], stream_id);
125 }
126 recording_state_[stream_id] = false;
127
128 // AUDIO_VAD_STATE_UNKNOWN
129 } else {
130 if (recording_state_[stream_id]) {
131 RCLCPP_INFO(this->get_logger(), "⏹️ Reset recording state");
132 recording_state_[stream_id] = false;
133 }
134 }
135
136 // Output the current buffer status.
137 size_t buffer_size = audio_buffers_[stream_id].size();
138 bool recording = recording_state_[stream_id];
139 RCLCPP_DEBUG(this->get_logger(),
140 "[Stream %u] Buffer size: %zu bytes, Recording state: %s",
141 stream_id, buffer_size, recording ? "true" : "false");
142 }
143
144 void save_audio_segment(const std::vector<uint8_t> &audio_data,
145 uint32_t stream_id) {
146 if (audio_data.empty())
147 return;
148
149 // Get the current timestamp.
150 auto now = std::chrono::system_clock::now();
151 std::time_t t = std::chrono::system_clock::to_time_t(now);
152 auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
153 now.time_since_epoch()) %
154 1000;
155
156 std::ostringstream oss;
157 oss << std::put_time(std::localtime(&t), "%Y%m%d_%H%M%S") << "_"
158 << std::setw(3) << std::setfill('0') << ms.count();
159
160 // Create a subdirectory by stream_id.
161 fs::path stream_dir =
162 fs::path(audio_output_dir_) / ("stream_" + std::to_string(stream_id));
163 fs::create_directories(stream_dir);
164
165 static const std::unordered_map<uint32_t, std::string> stream_names = {
166 {1, "internal_mic"}, {2, "external_mic"}};
167 std::string stream_name = stream_names.count(stream_id)
168 ? stream_names.at(stream_id)
169 : ("stream_" + std::to_string(stream_id));
170 std::string filename = stream_name + "_" + oss.str() + ".pcm";
171 fs::path filepath = stream_dir / filename;
172
173 try {
174 std::ofstream ofs(filepath, std::ios::binary);
175 ofs.write(reinterpret_cast<const char *>(audio_data.data()),
176 audio_data.size());
177 ofs.close();
178 RCLCPP_INFO(this->get_logger(),
179 "Audio segment saved: %s (size: %zu bytes)", filepath.c_str(),
180 audio_data.size());
181
182 // Record audio file duration (assuming 16kHz, 16-bit, mono)
183 int sample_rate = 16000;
184 int bits_per_sample = 16;
185 int channels = 1;
186 int bytes_per_sample = bits_per_sample / 8;
187 size_t total_samples = audio_data.size() / (bytes_per_sample * channels);
188 double duration_seconds =
189 static_cast<double>(total_samples) / sample_rate;
190
191 RCLCPP_INFO(this->get_logger(), "Audio duration: %.2f s (%zu samples)",
192 duration_seconds, total_samples);
193 } catch (const std::exception &e) {
194 RCLCPP_ERROR(this->get_logger(), "Failed to save audio file: %s",
195 e.what());
196 }
197 }
198
199 // Member variables
200 std::unordered_map<uint32_t, std::vector<uint8_t>> audio_buffers_;
201 std::unordered_map<uint32_t, bool> recording_state_;
202 std::string audio_output_dir_;
203 rclcpp::Subscription<aimdk_msgs::msg::ProcessedAudioOutput>::SharedPtr
204 subscription_;
205};
206
207int main(int argc, char **argv) {
208 rclcpp::init(argc, argv);
209 auto node = std::make_shared<AudioSubscriber>();
210 RCLCPP_INFO(
211 node->get_logger(),
212 "Listening for noise-suppressed audio data, press Ctrl+C to exit...");
213 rclcpp::spin(node);
214 rclcpp::shutdown();
215 return 0;
216}
Usage Instructions:
Run the program:
# Build colcon build --packages-select examples # Subscribe to GNSS data ros2 run examples echo_gnss_data
Press Ctrl+C to safely exit; the program automatically handles the start and end of audio streams.
Play saved PCM file:
After running the node, an
audio_recordingsdirectory will be created automaticallyAudio files are stored by stream_id (currently not distinguished; stream_1 is always used):
stream_1/: Built-in microphone audiostream_2/: External microphone audio
File naming format:
{stream_name}_{timestamp}.pcminternal_mic_20250909_133649_738.pcm(built-in microphone)external_mic_20250909_133650_123.pcm(external microphone)
Play saved PCM file:
# Play built-in microphone recording aplay -r 16000 -f S16_LE -c 1 audio_recordings/stream_1/internal_mic_20250909_133649_738.pcm
Parameter explanation:
-r 16000 # Sampling rate 16 kHz
-f S16_LE # 16-bit little-endian format
-c 1 # Mono. You may also use other audio players (e.g., Audacity) to import and play raw PCM files.
Convert to WAV format (optional):
# Use ffmpeg to convert to WAV format ffmpeg -f s16le -ar 16000 -ac 1 -i external_mic_20250909_133649_738.pcm output.wav
Output Example:
Normal startup and operation:
[INFO] [1779195658.978174026] [audio_subscriber]: Start subscribing to noise-suppressed audio data...
[INFO] [1779195658.978795319] [audio_subscriber]: Listening for noise-suppressed audio data, press Ctrl+C to exit...
Speech start detected:
[INFO] [1779195676.331873180] [audio_subscriber]: Received audio data: stream_id=1, vad_state=1(Speech start), audio_size=2560 bytes
[INFO] [1779195676.332029112] [audio_subscriber]: [Built-in microphone] VAD state: Speech start audio: 2560 bytes
[INFO] [1779195676.332061655] [audio_subscriber]: 🎤 Speech start detected
Speech processing:
[INFO] [1779195676.332590408] [audio_subscriber]: Received audio data: stream_id=1, vad_state=2(Speech in progress), audio_size=18560 bytes
[INFO] [1779195676.332690053] [audio_subscriber]: [Built-in microphone] VAD state: Speech in progress audio: 18560 bytes
[INFO] [1779195676.332720229] [audio_subscriber]: 🔄 Speech in progress...
...
Speech end and save:
[INFO] [1779195676.881115234] [audio_subscriber]: Received audio data: stream_id=1, vad_state=3(Speech end), audio_size=2560 bytes
[INFO] [1779195676.881210656] [audio_subscriber]: [Built-in microphone] VAD state: Speech end audio: 2560 bytes
[INFO] [1779195676.881234399] [audio_subscriber]: ✅ Speech end
[INFO] [1779195676.882756437] [audio_subscriber]: Audio segment saved: audio_recordings/stream_1/internal_mic_20260519_210116_881.pcm (size: 42880 bytes)
[INFO] [1779195676.882860210] [audio_subscriber]: Audio duration: 1.34 s (21440 samples)
6.2.22 Raw Microphone Audio Stream Receiver
This example uses mic_raw_receiver, which subscribes to the /aima/hal/audio/capture topic to receive raw audio data from the robot and automatically saves it to PCM files split by channel.
Features:
Stores recordings categorized by timestamp and audio stream
Automatically saves audio channel data as PCM files (mono, 16 kHz sample rate, S16LE format)
Supports mic-source switch detection
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(), "Flushing 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 appended: %zu bytes",
133 filepath.c_str(), 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}
Usage Instructions:
Run the program:
# Build colcon build --packages-select examples # Subscribe to GNSS data ros2 run examples echo_gnss_data
Play saved PCM file:
After running the node, an
audio_recordingsdirectory will be created automaticallyAudio files are stored in a directory named by the program’s startup timestamp:
20250909_133650_123
File naming format:
channel_{index}_{type}.pcm, e.g.channel_0_mic.pcm(microphone)channel_4_ref.pcm(reference / echo-cancellation)
Play saved PCM file:
Play on a computer:
aplay -r 16000 -f S16_LE -c 1 audio_recordings/20250909_133650_123/channel_0_mic.pcm
Play on the robot:
# Use the raw audio stream playback example ros2 run examples play_audio_stream --ros-args -p raw_audio_path:=$PWD/audio_recordings/20250909_133650_123/channel_0_mic.pcm # Or use the media file playback example; note that the file must be transferred to the interaction compute unit first, replace the path below ros2 run examples play_audio please/use/path/on/PC3.pcm
6.2.23 Get Current Microphone
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) { // SUCCESS (1)
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.24 Switch Microphone
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) { // SUCCESS (1)
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}
Usage Instructions
# Build
colcon build --packages-select examples
# Interactive run
ros2 run examples set_mic_source
# Run with argument
ros2 run examples set_mic_source ext # ext - external mic, int - internal mic
6.2.25 Raw Audio Stream Playback
This example uses play_audio_stream, which first requests audio focus then publishes a PCM file at real-time rate to the /aima/hal/audio/playback topic; it exits automatically when audio focus is taken away.
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 = kAudioPriority;
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 // retry as remote peer is NOT handled well by ROS
187 RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
188 }
189
190 auto response = future.get();
191 if (!response) {
192 RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
193 return;
194 }
195 if (response->reponse.status.value ==
196 aimdk_msgs::msg::CommonState::SUCCESS) { // SUCCESS (1)
197 focus_ = response->focus_response.focus_gain;
198 RCLCPP_INFO(this->get_logger(), "✅ RequestAudioFocus done: focus %s",
199 focus_ ? "true" : "false");
200 } else {
201 RCLCPP_ERROR(this->get_logger(),
202 "❌ Failed in response of RequestAudioFocus: %s",
203 response->reponse.message.c_str());
204 }
205 }
206
207 void send_focus_release() {
208 auto req = std::make_shared<AbandonAudioFocus::Request>();
209 aimdk_msgs::msg::FocusRequester requester;
210 requester.pkg_name = pkg_name_;
211 requester.priority = kAudioPriority; // must match RequestAudioFocus
212 req->focus_requester = requester;
213
214 RCLCPP_INFO(this->get_logger(), "📨 Sending AbandonAudioFocus request");
215 std::shared_future<AbandonAudioFocus::Response::SharedPtr> future;
216 for (int i = 0; i < 8; ++i) {
217 future = release_client_->async_send_request(req).future.share();
218 auto status = rclcpp::spin_until_future_complete(
219 shared_from_this(), future, std::chrono::milliseconds(250));
220 if (status == rclcpp::FutureReturnCode::SUCCESS)
221 break;
222 // retry as remote peer is NOT handled well by ROS
223 RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
224 }
225
226 auto response = future.get();
227 if (!response) {
228 RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
229 return;
230 }
231 if (response->reponse.status.value ==
232 aimdk_msgs::msg::CommonState::SUCCESS) { // SUCCESS (1)
233 // always focus False after abandon
234 focus_ = response->focus_response.focus_gain;
235 RCLCPP_INFO(this->get_logger(), "✅ AbandonAudioFocus done: focus %s",
236 focus_ ? "true" : "false");
237 } else {
238 RCLCPP_ERROR(this->get_logger(),
239 "❌ Failed in response of AbandonAudioFocus: %s",
240 response->reponse.message.c_str());
241 }
242 }
243
244 void
245 focus_event_callback(const aimdk_msgs::msg::FocusResponse::SharedPtr msg) {
246 if (msg->pkg_name == pkg_name_) {
247 RCLCPP_INFO(this->get_logger(),
248 "receive focus out event: focus state: %s",
249 msg->focus_gain ? "true" : "false");
250 focus_force_ = msg->focus_gain;
251 }
252 }
253
254 void run_once() {
255 aimdk_msgs::msg::AudioPlayback msg;
256 msg.pkg_name = pkg_name_;
257 msg.token_id = pkg_name_;
258
259 aimdk_msgs::msg::AudioInfo info;
260 info.channels = static_cast<uint8_t>(mic_device_->get_channel_count());
261 info.sample_rate = static_cast<uint32_t>(mic_device_->get_sample_rate());
262
263 aimdk_msgs::msg::AudioData audio_data;
264 audio_data.data = mic_device_->get_cached_data();
265
266 msg.info = info;
267 msg.data = audio_data;
268 pub_->publish(msg);
269 }
270
271 std::string pkg_name_;
272 bool focus_;
273 bool focus_force_;
274 static constexpr uint32_t kAudioPriority = 8;
275 std::unique_ptr<FakeMicDevice> mic_device_;
276 rclcpp::Client<RequestAudioFocus>::SharedPtr request_client_;
277 rclcpp::Client<AbandonAudioFocus>::SharedPtr release_client_;
278 rclcpp::Subscription<aimdk_msgs::msg::FocusResponse>::SharedPtr sub_;
279 rclcpp::Publisher<aimdk_msgs::msg::AudioPlayback>::SharedPtr pub_;
280};
281
282// ---------------------------------------------------------------------------
283// Signal handling
284// ---------------------------------------------------------------------------
285static std::shared_ptr<AudioStreamPlayer> g_node;
286
287void signal_handler(int sig) {
288 if (g_node) {
289 if (g_node->is_holding_focus()) {
290 g_node->send_request(false);
291 }
292 RCLCPP_INFO(g_node->get_logger(),
293 "Received signal %d, abandon audio focus and shutting down",
294 sig);
295 }
296 rclcpp::shutdown();
297}
298
299int main(int argc, char **argv) {
300 rclcpp::init(argc, argv);
301
302 try {
303 auto node = std::make_shared<AudioStreamPlayer>();
304 g_node = node;
305 std::signal(SIGINT, signal_handler);
306 std::signal(SIGTERM, signal_handler);
307
308 node->spin_until_focus();
309 node->run();
310
311 } catch (const std::exception &e) {
312 RCLCPP_ERROR(rclcpp::get_logger("main"),
313 "Program exited with exception: %s", e.what());
314 }
315
316 if (rclcpp::ok()) {
317 rclcpp::shutdown();
318 }
319 return 0;
320}
Usage Instructions:
# Replace /path/to/your/raw_audio_file.pcm with the actual PCM file path (mono, 16 kHz, S16LE)
ros2 run examples play_audio_stream --ros-args -p raw_audio_path:=/path/to/your/raw_audio_file.pcm
6.2.26 Emoji Control
This example uses play_emoji, which allows the robot to display a specified emoji. Users can choose an emoji from the available list; see the Emoji List for details.
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 <iostream>
6#include <memory>
7#include <signal.h>
8#include <string>
9
10// Global variable used for signal handling
11std::shared_ptr<rclcpp::Node> g_node = nullptr;
12
13// Signal handler function
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 PlayEmojiClient : public rclcpp::Node {
25public:
26 PlayEmojiClient() : Node("play_emoji_client") {
27 client_ = this->create_client<aimdk_msgs::srv::PlayEmoji>(
28 "/aimdk_5Fmsgs/srv/PlayEmoji");
29 RCLCPP_INFO(this->get_logger(), "✅ PlayEmoji client node started.");
30
31 // Wait for the service to become available
32 while (!client_->wait_for_service(std::chrono::seconds(2))) {
33 RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
34 }
35 RCLCPP_INFO(this->get_logger(),
36 "🟢 Service available, ready to send request.");
37 }
38
39 bool send_request(uint8_t emoji, uint8_t mode, int32_t priority) {
40 try {
41 auto request = std::make_shared<aimdk_msgs::srv::PlayEmoji::Request>();
42
43 request->emotion_id = emoji;
44 request->mode = mode;
45 request->priority = priority;
46
47 RCLCPP_INFO(
48 this->get_logger(),
49 "📨 Sending request to play emoji: id=%hhu, mode=%hhu, priority=%d",
50 emoji, mode, priority);
51
52 const std::chrono::milliseconds timeout(250);
53 for (int i = 0; i < 8; i++) {
54 request->header.header.stamp = this->now();
55 auto future = client_->async_send_request(request);
56 auto retcode = rclcpp::spin_until_future_complete(shared_from_this(),
57 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->success) {
66 RCLCPP_INFO(this->get_logger(),
67 "✅ Request to play emoji recorded successfully: %s",
68 response->message.c_str());
69 return true;
70 } else {
71 RCLCPP_ERROR(this->get_logger(),
72 "❌ Failed to record play-emoji request: %s",
73 response->message.c_str());
74 return false;
75 }
76 }
77 RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
78 return false;
79 } catch (const std::exception &e) {
80 RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
81 return false;
82 }
83 }
84
85private:
86 rclcpp::Client<aimdk_msgs::srv::PlayEmoji>::SharedPtr client_;
87};
88
89int main(int argc, char **argv) {
90 try {
91 rclcpp::init(argc, argv);
92
93 // Set up signal handlers
94 signal(SIGINT, signal_handler);
95 signal(SIGTERM, signal_handler);
96
97 int32_t priority = 10;
98
99 int emotion = 1; // Expression type, 1 means Blink
100 std::cout
101 << "Enter expression ID: 1-Blink, 60-Bored, 70-Abnormal, 80-Sleeping, "
102 "90-Happy, 190-Very Angry, 200-Adoration"
103 << std::endl;
104 std::cin >> emotion;
105
106 int mode = 1; // Playback mode, 1 means play once, 2 means loop
107 std::cout << "Enter play mode (1: once, 2: loop): ";
108 std::cin >> mode;
109 if (mode < 1 || mode > 2) {
110 RCLCPP_ERROR(rclcpp::get_logger("main"), "Invalid play mode: %d", mode);
111 rclcpp::shutdown();
112 return 1;
113 }
114
115 g_node = std::make_shared<PlayEmojiClient>();
116 auto client = std::dynamic_pointer_cast<PlayEmojiClient>(g_node);
117
118 if (client) {
119 client->send_request(emotion, mode, priority);
120 }
121
122 // Clean up resources
123 g_node.reset();
124 rclcpp::shutdown();
125
126 return 0;
127 } catch (const std::exception &e) {
128 RCLCPP_ERROR(rclcpp::get_logger("main"),
129 "Program exited with exception: %s", e.what());
130 return 1;
131 }
132}
6.2.27 Map Building
In this example, slam is used to send messages to the mapping service via this node to achieve mapping functionality.
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}
Usage Instructions:
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]
After starting the mapping process, push the machine or control the robot to move in order to complete the mapping.
6.2.28 Relocalization Using a Map
This example uses the map built in the Map Building step for localization.
1#include <chrono>
2#include <functional>
3#include <geometry_msgs/msg/pose.hpp>
4#include <iostream>
5#include <nav_msgs/msg/odometry.hpp>
6#include <rclcpp/rclcpp.hpp>
7#include <std_msgs/msg/string.hpp>
8#include <string>
9
10using namespace std::chrono_literals;
11
12class RelocalizationNode : public rclcpp::Node {
13public:
14 RelocalizationNode(uint64_t map_id, double init_x, double init_y)
15 : Node("relocalization_node"), map_id_(map_id), init_x_(init_x),
16 init_y_(init_y) {
17 // /integrated_command subscriber requires TRANSIENT_LOCAL durability
18 auto command_qos = rclcpp::QoS(rclcpp::KeepLast(10));
19 command_qos.transient_local();
20
21 integrated_command_pub_ = this->create_publisher<std_msgs::msg::String>(
22 "/integrated_command", command_qos);
23 relocalization_pose_pub_ = this->create_publisher<geometry_msgs::msg::Pose>(
24 "/relocalization_pose", 10);
25
26 // Create subscriber with BEST_EFFORT QoS
27 auto lidar_loc_qos = rclcpp::QoS(rclcpp::KeepLast(10));
28 lidar_loc_qos.best_effort();
29
30 odometry_sub_ = this->create_subscription<nav_msgs::msg::Odometry>(
31 "/slam/lidar_odom", lidar_loc_qos,
32 std::bind(&RelocalizationNode::odometryCallback, this,
33 std::placeholders::_1));
34
35 // Wait for subscribers before publishing
36 publish_timer_ = this->create_wall_timer(200ms, [this]() {
37 // Wait until both publishers have at least one subscriber
38 if (integrated_command_pub_->get_subscription_count() == 0 ||
39 relocalization_pose_pub_->get_subscription_count() == 0) {
40 RCLCPP_INFO_THROTTLE(
41 this->get_logger(), *this->get_clock(), 2000,
42 "Waiting for subscribers... (command: %zu, pose: %zu)",
43 integrated_command_pub_->get_subscription_count(),
44 relocalization_pose_pub_->get_subscription_count());
45 return;
46 }
47 publish_timer_->cancel();
48
49 // Publish integrated_command
50 auto integrated_command_msg = std_msgs::msg::String();
51 integrated_command_msg.data =
52 "start_relocalization:" + std::to_string(map_id_);
53 integrated_command_pub_->publish(integrated_command_msg);
54 RCLCPP_INFO(this->get_logger(), "Published integrated_command");
55
56 // Schedule relocalization_pose publication after delay
57 this->relocalization_pose_timer_ = this->create_wall_timer(1s, [this]() {
58 this->relocalization_pose_timer_->cancel();
59
60 auto relocalization_pose_msg = geometry_msgs::msg::Pose();
61 relocalization_pose_msg.position.x = init_x_;
62 relocalization_pose_msg.position.y = init_y_;
63 relocalization_pose_msg.position.z = 0.0;
64 relocalization_pose_msg.orientation.x = 0.0;
65 relocalization_pose_msg.orientation.y = 0.0;
66 relocalization_pose_msg.orientation.z = 0.0;
67 relocalization_pose_msg.orientation.w = 1.0;
68
69 relocalization_pose_pub_->publish(relocalization_pose_msg);
70 RCLCPP_INFO(this->get_logger(), "Published relocalization_pose");
71
72 // Start timeout timer (30 seconds)
73 this->timeout_timer_ = this->create_wall_timer(30s, [this]() {
74 if (!success_received_) {
75 RCLCPP_ERROR(this->get_logger(),
76 "Timeout reached - Relocalization failed!");
77 rclcpp::shutdown();
78 }
79 });
80
81 RCLCPP_INFO(this->get_logger(),
82 "Waiting for robot pose data (timeout: 30s)...");
83 });
84 });
85 }
86
87private:
88 // get robot pose here
89 void odometryCallback(const nav_msgs::msg::Odometry::SharedPtr msg) {
90 if (!success_received_) {
91 success_received_ = true;
92 if (timeout_timer_) {
93 timeout_timer_->cancel();
94 }
95 RCLCPP_INFO(this->get_logger(),
96 "Received odometry data - Relocalization successful!");
97 rclcpp::shutdown();
98 }
99 }
100
101 rclcpp::Publisher<std_msgs::msg::String>::SharedPtr integrated_command_pub_;
102 rclcpp::Publisher<geometry_msgs::msg::Pose>::SharedPtr
103 relocalization_pose_pub_;
104 rclcpp::Subscription<nav_msgs::msg::Odometry>::SharedPtr odometry_sub_;
105
106 // Timers
107 rclcpp::TimerBase::SharedPtr publish_timer_;
108 rclcpp::TimerBase::SharedPtr relocalization_pose_timer_;
109 rclcpp::TimerBase::SharedPtr timeout_timer_;
110
111 uint64_t map_id_;
112 double init_x_;
113 double init_y_;
114 bool success_received_ = false;
115};
116
117int main(int argc, char *argv[]) {
118 rclcpp::init(argc, argv);
119
120 // Inputs needed for relocalization. To find them, see:
121 // map_id -> sqlite3 /agibot/data/var/MapManagerModule/map.db
122 // "SELECT * FROM map;"
123 // init_x/init_y -> origin (u, v) in
124 // /agibot/data/var/MapManagerModule/<map_id>/grid_map_info.txt
125 // (line 2; pixel coordinates)
126 uint64_t map_id = 0;
127 double init_x = 0.0;
128 double init_y = 0.0;
129 try {
130 std::string line;
131 std::cout << "Enter map_id: ";
132 std::getline(std::cin, line);
133 map_id = std::stoull(line);
134 std::cout << "Enter initial position.x (pixel u): ";
135 std::getline(std::cin, line);
136 init_x = std::stod(line);
137 std::cout << "Enter initial position.y (pixel v): ";
138 std::getline(std::cin, line);
139 init_y = std::stod(line);
140 } catch (const std::exception &e) {
141 std::cerr << "Invalid input, aborted." << std::endl;
142 rclcpp::shutdown();
143 return 1;
144 }
145
146 auto node = std::make_shared<RelocalizationNode>(map_id, init_x, init_y);
147 rclcpp::spin(node);
148 rclcpp::shutdown();
149 return 0;
150}
Usage Instructions:
ros2 run examples relocate
Enter map_id: 1774430080403
Enter initial position.x (pixel u): 273
Enter initial position.y (pixel v): 200
[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: 30s)...
[INFO] [1772528945.130242770] [relocalization_node]: Received odometry data - Relocalization successful!
Move the robot to the starting point of the map construction, then run the relocation program.
How to obtain these parameters: for map_id and the starting-point pixel coordinates (position.x / position.y), see Obtaining Map Identifiers and Coordinates.
6.2.29 Retrieve a Stored Map
In this example, get_map is used to retrieve stored map data by name, including the map path, resolution, dimensions, and 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 <chrono>
8#include <cinttypes>
9#include <iostream>
10#include <memory>
11#include <string>
12
13class MapClient : public rclcpp::Node {
14public:
15 MapClient() : Node("get_map_client") {
16 client_ = this->create_client<aimdk_msgs::srv::GetStoredMapByName>(
17 "/aimdk_5Fmsgs/srv/GetStoredMapByName");
18 }
19
20 void send_request(const std::string &map_name) {
21 while (!client_->wait_for_service(std::chrono::seconds(1))) {
22 if (!rclcpp::ok()) {
23 RCLCPP_ERROR(this->get_logger(),
24 "Interrupted while waiting for service.");
25 return;
26 }
27 RCLCPP_INFO(this->get_logger(),
28 "Service not available, waiting again...");
29 }
30
31 auto request =
32 std::make_shared<aimdk_msgs::srv::GetStoredMapByName::Request>();
33
34 request->header.stamp = this->now();
35 request->header.frame_id = "";
36
37 request->map_name = map_name;
38
39 auto result_future = client_->async_send_request(request);
40
41 // Use a fresh executor to spin, avoiding any pre-existing state.
42 rclcpp::executors::SingleThreadedExecutor exec;
43 exec.add_node(shared_from_this());
44 if (exec.spin_until_future_complete(result_future,
45 std::chrono::seconds(60)) !=
46 rclcpp::FutureReturnCode::SUCCESS) {
47 RCLCPP_ERROR(this->get_logger(), "Service call failed or timed out.");
48 return;
49 }
50
51 auto result = result_future.get();
52
53 if (result->code == 0) {
54 RCLCPP_INFO(this->get_logger(), "Service call succeeded");
55 RCLCPP_INFO(this->get_logger(), "Map path: %s", result->map_path.c_str());
56 RCLCPP_INFO(this->get_logger(), "Map resolution: %f",
57 result->map_info.resolution);
58 RCLCPP_INFO(this->get_logger(), "Map width: %u", result->map_info.width);
59 RCLCPP_INFO(this->get_logger(), "Map height: %u",
60 result->map_info.height);
61 RCLCPP_INFO(this->get_logger(), "Map id: %" PRIu64, result->map_id);
62 } else {
63 RCLCPP_ERROR(this->get_logger(),
64 "Service call failed with code: %" PRIu64, result->code);
65 }
66 }
67
68private:
69 rclcpp::Client<aimdk_msgs::srv::GetStoredMapByName>::SharedPtr client_;
70};
71
72int main(int argc, char **argv) {
73 rclcpp::init(argc, argv);
74
75 auto client = std::make_shared<MapClient>();
76
77 // Map name (NOT map_id) is required. To list stored maps, run:
78 // sqlite3 /agibot/data/var/MapManagerModule/map.db "SELECT * FROM map;"
79 std::cout << "Enter map name: ";
80 std::string map_name;
81 std::getline(std::cin, map_name);
82 if (map_name.empty()) {
83 RCLCPP_ERROR(client->get_logger(), "Map name is empty, aborted.");
84 rclcpp::shutdown();
85 return 1;
86 }
87 client->send_request(map_name);
88
89 rclcpp::shutdown();
90 return 0;
91}
Usage Instructions:
ros2 run examples get_map
Enter map name: test_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: 1774430080403
How to obtain these parameters: the input is the map name (map_name), not map_id; to obtain map_name, see Obtaining Map Identifiers and Coordinates.
6.2.31 Play LinkCraft Action
In this example, play_linkcraft is used to list the LinkCraft actions available on the robot and select one for playback.
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 <cinttypes>
8#include <iostream>
9#include <rclcpp/rclcpp.hpp>
10#include <string>
11#include <vector>
12
13using namespace std::chrono_literals;
14
15class RobotResourceClient : public rclcpp::Node {
16public:
17 RobotResourceClient() : Node("robot_resource_client") {
18 get_resource_client_ =
19 this->create_client<aimdk_msgs::srv::GetRobotResources>(
20 "/aimdk_5Fmsgs/srv/GetRobotResources");
21 play_resource_client_ =
22 this->create_client<aimdk_msgs::srv::ExecuteActionResource>(
23 "/aimdk_5Fmsgs/srv/ExecuteActionResource");
24 }
25
26 bool get_robot_resources() {
27 while (!get_resource_client_->wait_for_service(1s)) {
28 if (!rclcpp::ok()) {
29 return false;
30 }
31 RCLCPP_INFO(this->get_logger(),
32 "Service not available, waiting again...");
33 }
34
35 if (!rclcpp::ok()) {
36 return false;
37 }
38
39 auto request =
40 std::make_shared<aimdk_msgs::srv::GetRobotResources::Request>();
41
42 auto future_result = get_resource_client_->async_send_request(request);
43
44 if (rclcpp::spin_until_future_complete(this->get_node_base_interface(),
45 future_result) !=
46 rclcpp::FutureReturnCode::SUCCESS) {
47 if (rclcpp::ok()) {
48 RCLCPP_ERROR(this->get_logger(), "Failed to call GetRobotResources");
49 }
50 return false;
51 }
52
53 auto response = future_result.get();
54 resources_ = response->robot_resources;
55 RCLCPP_INFO(this->get_logger(), "Found %zu resources", resources_.size());
56 return true;
57 }
58
59 void print_resource_menu() {
60 if (resources_.empty()) {
61 std::cout << "(no resources)\n";
62 return;
63 }
64
65 std::cout << "\n=== Available Robot Resources ===\n";
66 for (size_t i = 0; i < resources_.size(); ++i) {
67 const auto &ver = resources_[i].current_version;
68 std::cout << "[" << i << "] "
69 << (ver.name.empty() ? "<no name>" : ver.name) << "\n";
70 std::cout << " key: " << resources_[i].resource_key << "\n";
71 std::cout << " version: " << ver.version << "\n";
72 }
73 std::cout << "=================================\n\n";
74 }
75
76 int pick_resource() {
77 if (resources_.empty()) {
78 return -1;
79 }
80
81 std::string line;
82 while (true) {
83 std::cout << "Select resource index [0-" << resources_.size() - 1
84 << "] (q to quit): ";
85 if (!std::getline(std::cin, line)) {
86 return -1;
87 }
88 if (line == "q" || line == "quit" || line == "exit") {
89 return -1;
90 }
91 try {
92 int idx = std::stoi(line);
93 if (idx >= 0 && idx < static_cast<int>(resources_.size())) {
94 return idx;
95 }
96 std::cout << "Index out of range (0-" << resources_.size() - 1
97 << ").\n";
98 } catch (...) {
99 std::cout << "Please enter a number.\n";
100 }
101 }
102 }
103
104 void play_resource(int idx) {
105 while (!play_resource_client_->wait_for_service(1s)) {
106 if (!rclcpp::ok()) {
107 RCLCPP_ERROR(this->get_logger(),
108 "Interrupted while waiting for service.");
109 return;
110 }
111 RCLCPP_INFO(this->get_logger(), "Service not available, waiting...");
112 }
113
114 if (!rclcpp::ok()) {
115 return;
116 }
117
118 const auto &resource = resources_[idx];
119 auto request =
120 std::make_shared<aimdk_msgs::srv::ExecuteActionResource::Request>();
121
122 request->resource_key = resource.resource_key;
123 request->resource_version = resource.current_version.version;
124
125 if (request->resource_key.find("onnx") != std::string::npos) {
126 request->meta = R"({"resource_type": "BODY_MONTION"})";
127 } else {
128 request->meta = R"({"resource_type": "ARM_MONTION"})";
129 }
130
131 RCLCPP_INFO(this->get_logger(), "Sending request:");
132 RCLCPP_INFO(this->get_logger(), " resource_key: %s",
133 request->resource_key.c_str());
134 RCLCPP_INFO(this->get_logger(), " resource_version: %s",
135 request->resource_version.c_str());
136 RCLCPP_INFO(this->get_logger(), " meta: %s", request->meta.c_str());
137
138 auto future = play_resource_client_->async_send_request(request);
139
140 if (rclcpp::spin_until_future_complete(this->get_node_base_interface(),
141 future) ==
142 rclcpp::FutureReturnCode::SUCCESS) {
143 handle_response(future.get());
144 } else {
145 if (rclcpp::ok()) {
146 RCLCPP_ERROR(this->get_logger(),
147 "Failed to call ExecuteActionResource");
148 }
149 }
150 }
151
152private:
153 rclcpp::Client<aimdk_msgs::srv::GetRobotResources>::SharedPtr
154 get_resource_client_;
155 rclcpp::Client<aimdk_msgs::srv::ExecuteActionResource>::SharedPtr
156 play_resource_client_;
157 std::vector<aimdk_msgs::msg::RobotResource> resources_;
158
159 void handle_response(
160 const aimdk_msgs::srv::ExecuteActionResource::Response::SharedPtr
161 response) {
162 RCLCPP_INFO(this->get_logger(), "Service response received:");
163 RCLCPP_INFO(this->get_logger(), "Response Header:");
164 RCLCPP_INFO(this->get_logger(), " stamp: %d.%09u",
165 response->header.header.stamp.sec,
166 response->header.header.stamp.nanosec);
167 RCLCPP_INFO(this->get_logger(), " code: %" PRId64,
168 response->header.header.code);
169 RCLCPP_INFO(this->get_logger(), "Status:");
170 RCLCPP_INFO(this->get_logger(), " value: %d",
171 response->header.status.value);
172 RCLCPP_INFO(this->get_logger(), " message: %s",
173 response->header.message.c_str());
174 }
175};
176
177int main(int argc, char **argv) {
178 rclcpp::init(argc, argv);
179 auto client_node = std::make_shared<RobotResourceClient>();
180
181 if (!client_node->get_robot_resources()) {
182 rclcpp::shutdown();
183 return 1;
184 }
185
186 client_node->print_resource_menu();
187 int chosen = client_node->pick_resource();
188 if (chosen < 0) {
189 RCLCPP_INFO(client_node->get_logger(), "No resource selected, exiting.");
190 rclcpp::shutdown();
191 return 0;
192 }
193
194 client_node->play_resource(chosen);
195 rclcpp::shutdown();
196 return 0;
197}
Usage Instructions:
ros2 run examples play_linkcraft
[INFO] [1772529153.517267476] [robot_resource_client]: Found 1 resources
=== Available Robot Resources ===
[0] resource_key: linkcraft_resource_onnx_01KBM2BHNFM93Z0DK6ES9F6DJA
key: linkcraft_resource_onnx_01KBM2BHNFM93Z0DK6ES9F6DJA
version: 0.0.1
=================================
Select resource index [0-0]: 0
[INFO] [1772529153.517488347] [robot_resource_client]: Sending request:
[INFO] [1772529153.517497834] [robot_resource_client]: resource_key: linkcraft_resource_onnx_01KBM2BHNFM93Z0DK6ES9F6DJA
[INFO] [1772529153.517505889] [robot_resource_client]: resource_version: 0.0.1
[INFO] [1772529153.517473238] [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.32 Play LinkCraft Action (Multi-Robot)
In this example, multi_play_linkcraft is used to synchronize time across multiple robots and perform group-controlled LinkCraft action playback.
1//
2// Created by agiuser on 2026/3/3.
3//
4
5#include <aimdk_msgs/msg/slave_device.hpp>
6#include <aimdk_msgs/srv/execute_action_resource.hpp>
7#include <aimdk_msgs/srv/get_robot_resources.hpp>
8#include <aimdk_msgs/srv/group_control_time_sync.hpp>
9#include <cinttypes>
10#include <iostream>
11#include <rclcpp/rclcpp.hpp>
12#include <string>
13#include <vector>
14
15using namespace std::chrono_literals;
16
17class MultiRobotResourceClient : public rclcpp::Node {
18public:
19 MultiRobotResourceClient() : Node("robot_resource_client") {
20 get_resource_client_ =
21 this->create_client<aimdk_msgs::srv::GetRobotResources>(
22 "/aimdk_5Fmsgs/srv/GetRobotResources");
23 play_resource_client_ =
24 this->create_client<aimdk_msgs::srv::ExecuteActionResource>(
25 "/aimdk_5Fmsgs/srv/ExecuteActionResource");
26 time_sync_client_ =
27 this->create_client<aimdk_msgs::srv::GroupControlTimeSync>(
28 "/aimdk_5Fmsgs/srv/GroupControlTimeSync");
29 }
30
31 bool get_robot_resources() {
32 while (!get_resource_client_->wait_for_service(1s)) {
33 if (!rclcpp::ok()) {
34 return false;
35 }
36 RCLCPP_INFO(this->get_logger(),
37 "Service not available, waiting again...");
38 }
39
40 if (!rclcpp::ok()) {
41 return false;
42 }
43
44 auto request =
45 std::make_shared<aimdk_msgs::srv::GetRobotResources::Request>();
46
47 auto future_result = get_resource_client_->async_send_request(request);
48
49 if (rclcpp::spin_until_future_complete(this->get_node_base_interface(),
50 future_result) !=
51 rclcpp::FutureReturnCode::SUCCESS) {
52 if (rclcpp::ok()) {
53 RCLCPP_ERROR(this->get_logger(), "Failed to call GetRobotResources");
54 }
55 return false;
56 }
57
58 auto response = future_result.get();
59 resources_ = response->robot_resources;
60 RCLCPP_INFO(this->get_logger(), "Found %zu resources", resources_.size());
61 return true;
62 }
63
64 void print_resource_menu() {
65 if (resources_.empty()) {
66 std::cout << "(no resources)\n";
67 return;
68 }
69
70 std::cout << "\n=== Available Robot Resources ===\n";
71 for (size_t i = 0; i < resources_.size(); ++i) {
72 const auto &ver = resources_[i].current_version;
73 std::cout << "[" << i << "] "
74 << (ver.name.empty() ? "<no name>" : ver.name) << "\n";
75 std::cout << " key: " << resources_[i].resource_key << "\n";
76 std::cout << " version: " << ver.version << "\n";
77 }
78 std::cout << "=================================\n\n";
79 }
80
81 int pick_resource() {
82 if (resources_.empty()) {
83 return -1;
84 }
85
86 std::string line;
87 while (true) {
88 std::cout << "Select resource index [0-" << resources_.size() - 1
89 << "] (q to quit): ";
90 if (!std::getline(std::cin, line)) {
91 return -1;
92 }
93 if (line == "q" || line == "quit" || line == "exit") {
94 return -1;
95 }
96 try {
97 int idx = std::stoi(line);
98 if (idx >= 0 && idx < static_cast<int>(resources_.size())) {
99 return idx;
100 }
101 std::cout << "Index out of range (0-" << resources_.size() - 1
102 << ").\n";
103 } catch (...) {
104 std::cout << "Please enter a number.\n";
105 }
106 }
107 }
108
109 void play_resource(int idx) {
110 while (!play_resource_client_->wait_for_service(1s)) {
111 if (!rclcpp::ok()) {
112 RCLCPP_ERROR(this->get_logger(),
113 "Interrupted while waiting for service.");
114 return;
115 }
116 RCLCPP_INFO(this->get_logger(), "Service not available, waiting...");
117 }
118
119 if (!rclcpp::ok()) {
120 return;
121 }
122
123 const auto &resource = resources_[idx];
124 auto request =
125 std::make_shared<aimdk_msgs::srv::ExecuteActionResource::Request>();
126
127 request->resource_key = resource.resource_key;
128 request->resource_version = resource.current_version.version;
129
130 for (const auto &ip : slave_ips_) {
131 aimdk_msgs::msg::SlaveDevice slave;
132 slave.ip = ip;
133 request->slaves.push_back(slave);
134 }
135
136 if (request->resource_key.find("onnx") != std::string::npos) {
137 request->meta =
138 R"({"control_type": "GROUP_CONTROL","resource_type": "BODY_MONTION"})";
139 } else {
140 request->meta =
141 R"({"control_type": "GROUP_CONTROL","resource_type": "ARM_MONTION"})";
142 }
143
144 RCLCPP_INFO(this->get_logger(), "Sending request:");
145 RCLCPP_INFO(this->get_logger(), " resource_key: %s",
146 request->resource_key.c_str());
147 RCLCPP_INFO(this->get_logger(), " resource_version: %s",
148 request->resource_version.c_str());
149 RCLCPP_INFO(this->get_logger(), " meta: %s", request->meta.c_str());
150 for (size_t i = 0; i < request->slaves.size(); ++i) {
151 RCLCPP_INFO(this->get_logger(), " slaves[%zu] ip: %s", i,
152 request->slaves[i].ip.c_str());
153 }
154
155 auto future = play_resource_client_->async_send_request(request);
156
157 if (rclcpp::spin_until_future_complete(this->get_node_base_interface(),
158 future) ==
159 rclcpp::FutureReturnCode::SUCCESS) {
160 handle_response(future.get());
161 } else {
162 if (rclcpp::ok()) {
163 RCLCPP_ERROR(this->get_logger(),
164 "Failed to call ExecuteActionResource");
165 }
166 }
167 }
168
169 bool sync_time(const std::vector<std::string> &ips) {
170 slave_ips_ = ips;
171
172 while (!time_sync_client_->wait_for_service(1s)) {
173 if (!rclcpp::ok()) {
174 return false;
175 }
176 RCLCPP_INFO(this->get_logger(),
177 "TimeSync service not available, waiting again...");
178 }
179
180 if (!rclcpp::ok()) {
181 return false;
182 }
183
184 auto request =
185 std::make_shared<aimdk_msgs::srv::GroupControlTimeSync::Request>();
186
187 for (const auto &ip : ips) {
188 aimdk_msgs::msg::SlaveDevice slave;
189 slave.ip = ip;
190 request->slaves.push_back(slave);
191 }
192
193 RCLCPP_INFO(this->get_logger(), "Sending GroupControlTimeSync request:");
194 for (const auto &slave : request->slaves) {
195 RCLCPP_INFO(this->get_logger(), " slave ip: %s", slave.ip.c_str());
196 }
197
198 auto future = time_sync_client_->async_send_request(request);
199
200 if (rclcpp::spin_until_future_complete(this->get_node_base_interface(),
201 future) !=
202 rclcpp::FutureReturnCode::SUCCESS) {
203 if (rclcpp::ok()) {
204 RCLCPP_ERROR(this->get_logger(), "Failed to call GroupControlTimeSync");
205 }
206 return false;
207 }
208
209 auto response = future.get();
210 RCLCPP_INFO(this->get_logger(), "GroupControlTimeSync response:");
211 RCLCPP_INFO(this->get_logger(), " error_code: %d", response->error_code);
212 RCLCPP_INFO(this->get_logger(), " error_msg: %s",
213 response->error_msg.c_str());
214 return response->error_code == 0;
215 }
216
217private:
218 rclcpp::Client<aimdk_msgs::srv::GetRobotResources>::SharedPtr
219 get_resource_client_;
220 rclcpp::Client<aimdk_msgs::srv::ExecuteActionResource>::SharedPtr
221 play_resource_client_;
222 rclcpp::Client<aimdk_msgs::srv::GroupControlTimeSync>::SharedPtr
223 time_sync_client_;
224 std::vector<aimdk_msgs::msg::RobotResource> resources_;
225 std::vector<std::string> slave_ips_;
226
227 void handle_response(
228 const aimdk_msgs::srv::ExecuteActionResource::Response::SharedPtr
229 response) {
230 RCLCPP_INFO(this->get_logger(), "Service response received:");
231 RCLCPP_INFO(this->get_logger(), "Response Header:");
232 RCLCPP_INFO(this->get_logger(), " stamp: %d.%09u",
233 response->header.header.stamp.sec,
234 response->header.header.stamp.nanosec);
235 RCLCPP_INFO(this->get_logger(), " code: %" PRId64,
236 response->header.header.code);
237 RCLCPP_INFO(this->get_logger(), "Status:");
238 RCLCPP_INFO(this->get_logger(), " value: %d",
239 response->header.status.value);
240 RCLCPP_INFO(this->get_logger(), " message: %s",
241 response->header.message.c_str());
242 }
243};
244
245int main(int argc, char **argv) {
246 rclcpp::init(argc, argv);
247 auto client_node = std::make_shared<MultiRobotResourceClient>();
248
249 if (!client_node->get_robot_resources()) {
250 rclcpp::shutdown();
251 return 1;
252 }
253
254 // [USER_CONFIG] Replace the IPs below with your slave robot IPs.
255 std::vector<std::string> slave_ips = {"172.22.133.178", "172.22.135.45"};
256 if (!client_node->sync_time(slave_ips)) {
257 RCLCPP_WARN(client_node->get_logger(),
258 "Time sync failed or returned error.");
259 }
260
261 client_node->print_resource_menu();
262 int chosen = client_node->pick_resource();
263 if (chosen < 0) {
264 RCLCPP_INFO(client_node->get_logger(), "No resource selected, exiting.");
265 rclcpp::shutdown();
266 return 0;
267 }
268
269 client_node->play_resource(chosen);
270 rclcpp::shutdown();
271 return 0;
272}
Usage Instructions:
ros2 run examples multi_play_linkcraft
[INFO] [1772529153.517267476] [robot_resource_client]: Found 1 resources
[INFO] [1772529153.517473238] [robot_resource_client]: Sending GroupControlTimeSync request:
[INFO] [1772529153.517488347] [robot_resource_client]: slave ip: 172.22.133.178
[INFO] [1772529153.517497834] [robot_resource_client]: slave ip: 172.22.135.45
[INFO] [1772529153.517505889] [robot_resource_client]: GroupControlTimeSync response:
[INFO] [1772529153.517506000] [robot_resource_client]: error_code: 0
[INFO] [1772529153.517506100] [robot_resource_client]: error_msg: ok
[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: {"control_type": "GROUP_CONTROL","resource_type": "BODY_MONTION"}
[INFO] [1772529153.517506200] [robot_resource_client]: slaves[0] ip: 172.22.133.178
[INFO] [1772529153.517506300] [robot_resource_client]: slaves[1] ip: 172.22.135.45
[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.33 LED Strip Control
Function Description: Demonstrates how to control the robot’s LED strip, supporting multiple display modes and customizable colors.
Attention
Before using this feature, run the following command on the Motion Control Computing Unit (PC1) to stop the task_manager module and prevent it from competing with your program for LED strip control:
aima em stop-app task_manager
Core Code:
1#include <aimdk_msgs/msg/common_request.hpp>
2#include <aimdk_msgs/srv/set_pmu_led.hpp>
3#include <algorithm>
4#include <cctype>
5#include <chrono>
6#include <iostream>
7#include <memory>
8#include <rclcpp/rclcpp.hpp>
9#include <signal.h>
10#include <string>
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 PlayLightsClient : public rclcpp::Node {
25public:
26 PlayLightsClient() : Node("play_lights_client") {
27 client_ = this->create_client<aimdk_msgs::srv::SetPmuLed>(
28 "/aimdk_5Fmsgs/srv/SetPmuLed");
29 RCLCPP_INFO(this->get_logger(), "✅ PlayLights client node started.");
30
31 // Wait for the service to become available
32 while (!client_->wait_for_service(std::chrono::seconds(2))) {
33 RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
34 }
35 RCLCPP_INFO(this->get_logger(),
36 "🟢 Service available, ready to send request.");
37 }
38
39 bool send_request(uint8_t led_mode, uint8_t r, uint8_t g, uint8_t b,
40 int32_t priority = 6, bool reset_priority = false) {
41 try {
42 auto request = std::make_shared<aimdk_msgs::srv::SetPmuLed::Request>();
43
44 request->led_strip_mode = led_mode;
45 request->r = r;
46 request->g = g;
47 request->b = b;
48 request->priority = priority;
49 request->reset_priority = reset_priority;
50 request->trace_id = "play_lights";
51
52 RCLCPP_INFO(this->get_logger(),
53 "📨 Sending request to control led strip: mode=%hhu, "
54 "RGB=(%hhu, %hhu, %hhu), priority=%d, reset_priority=%s",
55 led_mode, r, g, b, priority,
56 reset_priority ? "true" : "false");
57
58 // LED strip is slow to response (up to ~5s)
59 const std::chrono::milliseconds timeout(5000);
60 for (int i = 0; i < 4; i++) {
61 request->request.header.stamp = this->now();
62 auto future = client_->async_send_request(request);
63 auto retcode = rclcpp::spin_until_future_complete(
64 this->shared_from_this(), future, timeout);
65
66 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
67 // retry as remote peer is NOT handled well by ROS
68 RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
69 continue;
70 }
71 // future.done
72 auto response = future.get();
73 if (response->status_code == 0) {
74 RCLCPP_INFO(this->get_logger(),
75 "✅ LED strip command sent successfully.");
76 return true;
77 } else {
78 // 0x1024: priority insufficient (priority < current threshold);
79 // use a higher priority or set reset_priority=true to reset the
80 // threshold to 0.
81 RCLCPP_ERROR(this->get_logger(),
82 "❌ LED strip command failed with status: 0x%X (%s)",
83 response->status_code,
84 response->status_code == 0x1024 ? "priority insufficient"
85 : "failure");
86 return false;
87 }
88 }
89 RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
90 return false;
91 } catch (const std::exception &e) {
92 RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
93 return false;
94 }
95 }
96
97private:
98 rclcpp::Client<aimdk_msgs::srv::SetPmuLed>::SharedPtr client_;
99};
100
101int main(int argc, char **argv) {
102 try {
103 rclcpp::init(argc, argv);
104 signal(SIGINT, signal_handler);
105 signal(SIGTERM, signal_handler);
106
107 g_node = std::make_shared<PlayLightsClient>();
108 auto client_node = std::dynamic_pointer_cast<PlayLightsClient>(g_node);
109
110 int led_mode = 0; // LED Strip Mode
111 int r = 255, g = 0, b = 0; // RGB values
112 int priority = 6; // Priority
113 bool reset_priority = false;
114
115 // Parse command-line arguments: mode R G B priority reset_priority
116 // e.g. ros2 run examples play_lights 1 255 0 0 6 false
117 if (argc > 6) {
118 led_mode = std::stoi(argv[1]);
119 if (led_mode < 0 || led_mode > 3) {
120 throw std::runtime_error("invalid mode");
121 }
122 r = std::stoi(argv[2]);
123 if (r < 0 || r > 255) {
124 throw std::runtime_error("invalid R value");
125 }
126 g = std::stoi(argv[3]);
127 if (g < 0 || g > 255) {
128 throw std::runtime_error("invalid G value");
129 }
130 b = std::stoi(argv[4]);
131 if (b < 0 || b > 255) {
132 throw std::runtime_error("invalid B value");
133 }
134 priority = std::stoi(argv[5]);
135 if (priority < 0) {
136 throw std::runtime_error("invalid priority value");
137 }
138 std::string reset_str = argv[6];
139 std::transform(reset_str.begin(), reset_str.end(), reset_str.begin(),
140 ::tolower);
141 reset_priority = (reset_str == "true" || reset_str == "1" ||
142 reset_str == "yes" || reset_str == "y");
143 } else {
144 std::cout << "=== LED strip control example ===" << std::endl;
145 std::cout << "Select LED strip mode:" << std::endl;
146 std::cout << "0 - Steady on" << std::endl;
147 std::cout << "1 - Breathing (4s cycle, sine brightness)" << std::endl;
148 std::cout << "2 - Blinking (1s cycle, 0.5s on, 0.5s off)" << std::endl;
149 std::cout << "3 - Flowing (2s cycle, light up from left to right)"
150 << std::endl;
151 std::cout << "Enter mode (0-3): ";
152 std::cin >> led_mode;
153 if (led_mode < 0 || led_mode > 3) {
154 throw std::runtime_error("invalid mode");
155 }
156
157 std::cout << "\nSet RGB color values (0-255):" << std::endl;
158 std::cout << "Red (R): ";
159 std::cin >> r;
160 if (r < 0 || r > 255) {
161 throw std::runtime_error("invalid R value");
162 }
163 std::cout << "Green (G): ";
164 std::cin >> g;
165 if (g < 0 || g > 255) {
166 throw std::runtime_error("invalid G value");
167 }
168 std::cout << "Blue (B): ";
169 std::cin >> b;
170 if (b < 0 || b > 255) {
171 throw std::runtime_error("invalid B value");
172 }
173
174 std::cout
175 << "\nSet priority (higher value = higher priority, default=6): ";
176 std::cin >> priority;
177 if (priority < 0) {
178 throw std::runtime_error("invalid priority value");
179 }
180
181 std::string reset_input;
182 std::cout << "Reset priority after command? (y/n, default=n): ";
183 std::cin >> reset_input;
184 std::transform(reset_input.begin(), reset_input.end(),
185 reset_input.begin(), ::tolower);
186 reset_priority = (reset_input == "true" || reset_input == "1" ||
187 reset_input == "yes" || reset_input == "y");
188 }
189
190 if (client_node) {
191 client_node->send_request(led_mode, r, g, b, priority, reset_priority);
192 }
193
194 g_node.reset();
195 rclcpp::shutdown();
196
197 return 0;
198 } catch (const std::exception &e) {
199 RCLCPP_ERROR(rclcpp::get_logger("main"),
200 "Program terminated with exception: %s", e.what());
201 return 1;
202 }
203}
Usage Instructions:
# Build
colcon build --packages-select examples
# Run interactively
ros2 run examples play_lights
# Run with command-line parameters: <mode> <R> <G> <B> <priority> <reset_priority(true/false)>
ros2 run examples play_lights 1 255 0 0 6 false # Mode 1, red, priority 6
ros2 run examples set_mic_source
# Run with argument
ros2 run examples set_mic_source ext # ext - external mic, int - internal mic
Output Example:
=== LED strip control example ===
Select LED strip mode:
0 - Steady on
1 - Breathing (4s cycle, sine brightness)
2 - Blinking (1s cycle, 0.5s on, 0.5s off)
3 - Flowing (2s cycle, light up from left to right)
Enter mode (0-3): 1
Set RGB color values (0-255):
Red (R): 255
Green (G): 0
Blue (B): 0
Set priority (higher value = higher priority, default=6): 6
Reset priority after command? (y/n, default=n): n
[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.
Technical Features:
Supports 4 LED display modes
Customizable RGB colors
Priority control (higher value = higher priority)
Priority reset support
Asynchronous service calls
Command-line parameter support
Input parameter validation
User-friendly interactive interface
6.2.34 GNSS Positioning Data Subscription
This example uses echo_gnss_data to subscribe to GNSS topics for positioning data, outputting latitude, longitude, altitude, and covariance information.
Features:
Subscribes to NavSatFix positioning data (latitude, longitude, altitude)
Real-time display of positioning status and covariance
Topics:
/aima/hal/sensor/gnss: Positioning data (sensor_msgs/NavSatFix), published automatically by the system
1#include <chrono>
2#include <iomanip>
3#include <rclcpp/rclcpp.hpp>
4#include <sensor_msgs/msg/nav_sat_fix.hpp>
5#include <sstream>
6#include <string>
7
8class GnssPrinter : public rclcpp::Node {
9public:
10 GnssPrinter() : rclcpp::Node("gnss_printer") {
11 constexpr char kNavSatTopic[] = "/aima/hal/sensor/gnss";
12
13 auto qos = rclcpp::QoS(rclcpp::KeepLast(10))
14 .reliability(rclcpp::ReliabilityPolicy::Reliable)
15 .durability(rclcpp::DurabilityPolicy::TransientLocal);
16
17 navsat_sub_ = this->create_subscription<sensor_msgs::msg::NavSatFix>(
18 kNavSatTopic, qos,
19 std::bind(&GnssPrinter::navsat_cb, this, std::placeholders::_1));
20
21 RCLCPP_INFO(this->get_logger(), "Subscribing NavSatFix: %s", kNavSatTopic);
22 }
23
24private:
25 void navsat_cb(const sensor_msgs::msg::NavSatFix::SharedPtr msg) {
26 auto now = std::chrono::steady_clock::now();
27 double dt_ms =
28 last_navsat_recv_.time_since_epoch().count() == 0
29 ? 0.0
30 : std::chrono::duration<double, std::milli>(now - last_navsat_recv_)
31 .count();
32 last_navsat_recv_ = now;
33
34 std::ostringstream oss;
35 oss << std::fixed << std::setprecision(6);
36 oss << "\n========== NavSatFix ==========\n";
37 oss << "stamp=" << rclcpp::Time(msg->header.stamp).seconds() << "s\n";
38 oss << "frame=" << msg->header.frame_id << "\n";
39 oss << "recv_dt=" << std::setprecision(3) << dt_ms << "ms\n";
40 oss << std::setprecision(8);
41 oss << "latitude: " << msg->latitude << " deg\n";
42 oss << "longitude: " << msg->longitude << " deg\n";
43 oss << std::setprecision(3);
44 oss << "altitude: " << msg->altitude << " m\n";
45
46 const char *status_str = "UNKNOWN";
47 switch (msg->status.status) {
48 case sensor_msgs::msg::NavSatStatus::STATUS_NO_FIX:
49 status_str = "NO_FIX";
50 break;
51 case sensor_msgs::msg::NavSatStatus::STATUS_FIX:
52 status_str = "FIX";
53 break;
54 }
55 oss << "status: " << status_str << "\n";
56 oss << "service: " << msg->status.service << "\n";
57
58 oss << std::setprecision(4);
59 oss << "position_covariance: [";
60 for (size_t i = 0; i < 9; ++i) {
61 if (i > 0)
62 oss << ", ";
63 oss << msg->position_covariance[i];
64 }
65 oss << "]\n";
66 oss << "position_covariance_type: "
67 << static_cast<int>(msg->position_covariance_type);
68
69 RCLCPP_INFO(this->get_logger(), "%s", oss.str().c_str());
70 }
71
72 rclcpp::Subscription<sensor_msgs::msg::NavSatFix>::SharedPtr navsat_sub_;
73 std::chrono::steady_clock::time_point last_navsat_recv_{};
74};
75
76int main(int argc, char **argv) {
77 rclcpp::init(argc, argv);
78 rclcpp::spin(std::make_shared<GnssPrinter>());
79 rclcpp::shutdown();
80 return 0;
81}
Usage Instructions:
# Build
colcon build --packages-select examples
# Subscribe to GNSS data
ros2 run examples echo_gnss_data
Output Example:
[INFO] [gnss_printer]: Subscribing NavSatFix: /aima/hal/sensor/gnss
========== NavSatFix ==========
stamp=1234567890.123456s
frame=gnss
recv_dt=1000.000ms
latitude: 31.84537000 deg
longitude: 117.19882000 deg
altitude: 30.000 m
status: FIX
service: 1
position_covariance: [1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000]
position_covariance_type: 2
Notes:
Positioning status: NO_FIX (no fix), FIX (single point fix)
Indoors, status is usually NO_FIX; move to an open outdoor area and wait for a fix
6.2.35 Upper Body Control
Note
Switch to the corresponding mode before running. See Upper Body Control.
1// Upper body control example.
2//
3// Usage:
4// ros2 run examples upper_body_control <mode>
5//
6// mode:
7// head — head center pose, requires HEAD_ONLY mode
8// claw — claw half-open, requires UPPERBODY_REMOTE_SPLIT mode
9// joint — dexterous hand half-open, requires UPPERBODY_REMOTE_SPLIT mode
10// gesture — dexterous hand gesture 1, requires UPPERBODY_REMOTE_SPLIT mode
11//
12// Switch to the desired MC mode before running, e.g. for head:
13// ros2 run examples set_mc_action SD # → STAND_DEFAULT
14// ros2 run examples set_mc_action HO # → HEAD_ONLY
15// or for claw / joint / gesture:
16// ros2 run examples set_mc_action URS # → UPPERBODY_REMOTE_SPLIT
17
18#include <array>
19#include <chrono>
20#include <cstdint>
21#include <cstdio>
22#include <map>
23#include <memory>
24#include <string>
25#include <vector>
26
27#include "aimdk_msgs/msg/message_header.hpp"
28#include "aimdk_msgs/msg/upper_body_command_array.hpp"
29#include "rclcpp/rclcpp.hpp"
30
31using namespace std::chrono_literals;
32using Msg = aimdk_msgs::msg::UpperBodyCommandArray;
33
34// hand_sub_mode, head_pos, arm_pos, hand_pos
35struct ModeConfig {
36 uint8_t hand_sub_mode;
37 std::array<double, 2> head_pos;
38 std::array<double, 14> arm_pos;
39 std::vector<double> hand_pos;
40};
41
42static const std::map<std::string, ModeConfig> &modes() {
43 static const std::map<std::string, ModeConfig> kModes = {
44 // head_yaw=0, head_pitch=0 (center)
45 {"head",
46 {0,
47 {{0.0, 0.0}},
48 {{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
49 0.0}},
50 {}}},
51 // left_open=0.5, right_open=0.5
52 {"claw",
53 {Msg::HAND_CLAW_OPEN_CLOSE, // claw (1)
54 {{0.0, 0.0}},
55 {{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
56 0.0}},
57 {0.5, 0.5}}},
58 // left 10 joints + right 10 joints, in rad, all half-open
59 // (each joint at half its URDF range limit). Right is the mirror of left.
60 // Joint order: [thumb_roll, thumb_abad, thumb_mcp, index_abad, index_pip,
61 // middle_pip, ring_abad, ring_pip, pinky_abad, pinky_pip]
62 {"joint",
63 {Msg::HAND_DEXTEROUS_JOINT, // dexterous joint (2)
64 {{0.0, 0.0}},
65 {{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
66 0.0}},
67 {-0.6, 0.8, -0.4, 0.1, 0.7, 0.7, -0.1, 0.7, -0.1, 0.7,
68 0.6, -0.8, 0.4, -0.1, 0.7, 0.7, 0.1, 0.7, 0.1, 0.7}}},
69 // left_gesture=1, left_open=1.0, right_gesture=1, right_open=1.0
70 {"gesture",
71 {Msg::HAND_DEXTEROUS_GESTURE, // dexterous gesture (3)
72 {{0.0, 0.0}},
73 {{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
74 0.0}},
75 {1.0, 1.0, 1.0, 1.0}}},
76 };
77 return kModes;
78}
79
80class UpperBodyControlNode : public rclcpp::Node {
81public:
82 UpperBodyControlNode(const std::string &mode)
83 : Node("upper_body_control"), seq_(0) {
84 const auto &cfg = modes().at(mode);
85 hand_sub_mode_ = cfg.hand_sub_mode;
86 head_pos_ = cfg.head_pos;
87 arm_pos_ = cfg.arm_pos;
88 hand_pos_ = cfg.hand_pos;
89 pub_ = create_publisher<Msg>("/mc/upper_body_command", 10);
90 timer_ = create_wall_timer(20ms, [this]() { publish(); });
91 RCLCPP_INFO(get_logger(), "mode=%s hand_sub_mode=%d", mode.c_str(),
92 hand_sub_mode_);
93 }
94
95private:
96 void publish() {
97 Msg msg;
98 auto t = this->now();
99 msg.header.stamp.sec = static_cast<int32_t>(t.seconds());
100 msg.header.stamp.nanosec =
101 static_cast<uint32_t>(t.nanoseconds() % 1000000000);
102 msg.header.frame_id = "mc_upper_body";
103 msg.header.sequence = seq_++;
104 msg.source = "upper_body_example";
105 msg.hand_sub_mode = hand_sub_mode_;
106 msg.head_pos = head_pos_;
107 msg.arm_pos = arm_pos_;
108 msg.hand_pos = hand_pos_;
109 pub_->publish(msg);
110 }
111
112 rclcpp::Publisher<Msg>::SharedPtr pub_;
113 rclcpp::TimerBase::SharedPtr timer_;
114 uint32_t seq_;
115 uint8_t hand_sub_mode_;
116 std::array<double, 2> head_pos_;
117 std::array<double, 14> arm_pos_;
118 std::vector<double> hand_pos_;
119};
120
121int main(int argc, char **argv) {
122 rclcpp::init(argc, argv);
123
124 std::string mode;
125 if (argc >= 2)
126 mode = argv[1];
127
128 if (modes().find(mode) == modes().end()) {
129 std::string usage = "Usage: upper_body_control <";
130 bool first = true;
131 for (const auto &[k, _] : modes()) {
132 if (!first)
133 usage += "|";
134 usage += k;
135 first = false;
136 }
137 usage += ">";
138 std::printf("%s\n", usage.c_str());
139 rclcpp::shutdown();
140 return 1;
141 }
142
143 rclcpp::spin(std::make_shared<UpperBodyControlNode>(mode));
144 rclcpp::shutdown();
145 return 0;
146}
Usage Instructions:
colcon build --packages-select examples
# Head control (HEAD_ONLY mode)
ros2 run examples upper_body_control head
# Claw (gripper) control (UPPERBODY_REMOTE_SPLIT mode)
ros2 run examples upper_body_control claw
# Dexterous hand joints (UPPERBODY_REMOTE_SPLIT mode)
ros2 run examples upper_body_control joint
# Dexterous hand gesture (UPPERBODY_REMOTE_SPLIT mode)
ros2 run examples upper_body_control gesture
6.2.36 Subscribe to HDS Diagnostics / Query Historical Events
Subscribe to /aima/hds/diag_code_list, /aima/hds/alert_code_list, and /aima/hds/post_proc/signal topics to receive diagnostic codes, alert codes, and post-processing signals in real time; also supports calling the GetHdsEventHistory service to query historical events. For the full list of diagnostic codes, see Diagnostic Code Reference.
1// HDS Diagnostics Tool — Subscribe to HDS topics or query history events.
2//
3// See docs/cn/example/Cpp.md section "Subscribe HDS diagnostics /
4// query history events" for full usage and field descriptions.
5
6#include <chrono>
7#include <cstdint>
8#include <functional>
9#include <iomanip>
10#include <iostream>
11#include <memory>
12#include <sstream>
13#include <string>
14#include <vector>
15
16#include <rclcpp/rclcpp.hpp>
17
18#include "aimdk_msgs/msg/alert_code_array.hpp"
19#include "aimdk_msgs/msg/diagnostic_info_array.hpp"
20#include "aimdk_msgs/msg/diagnostic_type.hpp"
21#include "aimdk_msgs/msg/hds_event_query.hpp"
22#include "aimdk_msgs/msg/post_proc_sig_array.hpp"
23#include "aimdk_msgs/srv/get_hds_event_history.hpp"
24
25using namespace std::chrono_literals;
26
27namespace {
28
29std::string DiagTypeName(int32_t value) {
30 switch (value) {
31 case aimdk_msgs::msg::DiagnosticType::NORMAL:
32 return "NORMAL";
33 case aimdk_msgs::msg::DiagnosticType::TRIGGER_APPEAR:
34 return "APPEAR";
35 case aimdk_msgs::msg::DiagnosticType::TRIGGER_DISAPPEAR:
36 return "DISAPPEAR";
37 default:
38 return "UNKNOWN(" + std::to_string(value) + ")";
39 }
40}
41
42std::string StateName(uint8_t value) {
43 switch (value) {
44 case aimdk_msgs::msg::HdsEventQuery::STATE_ALL:
45 return "ALL";
46 case aimdk_msgs::msg::HdsEventQuery::STATE_ACTIVE:
47 return "ACTIVE";
48 case aimdk_msgs::msg::HdsEventQuery::STATE_RECOVERED:
49 return "RECOVERED";
50 default:
51 return "UNKNOWN";
52 }
53}
54
55std::string HexCode(uint64_t code, int width = 10) {
56 std::ostringstream oss;
57 oss << "0x" << std::hex << std::uppercase << std::setw(width)
58 << std::setfill('0') << code;
59 return oss.str();
60}
61
62uint64_t ParseHex(const std::string &s) { return std::stoull(s, nullptr, 0); }
63
64// ---------- echo mode ----------
65
66class HdsDiagnosticsEcho : public rclcpp::Node {
67public:
68 HdsDiagnosticsEcho() : Node("hds_diagnostics_echo") {
69 // HDS publishers use BEST_EFFORT; match it to receive messages.
70 auto qos = rclcpp::QoS(10).best_effort().durability_volatile();
71
72 sub_diag_ = create_subscription<aimdk_msgs::msg::DiagnosticInfoArray>(
73 "/aima/hds/diag_code_list", qos,
74 std::bind(&HdsDiagnosticsEcho::OnDiagnostics, this,
75 std::placeholders::_1));
76
77 sub_alert_ = create_subscription<aimdk_msgs::msg::AlertCodeArray>(
78 "/aima/hds/alert_code_list", qos,
79 std::bind(&HdsDiagnosticsEcho::OnAlerts, this, std::placeholders::_1));
80
81 sub_post_ = create_subscription<aimdk_msgs::msg::PostProcSigArray>(
82 "/aima/hds/post_proc/signal", qos,
83 std::bind(&HdsDiagnosticsEcho::OnPostProc, this,
84 std::placeholders::_1));
85
86 RCLCPP_INFO(get_logger(), "Subscribed: diag_code_list / alert_code_list / "
87 "post_proc/signal");
88 }
89
90private:
91 void
92 OnDiagnostics(const aimdk_msgs::msg::DiagnosticInfoArray::SharedPtr msg) {
93 if (msg->diagnostics.empty())
94 return;
95 std::ostringstream oss;
96 oss << "[Diagnostics] count=" << msg->diagnostics.size();
97 for (const auto &d : msg->diagnostics) {
98 oss << "\n diag_code=" << HexCode(d.diag_code)
99 << " type=" << DiagTypeName(d.type.value)
100 << " module_id=" << static_cast<int>(d.module_id)
101 << " ts=" << d.timestamp << " info=\"" << d.info << "\"";
102 }
103 RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
104 }
105
106 void OnAlerts(const aimdk_msgs::msg::AlertCodeArray::SharedPtr msg) {
107 if (msg->list.empty())
108 return;
109 std::ostringstream oss;
110 oss << "[Alerts] count=" << msg->list.size();
111 for (const auto &a : msg->list) {
112 oss << "\n code=\"" << a.code << "\" ts=" << a.timestamp
113 << " first_ts=" << a.first_timestamp;
114 }
115 RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
116 }
117
118 void OnPostProc(const aimdk_msgs::msg::PostProcSigArray::SharedPtr msg) {
119 if (msg->post_proc_sig.empty())
120 return;
121 std::ostringstream oss;
122 oss << "[PostProc] count=" << msg->post_proc_sig.size();
123 for (const auto &p : msg->post_proc_sig) {
124 const auto &s = p.sig_set;
125 oss << "\n diag_code=" << HexCode(p.diag_code) << " ts=" << p.timestamp
126 << " robot=" << s.robot_sig
127 << " motion=" << static_cast<int>(s.motion_sig)
128 << " sound=" << static_cast<int>(s.sound_sig)
129 << " led=" << static_cast<int>(s.led_sig)
130 << " facial=" << static_cast<int>(s.facial_sig)
131 << " funcdisable=" << HexCode(s.funcdisable_sig, 16);
132 }
133 RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
134 }
135
136 rclcpp::Subscription<aimdk_msgs::msg::DiagnosticInfoArray>::SharedPtr
137 sub_diag_;
138 rclcpp::Subscription<aimdk_msgs::msg::AlertCodeArray>::SharedPtr sub_alert_;
139 rclcpp::Subscription<aimdk_msgs::msg::PostProcSigArray>::SharedPtr sub_post_;
140};
141
142int RunEcho(int argc, char **argv) {
143 rclcpp::init(argc, argv);
144 rclcpp::spin(std::make_shared<HdsDiagnosticsEcho>());
145 rclcpp::shutdown();
146 return 0;
147}
148
149// ---------- history mode ----------
150
151struct HistoryArgs {
152 uint64_t start_ns = 0;
153 uint64_t end_ns = 0;
154 std::vector<uint64_t> diag_codes;
155 std::vector<std::string> alert_codes;
156 uint8_t state = aimdk_msgs::msg::HdsEventQuery::STATE_ALL;
157 bool desc = false;
158 uint32_t offset = 0;
159 uint32_t limit = 20;
160};
161
162bool ParseHistoryArgs(int argc, char **argv, HistoryArgs *args) {
163 // argv[0] = exe, argv[1] = "history", real options start at 2.
164 for (int i = 2; i < argc; ++i) {
165 std::string a = argv[i];
166 auto need_value = [&](const char *flag) -> const char * {
167 if (i + 1 >= argc) {
168 std::cerr << "Missing value for " << flag << std::endl;
169 return nullptr;
170 }
171 return argv[++i];
172 };
173
174 if (a == "--start-ns") {
175 const char *v = need_value("--start-ns");
176 if (!v)
177 return false;
178 args->start_ns = std::stoull(v);
179 } else if (a == "--end-ns") {
180 const char *v = need_value("--end-ns");
181 if (!v)
182 return false;
183 args->end_ns = std::stoull(v);
184 } else if (a == "--state") {
185 const char *v = need_value("--state");
186 if (!v)
187 return false;
188 args->state = static_cast<uint8_t>(std::stoi(v));
189 } else if (a == "--offset") {
190 const char *v = need_value("--offset");
191 if (!v)
192 return false;
193 args->offset = static_cast<uint32_t>(std::stoul(v));
194 } else if (a == "--limit") {
195 const char *v = need_value("--limit");
196 if (!v)
197 return false;
198 args->limit = static_cast<uint32_t>(std::stoul(v));
199 } else if (a == "--desc") {
200 args->desc = true;
201 } else if (a == "--diag-codes") {
202 while (i + 1 < argc && std::string(argv[i + 1]).rfind("--", 0) != 0) {
203 args->diag_codes.push_back(ParseHex(argv[++i]));
204 }
205 } else if (a == "--alert-codes") {
206 while (i + 1 < argc && std::string(argv[i + 1]).rfind("--", 0) != 0) {
207 args->alert_codes.emplace_back(argv[++i]);
208 }
209 } else {
210 std::cerr << "Unknown option: " << a << std::endl;
211 return false;
212 }
213 }
214 return true;
215}
216
217void PrintRecords(
218 const aimdk_msgs::srv::GetHdsEventHistory::Response &response) {
219 std::cout << "total_count = " << response.total_count
220 << ", returned = " << response.records.size()
221 << ", has_more = " << (response.has_more ? "true" : "false")
222 << std::endl;
223 if (response.records.empty())
224 return;
225
226 std::string sep(80, '-');
227 std::cout << sep << std::endl;
228 for (const auto &r : response.records) {
229 double duration_s = static_cast<double>(r.duration_ns) / 1e9;
230 std::cout << "id=" << r.id << " diag=" << HexCode(r.diag_code)
231 << " alert=\"" << r.alert_code << "\"" << std::endl;
232 std::cout << " first_ts=" << r.first_timestamp
233 << " last_ts=" << r.last_timestamp << std::endl;
234 std::cout << " recovered=" << (r.recovered ? "YES" : "NO")
235 << " recover_ts=" << r.recover_timestamp
236 << " duration=" << std::fixed << std::setprecision(3)
237 << duration_s << "s" << std::endl;
238 if (!r.alert_location.empty()) {
239 std::cout << " location=\"" << r.alert_location << "\"" << std::endl;
240 }
241 std::cout << sep << std::endl;
242 }
243}
244
245int RunHistory(int argc, char **argv) {
246 HistoryArgs args;
247 if (!ParseHistoryArgs(argc, argv, &args))
248 return 1;
249
250 rclcpp::init(argc, argv);
251 auto node = std::make_shared<rclcpp::Node>("hds_history_client");
252 auto client = node->create_client<aimdk_msgs::srv::GetHdsEventHistory>(
253 "/aimdk_5Fmsgs/srv/GetHdsEventHistory");
254
255 if (!client->wait_for_service(5s)) {
256 RCLCPP_ERROR(node->get_logger(),
257 "Service /aimdk_5Fmsgs/srv/GetHdsEventHistory not available");
258 rclcpp::shutdown();
259 return 1;
260 }
261
262 auto request =
263 std::make_shared<aimdk_msgs::srv::GetHdsEventHistory::Request>();
264 auto now = node->now();
265 request->header.stamp.sec = now.seconds();
266 request->header.stamp.nanosec = now.nanoseconds() % 1000000000;
267 request->query.start_time = args.start_ns;
268 request->query.end_time = args.end_ns;
269 request->query.diag_codes = args.diag_codes;
270 request->query.alert_codes = args.alert_codes;
271 request->query.state = args.state;
272 request->query.order_desc = args.desc;
273 request->query.offset = args.offset;
274 request->query.limit = args.limit;
275
276 RCLCPP_INFO(node->get_logger(),
277 "Querying history: state=%s, offset=%u, limit=%u, desc=%s",
278 StateName(args.state).c_str(), args.offset, args.limit,
279 args.desc ? "true" : "false");
280
281 auto future = client->async_send_request(request);
282 if (rclcpp::spin_until_future_complete(node, future, 10s) !=
283 rclcpp::FutureReturnCode::SUCCESS) {
284 RCLCPP_ERROR(node->get_logger(), "Service call timeout");
285 rclcpp::shutdown();
286 return 1;
287 }
288
289 PrintRecords(*future.get());
290 rclcpp::shutdown();
291 return 0;
292}
293
294void PrintUsage(const char *prog) {
295 std::cout << "Usage:\n"
296 << " " << prog << " echo\n"
297 << " " << prog
298 << " history [--state N] [--limit N] [--offset N] [--desc]\n"
299 << " [--start-ns N] [--end-ns N]\n"
300 << " [--diag-codes 0xAAAA 0xBBBB ...]\n"
301 << " [--alert-codes \"code1\" \"code2\" ...]\n";
302}
303
304} // namespace
305
306int main(int argc, char **argv) {
307 if (argc < 2) {
308 PrintUsage(argv[0]);
309 return 1;
310 }
311
312 std::string mode = argv[1];
313 if (mode == "echo")
314 return RunEcho(argc, argv);
315 if (mode == "history")
316 return RunHistory(argc, argv);
317
318 PrintUsage(argv[0]);
319 return 1;
320}
Usage Instructions
Real-time subscription (echo):
# Subscribe to all three topics, print diagnostics, alerts, and post-proc signals in real time
ros2 run examples hds_diagnostics echo
Topic |
Message Type |
|---|---|
|
|
|
|
|
|
Query historical events (history) — all filter options correspond to HdsEventQuery fields. Usage examples:
# Query latest 20 records (default limit=20, ascending)
ros2 run examples hds_diagnostics history
# Query by time range (start_time / end_time)
ros2 run examples hds_diagnostics history \
--start-ns 1716000000000000000 \
--end-ns 1716100000000000000
# Only active alerts (state = STATE_ACTIVE)
ros2 run examples hds_diagnostics history --state 1
# Filter by diagnostic codes (diag_codes, hex supported)
ros2 run examples hds_diagnostics history --diag-codes 0x6200000001 0x6200000002
# Filter by alert codes (alert_codes)
ros2 run examples hds_diagnostics history --alert-codes MOTOR_OVERHEAT COMM_LOST
# Pagination + descending (limit / offset / order_desc, newest first)
ros2 run examples hds_diagnostics history --limit 50 --offset 0 --desc
# Combined: only recovered, 10 per page, newest first
ros2 run examples hds_diagnostics history --state 2 --limit 10 --desc
Response fields correspond to HdsEventRecord.
echo Mode Output Example
[INFO] [hds_diagnostics_echo]: Subscribed: diag_code_list / alert_code_list / post_proc/signal
[INFO] [hds_diagnostics_echo]: [Diagnostics] count=1
diag_code=0x6200000007 type=APPEAR module_id=98 ts=1770125762202825297 info="Unable to get robot localization"
[INFO] [hds_diagnostics_echo]: [Alerts] count=1
code="LOCALIZATION_LOST" ts=1770125762202825297 first_ts=1770125762202825297
[INFO] [hds_diagnostics_echo]: [PostProc] count=1
diag_code=0x6200000007 ts=1770125762202825297 robot=1 motion=2 sound=1 led=3 facial=0 funcdisable=0x0000000000000004
history Mode Output Example
[INFO] [hds_history_client]: Querying history: state=ALL, offset=0, limit=20, desc=true
total_count = 5, returned = 5, has_more = false
--------------------------------------------------------------------------------
id=42 diag=0x6200000007 alert="LOCALIZATION_LOST"
first_ts=1770125762202825297 last_ts=1770125765310145000
recovered=YES recover_ts=1770125770000000000 duration=7.797s
--------------------------------------------------------------------------------
Interface Reference
Topics:
/aima/hds/diag_code_list,/aima/hds/alert_code_list,/aima/hds/post_proc/signalService:
/aimdk_5Fmsgs/srv/GetHdsEventHistoryMessages:
aimdk_msgs/msg/DiagnosticInfoArray,aimdk_msgs/msg/AlertCodeArray,aimdk_msgs/msg/PostProcSigArray