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 directory of the extracted SDK and execute the following commands
source /opt/ros/humble/setup.bash colcon build source install/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)
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) {
128 migrate_retry_count_ = 0;
129 ++migrate_request_id_;
130 RCLCPP_INFO(this->get_logger(),
131 "Migration request accepted, starting to monitor "
132 "state...");
133 start_state_monitoring();
134 } else {
135 ++migrate_request_id_;
136 RCLCPP_ERROR(this->get_logger(),
137 "Migration request failed with status: %d, message: "
138 "%s",
139 response->header.status.value,
140 response->header.message.c_str());
141 rclcpp::shutdown();
142 }
143 } catch (const std::exception &e) {
144 ++migrate_request_id_;
145 RCLCPP_ERROR(this->get_logger(),
146 "Migration service call failed: %s", e.what());
147 rclcpp::shutdown();
148 }
149 });
150 }
151
152 /**
153 * @brief Start monitoring system state
154 */
155 void start_state_monitoring() {
156 RCLCPP_INFO(this->get_logger(), "Starting to monitor system state...");
157
158 // Create a timer to periodically check system state
159 monitor_timer_ = this->create_wall_timer(
160 1s, std::bind(&SystemStateMigrator::check_system_state, this));
161 }
162
163 /**
164 * @brief Check current system state with retry
165 */
166 void check_system_state() {
167 if (get_state_pending_) {
168 return;
169 }
170 if (get_state_retry_count_ >= max_get_state_retries_) {
171 RCLCPP_ERROR(this->get_logger(),
172 "Get system state failed after %d attempts, will retry on "
173 "next monitor cycle",
174 max_get_state_retries_);
175 get_state_retry_count_ = 0;
176 return;
177 }
178
179 auto request = std::make_shared<aimdk_msgs::srv::GetSystemState::Request>();
180
181 auto now = this->now();
182 request->header.header.stamp.sec = now.seconds();
183 request->header.header.stamp.nanosec = now.nanoseconds() % 1000000000;
184
185 get_state_retry_count_++;
186 get_state_pending_ = true;
187 uint64_t req_id = ++get_state_request_id_;
188
189 get_state_timeout_timer_ = this->create_wall_timer(10s, [this, req_id]() {
190 if (req_id != get_state_request_id_) {
191 return;
192 }
193 get_state_timeout_timer_.reset();
194 get_state_pending_ = false;
195 RCLCPP_WARN(this->get_logger(),
196 "GetSystemState request timeout (attempt %d/%d), "
197 "retrying...",
198 get_state_retry_count_, max_get_state_retries_);
199 check_system_state();
200 });
201
202 auto future = get_state_client_->async_send_request(
203 request,
204 [this,
205 req_id](rclcpp::Client<aimdk_msgs::srv::GetSystemState>::SharedFuture
206 future) {
207 if (req_id != get_state_request_id_) {
208 return;
209 }
210 if (get_state_timeout_timer_) {
211 get_state_timeout_timer_->cancel();
212 get_state_timeout_timer_.reset();
213 }
214 get_state_pending_ = false;
215
216 try {
217 auto response = future.get();
218 get_state_retry_count_ = 0;
219 ++get_state_request_id_;
220
221 RCLCPP_INFO(
222 this->get_logger(), "Current State: %s, System Status: %u",
223 response->cur_state.c_str(), response->curr_status.value);
224
225 if (is_migration_complete(response)) {
226 RCLCPP_INFO(this->get_logger(),
227 "Migration to %s completed successfully!",
228 target_state_.c_str());
229 monitor_timer_->cancel();
230 rclcpp::shutdown();
231 } else {
232 RCLCPP_INFO(this->get_logger(),
233 "Migration in progress, will check again in 1 "
234 "second...");
235 }
236 } catch (const std::exception &e) {
237 RCLCPP_ERROR(this->get_logger(),
238 "GetSystemState service call failed: %s", e.what());
239 }
240 });
241 }
242
243 /**
244 * @brief Check if migration to target state is complete
245 * @param response Response from GetSystemState service
246 * @return true if migration is complete, false otherwise
247 */
248 bool is_migration_complete(
249 const aimdk_msgs::srv::GetSystemState::Response::SharedPtr response) {
250 // Convert current state and target state to lowercase for case-insensitive
251 // comparison
252 std::string current_state_lower = response->cur_state;
253 std::transform(current_state_lower.begin(), current_state_lower.end(),
254 current_state_lower.begin(), ::tolower);
255
256 std::string target_state_lower = target_state_;
257 std::transform(target_state_lower.begin(), target_state_lower.end(),
258 target_state_lower.begin(), ::tolower);
259
260 // Check conditions:
261 // 1. Current state equals target state (case-insensitive)
262 // 2. System status equals 1 (IN_READY)
263 bool state_match = (current_state_lower == target_state_lower);
264 bool status_match = (response->curr_status.value ==
265 aimdk_msgs::msg::SystemStatus::IN_READY);
266
267 if (state_match && status_match) {
268 return true;
269 }
270
271 // Log detailed information if not complete
272 if (!state_match) {
273 RCLCPP_INFO(this->get_logger(),
274 "State mismatch: current='%s', expected='%s'",
275 response->cur_state.c_str(), target_state_.c_str());
276 }
277 if (!status_match) {
278 RCLCPP_INFO(this->get_logger(),
279 "Status mismatch: current= %u, expected=1 (IN_READY)",
280 response->curr_status.value);
281 }
282 if (state_match && !state_match) {
283 RCLCPP_ERROR(this->get_logger(), "State Migrate Fail");
284 exit(0);
285 }
286
287 return false;
288 }
289
290 // Target state to migrate to
291 std::string target_state_;
292
293 // Service clients
294 rclcpp::Client<aimdk_msgs::srv::MigrateSystemState>::SharedPtr
295 migrate_client_;
296 rclcpp::Client<aimdk_msgs::srv::GetSystemState>::SharedPtr get_state_client_;
297
298 // Timers
299 rclcpp::TimerBase::SharedPtr check_timer_;
300 rclcpp::TimerBase::SharedPtr monitor_timer_;
301
302 // Retry counters and timeout timers for migration
303 int migrate_retry_count_ = 0;
304 const int max_migrate_retries_ = 5;
305 uint64_t migrate_request_id_ = 0;
306 bool migrate_pending_ = false;
307 rclcpp::TimerBase::SharedPtr migrate_timeout_timer_;
308
309 // Retry counters and timeout timers for get state
310 int get_state_retry_count_ = 0;
311 const int max_get_state_retries_ = 3;
312 uint64_t get_state_request_id_ = 0;
313 bool get_state_pending_ = false;
314 rclcpp::TimerBase::SharedPtr get_state_timeout_timer_;
315};
316
317/**
318 * @brief Print usage information with detailed mode descriptions
319 */
320void print_usage(const char *program_name) {
321 std::cout << "Usage: " << program_name << " <target_state>" << std::endl;
322 std::cout << std::endl;
323 std::cout << "Available target states:" << std::endl;
324 std::cout << " " << program_name << " Ready" << std::endl;
325 std::cout << " " << program_name << " Develop_Nav" << std::endl;
326 std::cout << " " << program_name << " Develop_Audio_Linux" << std::endl;
327 std::cout << " " << program_name << " Develop_Audio_ROS" << std::endl;
328 std::cout << " " << program_name << " Develop_MC" << std::endl;
329 std::cout << std::endl;
330
331 std::cout << "Mode Descriptions:" << std::endl;
332 std::cout << "==================" << std::endl;
333
334 // Ready mode description
335 std::cout << "1. Ready - System Default Mode" << std::endl;
336 std::cout
337 << " • Description: System enters this mode by default after startup"
338 << std::endl;
339 std::cout << " • Purpose: Normal operation mode, ready for general tasks"
340 << std::endl;
341 std::cout
342 << " • Use case: Return to normal operation after development work"
343 << std::endl;
344 std::cout << std::endl;
345
346 // Develop_Nav mode description
347 std::cout << "2. Develop_Nav - Navigation Development Mode" << std::endl;
348 std::cout
349 << " • Description: Special mode for navigation system development"
350 << std::endl;
351 std::cout << " • Purpose: Enables navigation-related debugging and testing"
352 << std::endl;
353 std::cout << " • Use case: When developing or testing navigation "
354 "algorithms, SLAM, path planning"
355 << std::endl;
356 std::cout << " • Features: May provide access to raw sensor data, "
357 "navigation debug topics"
358 << std::endl;
359 std::cout << std::endl;
360
361 // Develop_Audio_Linux mode description
362 std::cout << "3. Develop_Audio_Linux - System-Level Audio Development Mode"
363 << std::endl;
364 std::cout << " • Description: Mode for low-level audio system development"
365 << std::endl;
366 std::cout << " • Purpose: Access to system audio streams at Linux level"
367 << std::endl;
368 std::cout << " • Use case: When developing audio drivers, audio processing "
369 "at system level"
370 << std::endl;
371 std::cout << " • Features: Direct access to audio hardware, "
372 "ALSA/PulseAudio interfaces"
373 << std::endl;
374 std::cout << std::endl;
375
376 // Develop_Audio_ROS mode description
377 std::cout << "4. Develop_Audio_ROS - ROS Audio Development Mode" << std::endl;
378 std::cout
379 << " • Description: Mode for ROS-based audio application development"
380 << std::endl;
381 std::cout << " • Purpose: Access to audio data through ROS topics"
382 << std::endl;
383 std::cout << " • Use case: When developing ROS nodes that process audio, "
384 "speech recognition"
385 << std::endl;
386 std::cout << " • Features: Audio data published as ROS topics, ROS message "
387 "interfaces"
388 << std::endl;
389 std::cout << std::endl;
390
391 // Develop_MC mode description
392 std::cout << "5. Develop_MC - Motion Control Development Mode" << std::endl;
393 std::cout << " • Description: Mode for motion control system development"
394 << std::endl;
395 std::cout
396 << " • Purpose: Enables direct control and testing of motion systems"
397 << std::endl;
398 std::cout << " • Use case: When developing motor control algorithms, "
399 "testing motion hardware"
400 << std::endl;
401 std::cout << " • Features: Low-level motor control access, motion system "
402 "debugging tools"
403 << std::endl;
404 std::cout << std::endl;
405
406 std::cout << "Program Workflow:" << std::endl;
407 std::cout << "=================" << std::endl;
408 std::cout
409 << "1. Calls MigrateSystemState service with the specified target state"
410 << std::endl;
411 std::cout << "2. Continuously monitors GetSystemState service every second"
412 << std::endl;
413 std::cout << "3. Exits when both conditions are met:" << std::endl;
414 std::cout << " • Current State equals target state (case-insensitive)"
415 << std::endl;
416 std::cout << " • System Status equals 1 (IN_READY)" << std::endl;
417 std::cout << std::endl;
418 std::cout << "Note: The program will automatically exit when migration is "
419 "complete or on error."
420 << std::endl;
421}
422
423/**
424 * @brief Validate if the target state is a known development mode
425 * @param state The state to validate
426 * @return true if state is valid, false otherwise
427 */
428bool is_valid_state(const std::string &state) {
429 // List of known valid states
430 const std::vector<std::string> valid_states = {
431 "Ready", "Develop_Nav", "Develop_Audio_Linux", "Develop_Audio_ROS",
432 "Develop_MC"};
433
434 // Convert input to lowercase for case-insensitive comparison
435 std::string state_lower = state;
436 std::transform(state_lower.begin(), state_lower.end(), state_lower.begin(),
437 ::tolower);
438
439 // Check if state is in the list of valid states
440 for (const auto &valid_state : valid_states) {
441 std::string valid_state_lower = valid_state;
442 std::transform(valid_state_lower.begin(), valid_state_lower.end(),
443 valid_state_lower.begin(), ::tolower);
444
445 if (state_lower == valid_state_lower) {
446 return true;
447 }
448 }
449
450 return false;
451}
452
453/**
454 * @brief Main function
455 * @param argc Argument count
456 * @param argv Argument vector
457 * @return Exit code
458 */
459int main(int argc, char **argv) {
460 // Check command line arguments
461 if (argc != 2) {
462 print_usage(argv[0]);
463 return 1;
464 }
465
466 // Get target state from command line argument
467 std::string target_state = argv[1];
468
469 // Validate target state
470 if (target_state.empty()) {
471 std::cerr << "Error: Target state cannot be empty" << std::endl;
472 print_usage(argv[0]);
473 return 1;
474 }
475
476 // Optional: Validate against known states
477 if (!is_valid_state(target_state)) {
478 std::cout << "Warning: '" << target_state
479 << "' is not in the list of known states." << std::endl;
480 std::cout << "The program will still attempt to migrate to this state."
481 << std::endl;
482 std::cout << "Continue? (y/n): ";
483
484 std::string response;
485 std::getline(std::cin, response);
486
487 if (response != "y" && response != "Y") {
488 std::cout << "Operation cancelled." << std::endl;
489 return 0;
490 }
491 }
492
493 // Initialize ROS2
494 rclcpp::init(argc, argv);
495
496 // Create migrator node with target state
497 auto node = std::make_shared<SystemStateMigrator>(target_state);
498
499 // Create single-threaded executor
500 auto executor = std::make_shared<rclcpp::executors::SingleThreadedExecutor>();
501
502 // Add node to executor
503 executor->add_node(node);
504
505 // Run executor (blocking call)
506 executor->spin();
507
508 // Cleanup
509 executor->remove_node(node);
510 rclcpp::shutdown();
511
512 return 0;
513}
Usage Instructions
ros2 run examples get_mc_action
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...
[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] [1764066631.021247791] [get_mc_action_client]: Current robot mode:
[INFO] [1764066631.021832667] [get_mc_action_client]: Mode name: PASSIVE_DEFAULT
[INFO] [1764066631.022396136] [get_mc_action_client]: Mode status: 100
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 digram, other transitions would 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 <memory>
11#include <signal.h>
12#include <unordered_map>
13#include <vector>
14
15// Global variable used for signal handling
16std::shared_ptr<rclcpp::Node> g_node = nullptr;
17
18// Signal handler function
19void signal_handler(int signal) {
20 if (g_node) {
21 RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
22 signal);
23 g_node.reset();
24 }
25 rclcpp::shutdown();
26 exit(signal);
27}
28
29class SetMcActionClient : public rclcpp::Node {
30public:
31 SetMcActionClient() : Node("set_mc_action_client") {
32
33 client_ = this->create_client<aimdk_msgs::srv::SetMcAction>(
34 "/aimdk_5Fmsgs/srv/SetMcAction");
35 RCLCPP_INFO(this->get_logger(), "✅ SetMcAction client node created.");
36
37 // Wait for the service to become available
38 while (!client_->wait_for_service(std::chrono::seconds(2))) {
39 RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
40 }
41 RCLCPP_INFO(this->get_logger(),
42 "🟢 Service available, ready to send request.");
43 }
44
45 bool send_request(std::string &action_name) {
46 try {
47 auto request = std::make_shared<aimdk_msgs::srv::SetMcAction::Request>();
48 request->header = aimdk_msgs::msg::RequestHeader();
49
50 request->source = "node";
51
52 // Set robot mode
53 aimdk_msgs::msg::McActionCommand command;
54 command.action_desc = action_name;
55 request->command = command;
56
57 RCLCPP_INFO(this->get_logger(), "📨 Sending request to set robot mode: %s",
58 action_name.c_str());
59
60 // Set Service Call Timeout
61 const std::chrono::milliseconds timeout(250);
62 for (int i = 0; i < 8; i++) {
63 request->header.stamp = this->now();
64 auto future = client_->async_send_request(request);
65 auto retcode = rclcpp::spin_until_future_complete(shared_from_this(),
66 future, timeout);
67 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
68 // retry as remote peer is NOT handled well by ROS
69 RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
70 continue;
71 }
72 // future.done
73 auto response = future.get();
74 if (response->response.status.value ==
75 aimdk_msgs::msg::CommonState::SUCCESS) {
76 RCLCPP_INFO(this->get_logger(), "✅ Robot mode set successfully.");
77 return true;
78 } else {
79 RCLCPP_ERROR(this->get_logger(), "❌ Failed to set robot mode: %s",
80 response->response.message.c_str());
81 return false;
82 }
83 }
84 RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
85 return false;
86 } catch (const std::exception &e) {
87 RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
88 return false;
89 }
90 }
91
92private:
93 rclcpp::Client<aimdk_msgs::srv::SetMcAction>::SharedPtr client_;
94};
95
96static std::unordered_map<std::string, std::vector<std::string>> g_action_info =
97 {
98 {"PASSIVE_DEFAULT", {"PD", "joints with zero torque"}},
99 {"DAMPING_DEFAULT", {"DD", "joints in damping mode"}},
100 {"JOINT_DEFAULT", {"JD", "Position Control Stand (joints locked)"}},
101 {"STAND_DEFAULT", {"SD", "Stable Stand (auto-balance)"}},
102 {"LOCOMOTION_DEFAULT", {"LD", "locomotion mode (walk or run)"}},
103};
104
105int main(int argc, char *argv[]) {
106 try {
107 rclcpp::init(argc, argv);
108
109 // Set up signal handlers
110 signal(SIGINT, signal_handler);
111 signal(SIGTERM, signal_handler);
112
113 // Create node
114 g_node = std::make_shared<SetMcActionClient>();
115 auto client = std::dynamic_pointer_cast<SetMcActionClient>(g_node);
116
117 if (client) {
118 std::unordered_map<std::string, std::string> choices;
119 std::string motion;
120
121 // Prefer command-line argument; otherwise prompt user
122 if (argc > 1) {
123 motion = argv[1];
124 RCLCPP_INFO(g_node->get_logger(),
125 "Using abbr of motion mode from cmdline: %s", argv[1]);
126 } else {
127 std::cout << std::left << std::setw(4) << "abbr"
128 << " - " << std::setw(20) << "robot mode"
129 << " : "
130 << "description" << std::endl;
131 for (auto &it : g_action_info) {
132 std::cout << std::left << std::setw(4) << it.second[0] << " - "
133 << std::setw(20) << it.first << " : " << it.second[1]
134 << std::endl;
135 }
136 std::cout << "Enter abbr of motion mode:";
137 std::cin >> motion;
138 }
139 for (auto &it : g_action_info) {
140 choices[it.second[0]] = it.first;
141 }
142
143 auto m = choices.find(motion);
144 if (m != choices.end()) {
145 auto &action_name = m->second;
146 client->send_request(action_name);
147 } else {
148 RCLCPP_ERROR(g_node->get_logger(), "Invalid abbr of robot mode: %s",
149 motion.c_str());
150 }
151 }
152
153 // Clean up resources
154 g_node.reset();
155 rclcpp::shutdown();
156
157 return 0;
158 } catch (const std::exception &e) {
159 RCLCPP_ERROR(rclcpp::get_logger("main"),
160 "Program exited with exception: %s", e.what());
161 return 1;
162 }
163}
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’s feet are on the ground before switching to the
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_preset_motion.hpp"
6#include "aimdk_msgs/msg/request_header.hpp"
7#include "aimdk_msgs/srv/set_mc_preset_motion.hpp"
8#include "rclcpp/rclcpp.hpp"
9#include <chrono>
10#include <memory>
11#include <signal.h>
12
13std::shared_ptr<rclcpp::Node> g_node = nullptr;
14
15void signal_handler(int signal) {
16 if (g_node) {
17 RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
18 signal);
19 g_node.reset();
20 }
21 rclcpp::shutdown();
22 exit(signal);
23}
24
25class PresetMotionClient : public rclcpp::Node {
26public:
27 PresetMotionClient() : Node("preset_motion_client") {
28 const std::chrono::seconds timeout(8);
29
30 client_ = this->create_client<aimdk_msgs::srv::SetMcPresetMotion>(
31 "/aimdk_5Fmsgs/srv/SetMcPresetMotion");
32
33 RCLCPP_INFO(this->get_logger(), "✅ SetMcPresetMotion client node created.");
34
35 // Wait for the service to become available
36 while (!client_->wait_for_service(std::chrono::seconds(2))) {
37 RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
38 }
39 RCLCPP_INFO(this->get_logger(),
40 "🟢 Service available, ready to send request.");
41 }
42
43 bool send_request(int area_id, int motion_id) {
44 try {
45 auto request =
46 std::make_shared<aimdk_msgs::srv::SetMcPresetMotion::Request>();
47 request->header = aimdk_msgs::msg::RequestHeader();
48
49 aimdk_msgs::msg::McPresetMotion motion;
50 aimdk_msgs::msg::McControlArea area;
51
52 motion.value = motion_id; // Preset motion ID
53 area.value = area_id; // Control area ID
54 request->motion = motion;
55 request->area = area;
56 request->interrupt = false; // Not interrupt current motion
57
58 RCLCPP_INFO(this->get_logger(),
59 "📨 Sending request to set preset motion: motion=%d, area=%d",
60 motion_id, area_id);
61
62 const std::chrono::milliseconds timeout(250);
63 for (int i = 0; i < 8; i++) {
64 request->header.stamp = this->now();
65 auto future = client_->async_send_request(request);
66 auto retcode = rclcpp::spin_until_future_complete(shared_from_this(),
67 future, timeout);
68 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
69 // retry as remote peer is NOT handled well by ROS
70 RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
71 continue;
72 }
73 // future.done
74 auto response = future.get();
75 if (response->response.header.code == 0) {
76 RCLCPP_INFO(this->get_logger(),
77 "✅ Preset motion set successfully: %lu",
78 response->response.task_id);
79 return true;
80 } else if (response->response.state.value ==
81 aimdk_msgs::msg::CommonState::RUNNING) {
82 RCLCPP_INFO(this->get_logger(), "⏳ Preset motion executing: %lu",
83 response->response.task_id);
84 return true;
85 } else {
86 RCLCPP_WARN(this->get_logger(), "❌ Failed to set preset motion: %lu",
87 response->response.task_id);
88 return false;
89 }
90 }
91 RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
92 return false;
93 } catch (const std::exception &e) {
94 RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
95 return false;
96 }
97 }
98
99private:
100 rclcpp::Client<aimdk_msgs::srv::SetMcPresetMotion>::SharedPtr client_;
101};
102
103int main(int argc, char *argv[]) {
104 try {
105 rclcpp::init(argc, argv);
106 signal(SIGINT, signal_handler);
107 signal(SIGTERM, signal_handler);
108
109 g_node = std::make_shared<PresetMotionClient>();
110 // Cast g_node (std::shared_ptr<rclcpp::Node>) to a derived
111 // PresetMotionClient pointer (std::shared_ptr<PresetMotionClient>)
112 auto client = std::dynamic_pointer_cast<PresetMotionClient>(g_node);
113
114 int area = 1;
115 int motion = 1003;
116 std::cout << "Enter arm area ID (1-left, 2-right): ";
117 std::cin >> area;
118 std::cout
119 << "Enter preset motion ID (1001-raise, 1002-wave, 1003-handshake, "
120 "1004-airkiss): ";
121 std::cin >> motion;
122 if (client) {
123 client->send_request(area, motion);
124 }
125
126 // Clean up resources
127 g_node.reset();
128 rclcpp::shutdown();
129
130 return 0;
131 } catch (const std::exception &e) {
132 RCLCPP_ERROR(rclcpp::get_logger("main"),
133 "Program exited with exception: %s", e.what());
134 return 1;
135 }
136}
6.2.5 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 <vector>
8
9class HandControl : public rclcpp::Node {
10public:
11 HandControl()
12 : Node("hand_control"), position_pairs_({
13 {1.0, 1.0},
14 {0.0, 0.0},
15 {0.5, 0.5},
16 {0.2, 0.8},
17 {0.7, 0.3},
18 }),
19 current_index_(0) {
20 publisher_ = this->create_publisher<aimdk_msgs::msg::HandCommandArray>(
21 "/aima/hal/joint/hand/command", 10);
22
23 timer_ = this->create_wall_timer(
24 std::chrono::milliseconds(20), // 50Hz
25 std::bind(&HandControl::publish_hand_commands, this));
26
27 last_switch_time_ = now();
28 RCLCPP_INFO(this->get_logger(), "The hand control node has been started!");
29 }
30
31 void publish_hand_commands() {
32 // 1. Determine if it's time to switch parameters.
33 auto now_time = this->now();
34 if ((now_time - last_switch_time_).seconds() >= 2.0) {
35 current_index_ = (current_index_ + 1) % position_pairs_.size();
36 last_switch_time_ = now_time;
37 RCLCPP_INFO(this->get_logger(),
38 "Switched to the next parameter group, index=%zu (left=%.2f, "
39 "right=%.2f)",
40 current_index_, position_pairs_[current_index_].first,
41 position_pairs_[current_index_].second);
42 }
43
44 auto msg = std::make_unique<aimdk_msgs::msg::HandCommandArray>();
45 msg->header = aimdk_msgs::msg::MessageHeader();
46
47 float left_position = position_pairs_[current_index_].first;
48 float right_position = position_pairs_[current_index_].second;
49
50 aimdk_msgs::msg::HandCommand left_hands;
51 left_hands.name = "left_hand";
52 left_hands.position = left_position;
53 left_hands.velocity = 1.0;
54 left_hands.acceleration = 1.0;
55 left_hands.deceleration = 1.0;
56 left_hands.effort = 1.0;
57
58 aimdk_msgs::msg::HandCommand right_hands;
59 right_hands.name = "right_hand";
60 right_hands.position = right_position;
61 right_hands.velocity = 1.0;
62 right_hands.acceleration = 1.0;
63 right_hands.deceleration = 1.0;
64 right_hands.effort = 1.0;
65
66 msg->left_hands.push_back(left_hands);
67 msg->right_hands.push_back(right_hands);
68 msg->left_hand_type.value = 2;
69 msg->right_hand_type.value = 2;
70
71 publisher_->publish(std::move(msg));
72 }
73
74private:
75 rclcpp::Publisher<aimdk_msgs::msg::HandCommandArray>::SharedPtr publisher_;
76 rclcpp::TimerBase::SharedPtr timer_;
77
78 std::vector<std::pair<float, float>> position_pairs_;
79 size_t current_index_;
80
81 rclcpp::Time last_switch_time_;
82};
83
84int main(int argc, char *argv[]) {
85 rclcpp::init(argc, argv);
86 auto hand_control_node = std::make_shared<HandControl>();
87 rclcpp::spin(hand_control_node);
88 rclcpp::shutdown();
89 return 0;
90}
6.2.6 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 <chrono>
3#include <cmath>
4#include <memory>
5#include <rclcpp/rclcpp.hpp>
6
7using namespace std::chrono_literals;
8
9class HandCommandPublisher : public rclcpp::Node {
10public:
11 HandCommandPublisher() : Node("hand_command_publisher") {
12 publisher_ = this->create_publisher<aimdk_msgs::msg::HandCommandArray>(
13 "/aima/hal/joint/hand/command", 10);
14
15 // Create a timer to publish once per second
16 timer_ = this->create_wall_timer(
17 1s, std::bind(&HandCommandPublisher::publish_command, this));
18 }
19
20private:
21 void publish_command() {
22 auto message = aimdk_msgs::msg::HandCommandArray();
23
24 // Set hander
25 message.header.stamp = this->now();
26 message.header.frame_id = "hand_command";
27
28 // Set the hand type
29 message.left_hand_type.value = 1; // NIMBLE_HANDS
30 message.right_hand_type.value = 1; // NIMBLE_HANDS
31
32 // Create left hand command array
33 message.left_hands.resize(10);
34
35 // Set left thumb
36 message.left_hands[0].name = "left_thumb";
37 message.left_hands[0].position = 0.0;
38 message.left_hands[0].velocity = 0.1;
39 message.left_hands[0].acceleration = 0.0;
40 message.left_hands[0].deceleration = 0.0;
41 message.left_hands[0].effort = 0.0;
42 // Set other left fingers
43 for (int i = 1; i < 10; i++) {
44 message.left_hands[i].name = "left_index";
45 message.left_hands[i].position = 0.0;
46 message.left_hands[i].velocity = 0.1;
47 message.left_hands[i].acceleration = 0.0;
48 message.left_hands[i].deceleration = 0.0;
49 message.left_hands[i].effort = 0.0;
50 }
51
52 // Create right hand command array
53 message.right_hands.resize(10);
54
55 // Set right thumb
56 message.right_hands[0].name = "right_thumb";
57 message.right_hands[0].position = 0.0;
58 message.right_hands[0].velocity = 0.1;
59 message.right_hands[0].acceleration = 0.0;
60 message.right_hands[0].deceleration = 0.0;
61 message.right_hands[0].effort = 0.0;
62
63 // Set other right fingers (pinky)
64 for (int i = 1; i < 10; i++) {
65 message.right_hands[i].name = "right_pinky";
66 message.right_hands[i].position = 0.0;
67 message.right_hands[i].velocity = 0.1;
68 message.right_hands[i].acceleration = 0.0;
69 message.right_hands[i].deceleration = 0.0;
70 message.right_hands[i].effort = 0.0;
71 }
72
73 if (target_finger <= 10) {
74 message.right_hands[target_finger].position = 0.8;
75 } else {
76 int target_finger_ = target_finger - 10;
77 double target_position = 0.8;
78 if (target_finger_ < 3) {
79 // The three thumb motors on the left hand need their signs inverted to
80 // mirror the right hand's motion
81 target_position = -target_position;
82 }
83 message.left_hands[target_finger_].position = target_position;
84 }
85
86 // Publish the message
87 publisher_->publish(message);
88
89 RCLCPP_INFO(this->get_logger(),
90 "Published hand command with target_finger: %d", target_finger);
91
92 update_target_finger();
93 }
94
95 void update_target_finger() {
96 if (increasing_) {
97 target_finger += step_;
98 if (target_finger >= 19) {
99 target_finger = 19;
100 increasing_ = false;
101 }
102 } else {
103 target_finger -= step_;
104 if (target_finger <= 0) {
105 target_finger = 0;
106 increasing_ = true;
107 }
108 }
109 }
110
111 rclcpp::Publisher<aimdk_msgs::msg::HandCommandArray>::SharedPtr publisher_;
112 rclcpp::TimerBase::SharedPtr timer_;
113
114 int target_finger = 0;
115 int step_ = 1;
116 bool increasing_ = true;
117};
118
119int main(int argc, char **argv) {
120 rclcpp::init(argc, argv);
121 auto node = std::make_shared<HandCommandPublisher>();
122 rclcpp::spin(node);
123 rclcpp::shutdown();
124 return 0;
125}
6.2.7 Omnihand touch sensors (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 <chrono>
7#include <iomanip>
8#include <iostream>
9#include <rclcpp/rclcpp.hpp>
10
11using namespace std::chrono_literals;
12
13class HandStateSubscriber : public rclcpp::Node {
14public:
15 HandStateSubscriber() : Node("hand_state_subscriber") {
16 publisher_ = this->create_publisher<aimdk_msgs::msg::HandCommandArray>(
17 "/aima/hal/joint/hand/command", 10);
18 rclcpp::QoS qos_profile(rclcpp::KeepLast(10));
19 qos_profile.best_effort(); // Set reliability to BEST_EFFORT
20 // Create subscriber
21 subscription_ = this->create_subscription<aimdk_msgs::msg::HandStateArray>(
22 "/aima/hal/joint/hand/state", qos_profile,
23 std::bind(&HandStateSubscriber::topic_callback, this,
24 std::placeholders::_1));
25 // Create a timer to publish once per second
26 timer_ = this->create_wall_timer(
27 1s, std::bind(&HandStateSubscriber::publish_command, this));
28 RCLCPP_INFO(
29 this->get_logger(),
30 "Subscriber started, listening to /aima/hal/joint/hand/state topic...");
31 }
32
33private:
34 void topic_callback(const aimdk_msgs::msg::HandStateArray::SharedPtr msg) {
35 // Print message header information
36 RCLCPP_INFO(this->get_logger(),
37 "Message received - Sequence: %u, Timestamp: %d.%09d",
38 msg->header.sequence, msg->header.stamp.sec,
39 msg->header.stamp.nanosec);
40
41 // Print left hand touch sensor data
42 print_touch_sensor_data("Left Hand", msg->left_touch_sensors);
43
44 // Print right hand touch sensor data
45 print_touch_sensor_data("Right Hand", msg->right_touch_sensors);
46
47 std::cout << std::endl;
48 }
49
50 /**
51 * @brief Print touch sensor data for a specific hand
52 * @param hand_name Name of the hand (Left Hand/Right Hand)
53 * @param sensor_data Touch sensor data structure
54 */
55 void print_touch_sensor_data(
56 const std::string &hand_name,
57 const aimdk_msgs::msg::HandTouchSensorData &sensor_data) {
58 std::cout << "=== " << hand_name << " Touch Sensor Data ===" << std::endl;
59
60 // Print palm touch data
61 std::cout << "Palm Touch Data (36 elements): ";
62 print_array(sensor_data.palm_touch_data);
63
64 // Print back of hand touch data
65 std::cout << "Back of Hand Touch Data (36 elements): ";
66 print_array(sensor_data.back_of_hand_touch_data);
67
68 // Print finger touch data
69 std::cout << "Thumb Touch Data (16 elements): ";
70 print_array(sensor_data.thumb_touch_data);
71
72 std::cout << "Index Finger Touch Data (16 elements): ";
73 print_array(sensor_data.index_finger_touch_data);
74
75 std::cout << "Middle Finger Touch Data (16 elements): ";
76 print_array(sensor_data.middle_finger_touch_data);
77
78 std::cout << "Ring Finger Touch Data (16 elements): ";
79 print_array(sensor_data.ring_finger_touch_data);
80
81 std::cout << "Little Finger Touch Data (16 elements): ";
82 print_array(sensor_data.little_finger_touch_data);
83 }
84
85 /**
86 * @brief Print array of 25 uint8_t elements
87 * @param arr Array to print
88 */
89 void print_array(const std::array<uint8_t, 36> &arr) {
90 std::cout << "[";
91 for (size_t i = 0; i < arr.size(); ++i) {
92 std::cout << std::setw(3) << static_cast<int>(arr[i]);
93 if (i < arr.size() - 1)
94 std::cout << " ";
95 }
96 std::cout << "]" << std::endl;
97 }
98
99 /**
100 * @brief Print array of 16 uint8_t elements
101 * @param arr Array to print
102 */
103 void print_array(const std::array<uint8_t, 16> &arr) {
104 std::cout << "[";
105 for (size_t i = 0; i < arr.size(); ++i) {
106 std::cout << std::setw(3) << static_cast<int>(arr[i]);
107 if (i < arr.size() - 1)
108 std::cout << " ";
109 }
110 std::cout << "]" << std::endl;
111 }
112
113 void publish_command() {
114 auto message = aimdk_msgs::msg::HandCommandArray();
115
116 // Set hander
117 message.header.stamp = this->now();
118 message.header.frame_id = "hand_command";
119
120 // Set the hand type
121 message.left_hand_type.value = 1; // NIMBLE_HANDS
122 message.right_hand_type.value = 1; // NIMBLE_HANDS
123
124 // Create left hand command array
125 message.left_hands.resize(10);
126
127 // Set left thumb
128 message.left_hands[0].name = "left_thumb";
129 message.left_hands[0].position = 0.0;
130 message.left_hands[0].velocity = 0.1;
131 message.left_hands[0].acceleration = 0.0;
132 message.left_hands[0].deceleration = 0.0;
133 message.left_hands[0].effort = 0.0;
134 // Set other left fingers
135 for (int i = 1; i < 10; i++) {
136 message.left_hands[i].name = "left_index";
137 message.left_hands[i].position = 0.0;
138 message.left_hands[i].velocity = 0.1;
139 message.left_hands[i].acceleration = 0.0;
140 message.left_hands[i].deceleration = 0.0;
141 message.left_hands[i].effort = 0.0;
142 }
143
144 // Create right hand command array
145 message.right_hands.resize(10);
146
147 // Set right thumb
148 message.right_hands[0].name = "right_thumb";
149 message.right_hands[0].position = 0.0;
150 message.right_hands[0].velocity = 0.1;
151 message.right_hands[0].acceleration = 0.0;
152 message.right_hands[0].deceleration = 0.0;
153 message.right_hands[0].effort = 0.0;
154
155 // Set other right fingers (pinky)
156 for (int i = 1; i < 10; i++) {
157 message.right_hands[i].name = "right_pinky";
158 message.right_hands[i].position = 0.0;
159 message.right_hands[i].velocity = 0.1;
160 message.right_hands[i].acceleration = 0.0;
161 message.right_hands[i].deceleration = 0.0;
162 message.right_hands[i].effort = 0.0;
163 }
164
165 // Publish the message
166 publisher_->publish(message);
167
168 RCLCPP_INFO(this->get_logger(), "Published hand command");
169 }
170
171 rclcpp::Publisher<aimdk_msgs::msg::HandCommandArray>::SharedPtr publisher_;
172 rclcpp::Subscription<aimdk_msgs::msg::HandStateArray>::SharedPtr
173 subscription_;
174 rclcpp::TimerBase::SharedPtr timer_;
175};
176
177int main(int argc, char *argv[]) {
178 rclcpp::init(argc, argv);
179 auto node = std::make_shared<HandStateSubscriber>();
180 rclcpp::spin(node);
181 rclcpp::shutdown();
182 return 0;
183}
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 <memory>
10#include <signal.h>
11
12std::shared_ptr<rclcpp::Node> g_node = nullptr;
13
14void signal_handler(int signal) {
15 if (g_node) {
16 RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
17 signal);
18 g_node.reset();
19 }
20 rclcpp::shutdown();
21 exit(signal);
22}
23
24class McInputClient : public rclcpp::Node {
25public:
26 McInputClient() : Node("set_mc_input_source_client") {
27 client_ = this->create_client<aimdk_msgs::srv::SetMcInputSource>(
28 "/aimdk_5Fmsgs/srv/SetMcInputSource");
29
30 RCLCPP_INFO(this->get_logger(), "✅ SetMcInputSource client node created.");
31
32 // Wait for the service to become available
33 while (!client_->wait_for_service(std::chrono::seconds(2))) {
34 RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
35 }
36 RCLCPP_INFO(this->get_logger(),
37 "🟢 Service available, ready to send request.");
38 }
39
40 bool send_request() {
41 try {
42 auto request =
43 std::make_shared<aimdk_msgs::srv::SetMcInputSource::Request>();
44
45 // Set request data
46 request->action.value = 1001; // Add new input source
47 request->input_source.name = "node"; // Set message source
48 request->input_source.priority = 40; // Set priority
49 request->input_source.timeout = 1000; // Set timeout (ms)
50
51 RCLCPP_INFO(this->get_logger(), "📨 Sending input source request: (ID=%d)",
52 request->action.value);
53
54 auto timeout = std::chrono::milliseconds(250);
55 for (int i = 0; i < 8; i++) {
56 // Set header timestamp
57 request->request.header.stamp = this->now(); // use Node::now()
58 auto future = client_->async_send_request(request);
59 auto retcode = rclcpp::spin_until_future_complete(
60 this->shared_from_this(), future, timeout);
61 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
62 // retry as remote peer is NOT handled well by ROS
63 RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
64 continue;
65 }
66 // future.done
67 auto response = future.get();
68 auto code = response->response.header.code;
69 if (code == 0) {
70 RCLCPP_INFO(this->get_logger(),
71 "✅ Input source set successfully. task_id=%lu",
72 response->response.task_id);
73 return true;
74 } else {
75 RCLCPP_ERROR(
76 this->get_logger(),
77 "❌ Input source set failed. ret_code=%ld, task_id=%lu "
78 "(duplicated ADD? or MODIFY/ENABLE/DISABLE for unknown source?)",
79 code, response->response.task_id);
80 return false;
81 }
82 }
83 RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
84 return false;
85 } catch (const std::exception &e) {
86 RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
87 return false;
88 }
89 }
90
91private:
92 rclcpp::Client<aimdk_msgs::srv::SetMcInputSource>::SharedPtr client_;
93};
94
95int main(int argc, char *argv[]) {
96 try {
97 rclcpp::init(argc, argv);
98 signal(SIGINT, signal_handler);
99 signal(SIGTERM, signal_handler);
100
101 g_node = std::make_shared<McInputClient>();
102 auto client = std::dynamic_pointer_cast<McInputClient>(g_node);
103
104 if (client) {
105 client->send_request();
106 }
107
108 g_node.reset();
109 rclcpp::shutdown();
110
111 return 0;
112 } catch (const std::exception &e) {
113 RCLCPP_ERROR(rclcpp::get_logger("main"),
114 "Program terminated with exception: %s", e.what());
115 return 1;
116 }
117}
6.2.9 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 <memory>
7#include <signal.h>
8
9// Global node object
10std::shared_ptr<rclcpp::Node> g_node = nullptr;
11
12// Signal handler
13void signal_handler(int signal) {
14 if (g_node) {
15 RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
16 signal);
17 g_node.reset();
18 }
19 rclcpp::shutdown();
20 exit(signal);
21}
22
23// Client Class
24class GetCurrentInputSourceClient : public rclcpp::Node {
25public:
26 GetCurrentInputSourceClient() : Node("get_current_input_source_client") {
27
28 client_ = this->create_client<aimdk_msgs::srv::GetCurrentInputSource>(
29 "/aimdk_5Fmsgs/srv/GetCurrentInputSource");
30
31 RCLCPP_INFO(this->get_logger(),
32 "✅ GetCurrentInputSource client node created.");
33
34 // Wait for the service to become available
35 while (!client_->wait_for_service(std::chrono::seconds(2))) {
36 RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
37 }
38 RCLCPP_INFO(this->get_logger(),
39 "🟢 Service available, ready to send request.");
40 }
41
42 void send_request() {
43 try {
44 auto request =
45 std::make_shared<aimdk_msgs::srv::GetCurrentInputSource::Request>();
46 request->request = aimdk_msgs::msg::CommonRequest();
47
48 RCLCPP_INFO(this->get_logger(),
49 "📨 Sending request to get current input source");
50
51 auto timeout = std::chrono::milliseconds(250);
52
53 for (int i = 0; i < 8; i++) {
54 request->request.header.stamp = this->now();
55 auto future = client_->async_send_request(request);
56 auto retcode = rclcpp::spin_until_future_complete(
57 this->shared_from_this(), future, timeout);
58 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
59 // retry as remote peer is NOT handled well by ROS
60 RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
61 continue;
62 }
63 // future.done
64 auto response = future.get();
65 if (response->response.header.code == 0) {
66 RCLCPP_INFO(this->get_logger(),
67 "✅ Current input source get successfully:");
68 RCLCPP_INFO(this->get_logger(), "Name: %s",
69 response->input_source.name.c_str());
70 RCLCPP_INFO(this->get_logger(), "Priority: %d",
71 response->input_source.priority);
72 RCLCPP_INFO(this->get_logger(), "Timeout: %d",
73 response->input_source.timeout);
74 } else {
75 RCLCPP_WARN(this->get_logger(),
76 "❌ Current input source get failed, return code: %ld",
77 response->response.header.code);
78 }
79 return;
80 }
81 RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
82 } catch (const std::exception &e) {
83 RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
84 }
85 }
86
87private:
88 rclcpp::Client<aimdk_msgs::srv::GetCurrentInputSource>::SharedPtr client_;
89};
90
91int main(int argc, char *argv[]) {
92 try {
93 rclcpp::init(argc, argv);
94
95 signal(SIGINT, signal_handler);
96 signal(SIGTERM, signal_handler);
97
98 g_node = std::make_shared<GetCurrentInputSourceClient>();
99 auto client =
100 std::dynamic_pointer_cast<GetCurrentInputSourceClient>(g_node);
101
102 if (client) {
103 client->send_request();
104 }
105
106 g_node.reset();
107 rclcpp::shutdown();
108 return 0;
109 } catch (const std::exception &e) {
110 RCLCPP_ERROR(rclcpp::get_logger("main"),
111 "Program exited with exception: %s", e.what());
112 return 1;
113 }
114}
Usage Instructions
# Get current input source information
ros2 run examples get_current_input_source
Output Example
[INFO] [get_current_input_source_client]: Current input source: node
[INFO] [get_current_input_source_client]: Priority: 40
[INFO] [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 <memory>
13#include <signal.h>
14#include <thread>
15
16class DirectVelocityControl : public rclcpp::Node {
17public:
18 DirectVelocityControl() : Node("direct_velocity_control") {
19 // Create publisher
20 publisher_ = this->create_publisher<aimdk_msgs::msg::McLocomotionVelocity>(
21 "/aima/mc/locomotion/velocity", 10);
22 // Create service client
23 client_ = this->create_client<aimdk_msgs::srv::SetMcInputSource>(
24 "/aimdk_5Fmsgs/srv/SetMcInputSource");
25
26 // Maximum speed limits
27 max_forward_speed_ = 1.0; // m/s
28 max_lateral_speed_ = 1.0; // m/s
29 max_angular_speed_ = 1.0; // rad/s
30 // Minimum speed limits (0 is also OK)
31 min_forward_speed_ = 0.2; // m/s
32 min_lateral_speed_ = 0.2; // m/s
33 min_angular_speed_ = 0.1; // rad/s
34
35 RCLCPP_INFO(this->get_logger(), "Direct velocity control node started.");
36 }
37
38 void start_publish() {
39 if (timer_ != nullptr) {
40 return;
41 }
42 // Set timer to periodically publish velocity messages (50Hz)
43 timer_ = this->create_wall_timer(
44 std::chrono::milliseconds(20),
45 std::bind(&DirectVelocityControl::publish_velocity, this));
46 }
47
48 bool register_input_source() {
49 const std::chrono::seconds timeout(8);
50 auto start_time = std::chrono::steady_clock::now();
51 while (!client_->wait_for_service(std::chrono::seconds(2))) {
52 if (std::chrono::steady_clock::now() - start_time > timeout) {
53 RCLCPP_ERROR(this->get_logger(), "Waiting for service timed out");
54 return false;
55 }
56 RCLCPP_INFO(this->get_logger(), "Waiting for input source service...");
57 }
58
59 auto request =
60 std::make_shared<aimdk_msgs::srv::SetMcInputSource::Request>();
61 request->action.value = 1001;
62 request->input_source.name = "node";
63 request->input_source.priority = 40;
64 request->input_source.timeout = 1000;
65
66 auto timeout2 = std::chrono::milliseconds(250);
67
68 for (int i = 0; i < 8; i++) {
69 request->request.header.stamp = this->now();
70 auto future = client_->async_send_request(request);
71 auto retcode = rclcpp::spin_until_future_complete(
72 this->shared_from_this(), future, timeout2);
73 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
74 // retry as remote peer is NOT handled well by ROS
75 RCLCPP_INFO(this->get_logger(),
76 "trying to register input source... [%d]", i);
77 continue;
78 }
79 // future.done
80 auto response = future.get();
81 int state = response->response.state.value;
82 RCLCPP_INFO(this->get_logger(),
83 "Set input source succeeded: state=%d, task_id=%lu", state,
84 response->response.task_id);
85 return true;
86 }
87 RCLCPP_ERROR(this->get_logger(), "Service call failed or timed out");
88 return false;
89 }
90
91 void publish_velocity() {
92 auto msg = std::make_unique<aimdk_msgs::msg::McLocomotionVelocity>();
93 msg->header = aimdk_msgs::msg::MessageHeader();
94 msg->header.stamp = this->now();
95 msg->source = "node"; // Set message source
96 msg->forward_velocity = forward_velocity_;
97 msg->lateral_velocity = lateral_velocity_;
98 msg->angular_velocity = angular_velocity_;
99
100 publisher_->publish(std::move(msg));
101 RCLCPP_INFO(this->get_logger(),
102 "Published velocity: Forward %.2f m/s, Lateral %.2f m/s, "
103 "Angular %.2f rad/s",
104 forward_velocity_, lateral_velocity_, angular_velocity_);
105 }
106
107 void clear_velocity() {
108 forward_velocity_ = 0.0;
109 lateral_velocity_ = 0.0;
110 angular_velocity_ = 0.0;
111 }
112
113 bool set_forward(double forward) {
114 if (abs(forward) < 0.005) {
115 forward_velocity_ = 0.0;
116 return true;
117 } else if ((abs(forward) > max_forward_speed_) ||
118 (abs(forward) < min_forward_speed_)) {
119 RCLCPP_ERROR(this->get_logger(), "input value out of range, exiting");
120 return false;
121 } else {
122 forward_velocity_ = forward;
123 return true;
124 }
125 }
126
127 bool set_lateral(double lateral) {
128 if (abs(lateral) < 0.005) {
129 lateral_velocity_ = 0.0;
130 return true;
131 } else if ((abs(lateral) > max_lateral_speed_) ||
132 (abs(lateral) < min_lateral_speed_)) {
133 RCLCPP_ERROR(this->get_logger(), "input value out of range, exiting");
134 return false;
135 } else {
136 lateral_velocity_ = lateral;
137 return true;
138 }
139 }
140
141 bool set_angular(double angular) {
142 if (abs(angular) < 0.005) {
143 angular_velocity_ = 0.0;
144 return true;
145 } else if ((abs(angular) > max_angular_speed_) ||
146 (abs(angular) < min_angular_speed_)) {
147 RCLCPP_ERROR(this->get_logger(), "input value out of range, exiting");
148 return false;
149 } else {
150 angular_velocity_ = angular;
151 return true;
152 }
153 }
154
155private:
156 rclcpp::Publisher<aimdk_msgs::msg::McLocomotionVelocity>::SharedPtr
157 publisher_;
158 rclcpp::Client<aimdk_msgs::srv::SetMcInputSource>::SharedPtr client_;
159 rclcpp::TimerBase::SharedPtr timer_;
160
161 double forward_velocity_;
162 double lateral_velocity_;
163 double angular_velocity_;
164
165 double max_forward_speed_;
166 double max_lateral_speed_;
167 double max_angular_speed_;
168
169 double min_forward_speed_;
170 double min_lateral_speed_;
171 double min_angular_speed_;
172};
173
174// Signal Processing
175std::shared_ptr<DirectVelocityControl> global_node = nullptr;
176void signal_handler(int sig) {
177 if (global_node) {
178 global_node->clear_velocity();
179 RCLCPP_INFO(global_node->get_logger(),
180 "Received signal %d: clearing velocity and shutting down", sig);
181 }
182 rclcpp::shutdown();
183 exit(sig);
184}
185
186int main(int argc, char *argv[]) {
187 rclcpp::init(argc, argv);
188 signal(SIGINT, signal_handler);
189 signal(SIGTERM, signal_handler);
190
191 global_node = std::make_shared<DirectVelocityControl>();
192 auto node = global_node;
193
194 if (!node->register_input_source()) {
195 RCLCPP_ERROR(node->get_logger(),
196 "Input source registration failed, exiting");
197 global_node.reset();
198 rclcpp::shutdown();
199 return 1;
200 }
201
202 // get and check control values
203 // notice that mc has thresholds to start movement
204 double forward, lateral, angular;
205 std::cout << "Enter forward speed 0 or ±(0.2 ~ 1.0) m/s: ";
206 std::cin >> forward;
207 if (!node->set_forward(forward)) {
208 return 2;
209 }
210 std::cout << "Enter lateral speed 0 or ±(0.2 ~ 1.0) m/s: ";
211 std::cin >> lateral;
212 if (!node->set_lateral(lateral)) {
213 return 2;
214 }
215 std::cout << "Enter angular speed 0 or ±(0.1 ~ 1.0) rad/s: ";
216 std::cin >> angular;
217 if (!node->set_angular(angular)) {
218 return 2;
219 }
220
221 RCLCPP_INFO(node->get_logger(), "Setting velocity; moving for 5 seconds");
222
223 node->start_publish();
224
225 auto start_time = node->now();
226 while ((node->now() - start_time).seconds() < 5.0) {
227 rclcpp::spin_some(node);
228 std::this_thread::sleep_for(std::chrono::milliseconds(1));
229 }
230
231 node->clear_velocity();
232 RCLCPP_INFO(node->get_logger(), "5 seconds elapsed; robot stopped");
233
234 rclcpp::spin(node);
235 rclcpp::shutdown();
236 return 0;
237}
6.2.11 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 (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:
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 period of
control_timer_(currently 2 ms)
1#include <aimdk_msgs/msg/joint_command_array.hpp>
2#include <aimdk_msgs/msg/joint_state_array.hpp>
3#include <atomic>
4#include <cstdlib>
5#include <memory>
6#include <rclcpp/rclcpp.hpp>
7#include <ruckig/ruckig.hpp>
8#include <signal.h>
9#include <string>
10#include <unordered_map>
11#include <vector>
12
13/**
14 * @brief Global variables and signal handling
15 */
16// Global variables to control program state
17std::atomic<bool> g_running(true);
18std::atomic<bool> g_emergency_stop(false);
19
20// Signal handler function
21void signal_handler(int) {
22 g_running = false;
23 RCLCPP_INFO(rclcpp::get_logger("main"),
24 "Received termination signal, shutting down...");
25}
26
27/**
28 * @brief Robot model definition
29 */
30enum class JointArea {
31 HEAD, // Head joints
32 ARM, // Arm joints
33 WAIST, // Waist joints
34 LEG, // Leg joints
35};
36
37/**
38 * @brief Joint information structure
39 */
40struct JointInfo {
41 std::string name; // Joint name
42 double lower_limit; // Joint angle lower limit
43 double upper_limit; // Joint angle upper limit
44 double kp; // Position control gain
45 double kd; // Velocity control gain
46};
47
48/**
49 * @brief Robot model configuration
50 * Contains parameters for all joints, enabling or disabling specific joints as
51 * needed
52 */
53std::map<JointArea, std::vector<JointInfo>> robot_model = {
54 {JointArea::LEG,
55 {
56 // Left leg joint configuration
57 {"left_hip_pitch_joint", -2.704, 2.556, 40.0, 4.0},
58 {"left_hip_roll_joint", -0.235, 2.906, 40.0, 4.0},
59 {"left_hip_yaw_joint", -1.684, 3.430, 30.0, 3.0},
60 {"left_knee_joint", 0.0000, 2.4073, 80.0, 8.0},
61 {"left_ankle_pitch_joint", -0.803, 0.453, 40.0, 4.0},
62 {"left_ankle_roll_joint", -0.2625, 0.2625, 20.0, 2.0},
63 // Right leg joint configuration
64 {"right_hip_pitch_joint", -2.704, 2.556, 40.0, 4.0},
65 {"right_hip_roll_joint", -2.906, 0.235, 40.0, 4.0},
66 {"right_hip_yaw_joint", -3.430, 1.684, 30.0, 3.0},
67 {"right_knee_joint", 0.0000, 2.4073, 80.0, 8.0},
68 {"right_ankle_pitch_joint", -0.803, 0.453, 40.0, 4.0},
69 {"right_ankle_roll_joint", -0.2625, 0.2625, 20.0, 2.0},
70 }},
71
72 {JointArea::WAIST,
73 {
74 // Waist joint configuration
75 {"waist_yaw_joint", -3.43, 2.382, 20.0, 4.0},
76 {"waist_pitch_joint", -0.314, 0.314, 20.0, 4.0},
77 {"waist_roll_joint", -0.488, 0.488, 20.0, 4.0},
78 }},
79 {JointArea::ARM,
80 {
81 // Left arm joint configuration
82 {"left_shoulder_pitch_joint", -3.08, 2.04, 20.0, 2.0},
83 {"left_shoulder_roll_joint", -0.061, 2.993, 20.0, 2.0},
84 {"left_shoulder_yaw_joint", -2.556, 2.556, 20.0, 2.0},
85 {"left_elbow_joint", -2.3556, 0.0, 20.0, 2.0},
86 {"left_wrist_yaw_joint", -2.556, 2.556, 20.0, 2.0},
87 {"left_wrist_pitch_joint", -0.558, 0.558, 20.0, 2.0},
88 {"left_wrist_roll_joint", -1.571, 0.724, 20.0, 2.0},
89 // Right arm joint configuration
90 {"right_shoulder_pitch_joint", -3.08, 2.04, 20.0, 2.0},
91 {"right_shoulder_roll_joint", -2.993, 0.061, 20.0, 2.0},
92 {"right_shoulder_yaw_joint", -2.556, 2.556, 20.0, 2.0},
93 {"right_elbow_joint", -2.3556, 0.0000, 20.0, 2.0},
94 {"right_wrist_yaw_joint", -2.556, 2.556, 20.0, 2.0},
95 {"right_wrist_pitch_joint", -0.558, 0.558, 20.0, 2.0},
96 {"right_wrist_roll_joint", -0.724, 1.571, 20.0, 2.0},
97 }},
98 {JointArea::HEAD,
99 {
100 // Head joint configuration
101 {"head_yaw_joint", -0.366, 0.366, 20.0, 2.0},
102 {"head_pitch_joint", -0.3838, 0.3838, 20.0, 2.0},
103 }},
104};
105
106/**
107 * @brief Joint controller node class
108 * @tparam DOFs Degrees of freedom
109 * @tparam Area Joint area
110 */
111template <int DOFs, JointArea Area>
112class JointControllerNode : public rclcpp::Node {
113public:
114 /**
115 * @brief Constructor
116 * @param node_name Node name
117 * @param sub_topic Subscription topic name
118 * @param pub_topic Publication topic name
119 * @param qos QoS configuration
120 */
121 JointControllerNode(std::string node_name, std::string sub_topic,
122 std::string pub_topic,
123 rclcpp::QoS qos = rclcpp::SensorDataQoS())
124 : Node(node_name), ruckig(0.002) {
125 joint_info_ = robot_model[Area];
126 if (joint_info_.size() != DOFs) {
127 RCLCPP_ERROR(this->get_logger(), "Joint count mismatch.");
128 exit(1);
129 }
130
131 // Set motion constraints for Ruckig trajectory planner
132 for (int i = 0; i < DOFs; ++i) {
133 input.max_velocity[i] = 1.0; // Max velocity limit
134 input.max_acceleration[i] = 1.0; // Max acceleration limit
135 input.max_jerk[i] = 25.0; // Max jerk (change of acceleration) limit
136 }
137
138 // Create joint state subscriber
139 sub_ = this->create_subscription<aimdk_msgs::msg::JointStateArray>(
140 sub_topic, qos,
141 std::bind(&JointControllerNode::JointStateCallback, this,
142 std::placeholders::_1));
143
144 // Create joint command publisher
145 pub_ = this->create_publisher<aimdk_msgs::msg::JointCommandArray>(pub_topic,
146 qos);
147 }
148
149private:
150 // Ruckig trajectory planner variables
151 ruckig::Ruckig<DOFs> ruckig; // Trajectory planner instance
152 ruckig::InputParameter<DOFs> input; // Input parameters
153 ruckig::OutputParameter<DOFs> output; // Output parameters
154 bool ruckig_initialized_ = false; // Trajectory planner initialization flag
155 std::vector<JointInfo> joint_info_; // Joint information list
156
157 // ROS communication variables
158 rclcpp::Subscription<aimdk_msgs::msg::JointStateArray>::SharedPtr
159 sub_; // State subscriber
160 rclcpp::Publisher<aimdk_msgs::msg::JointCommandArray>::SharedPtr
161 pub_; // Command publisher
162
163 /**
164 * @brief Joint state callback function
165 * @param msg Joint state message
166 */
167 void
168 JointStateCallback(const aimdk_msgs::msg::JointStateArray::SharedPtr msg) {
169 // Initialize trajectory planner on first state reception
170 if (!ruckig_initialized_) {
171 for (int i = 0; i < DOFs; ++i) {
172 input.current_position[i] = msg->joints[i].position;
173 input.current_velocity[i] = msg->joints[i].velocity;
174 input.current_acceleration[i] = 0.0;
175 }
176 ruckig_initialized_ = true;
177 RCLCPP_INFO(this->get_logger(),
178 "Ruckig trajectory planner initialization complete");
179 }
180 }
181
182public:
183 /**
184 * @brief Set target joint position
185 * @param joint_name Joint name
186 * @param target_position Target position
187 * @return Whether the target position was successfully set
188 */
189 bool SetTargetPosition(std::string joint_name, double target_position) {
190 if (!ruckig_initialized_) {
191 RCLCPP_WARN(this->get_logger(),
192 "Ruckig trajectory planner not initialized");
193 return false;
194 }
195
196 // Find target joint and set its position
197 int target_joint = -1;
198 for (int i = 0; i < DOFs; ++i) {
199 if (joint_info_[i].name == joint_name) {
200 // Check if target position is within limits
201 if (target_position < joint_info_[i].lower_limit ||
202 target_position > joint_info_[i].upper_limit) {
203 RCLCPP_ERROR(
204 this->get_logger(),
205 "Target position %.3f exceeds limit for joint %s [%.3f, %.3f]",
206 target_position, joint_name.c_str(), joint_info_[i].lower_limit,
207 joint_info_[i].upper_limit);
208 return false;
209 }
210 input.target_position[i] = target_position;
211 input.target_velocity[i] = 0.0;
212 input.target_acceleration[i] = 0.0;
213 target_joint = i;
214 } else {
215 input.target_position[i] = input.current_position[i];
216 input.target_velocity[i] = 0.0;
217 input.target_acceleration[i] = 0.0;
218 }
219 }
220
221 if (target_joint == -1) {
222 RCLCPP_ERROR(this->get_logger(), "Joint %s not found",
223 joint_name.c_str());
224 return false;
225 }
226
227 // Perform trajectory planning and send command using Ruckig
228 const double tolerance = 1e-6;
229 while (g_running && rclcpp::ok() && !g_emergency_stop) {
230 auto result = ruckig.update(input, output);
231 if (result != ruckig::Result::Working &&
232 result != ruckig::Result::Finished) {
233 RCLCPP_WARN(this->get_logger(), "Trajectory planning failed");
234 break;
235 }
236
237 // Update current state
238 for (int i = 0; i < DOFs; ++i) {
239 input.current_position[i] = output.new_position[i];
240 input.current_velocity[i] = output.new_velocity[i];
241 input.current_acceleration[i] = output.new_acceleration[i];
242 }
243
244 // Check if target position is reached
245 if (std::abs(output.new_position[target_joint] - target_position) <
246 tolerance) {
247 RCLCPP_INFO(this->get_logger(), "Joint %s reached target position",
248 joint_name.c_str());
249 break;
250 }
251
252 // Create and send joint command
253 aimdk_msgs::msg::JointCommandArray cmd;
254 cmd.joints.resize(DOFs);
255 for (int i = 0; i < DOFs; ++i) {
256 auto &joint = joint_info_[i];
257 cmd.joints[i].name = joint.name;
258 cmd.joints[i].position = output.new_position[i];
259 cmd.joints[i].velocity = output.new_velocity[i];
260 cmd.joints[i].stiffness = joint.kp;
261 cmd.joints[i].damping = joint.kd;
262 }
263 pub_->publish(cmd);
264
265 // Short delay to avoid excessive CPU usage
266 std::this_thread::sleep_for(std::chrono::milliseconds(2));
267 }
268
269 return true;
270 }
271
272 /**
273 * @brief Safely stop all joints
274 */
275 void safe_stop() {
276 if (!ruckig_initialized_) {
277 RCLCPP_WARN(this->get_logger(), "Ruckig trajectory planner not "
278 "initialized, cannot perform safe stop");
279 return;
280 }
281
282 RCLCPP_INFO(this->get_logger(), "Performing safe stop...");
283
284 // Set all joint target positions to current positions
285 for (int i = 0; i < DOFs; ++i) {
286 input.target_position[i] = input.current_position[i];
287 input.target_velocity[i] = 0.0;
288 input.target_acceleration[i] = 0.0;
289 }
290
291 // Send final command to ensure joints stop
292 aimdk_msgs::msg::JointCommandArray cmd;
293 cmd.joints.resize(DOFs);
294 for (int i = 0; i < DOFs; ++i) {
295 auto &joint = joint_info_[i];
296 cmd.joints[i].name = joint.name;
297 cmd.joints[i].position = input.current_position[i];
298 cmd.joints[i].velocity = 0.0;
299 cmd.joints[i].stiffness = joint.kp;
300 cmd.joints[i].damping = joint.kd;
301 }
302 pub_->publish(cmd);
303
304 RCLCPP_INFO(this->get_logger(), "Safe stop complete");
305 }
306
307 /**
308 * @brief Emergency stop for all joints
309 */
310 void emergency_stop() {
311 g_emergency_stop = true;
312 safe_stop();
313 RCLCPP_ERROR(this->get_logger(), "Emergency stop triggered");
314 }
315};
316
317/**
318 * @brief Main function
319 */
320int main(int argc, char *argv[]) {
321 rclcpp::init(argc, argv);
322
323 // Set up signal handling
324 signal(SIGINT, signal_handler);
325 signal(SIGTERM, signal_handler);
326
327 try {
328 // Create leg controller node
329 auto leg_node = std::make_shared<JointControllerNode<12, JointArea::LEG>>(
330 "leg_node", "/aima/hal/joint/leg/state", "/aima/hal/joint/leg/command");
331
332 // Create timer node
333 rclcpp::Node::SharedPtr timer_node =
334 rclcpp::Node::make_shared("timer_node");
335 double position = 0.8;
336
337 // Create timer callback function
338 auto timer = timer_node->create_wall_timer(std::chrono::seconds(3), [&]() {
339 if (!g_running || g_emergency_stop)
340 return; // If the program is shutting down or emergency stopped, do not
341 // execute new actions
342 position = -position;
343 position = 1.3 + position;
344 if (!leg_node->SetTargetPosition("left_knee_joint", position)) {
345 RCLCPP_ERROR(rclcpp::get_logger("main"),
346 "Failed to set target position");
347 }
348 });
349
350 // Create executor
351 rclcpp::executors::MultiThreadedExecutor executor;
352 executor.add_node(leg_node);
353 executor.add_node(timer_node);
354
355 // Main loop
356 while (g_running && rclcpp::ok() && !g_emergency_stop) {
357 executor.spin_once(std::chrono::milliseconds(100));
358 }
359
360 // Safely stop all joints
361 RCLCPP_INFO(rclcpp::get_logger("main"), "Safely stopping all joints...");
362 leg_node->safe_stop();
363
364 // Wait a short time to ensure command transmission is complete
365 std::this_thread::sleep_for(std::chrono::milliseconds(100));
366
367 // Clean up resources
368 RCLCPP_INFO(rclcpp::get_logger("main"), "Cleaning up resources...");
369 leg_node.reset();
370 timer_node.reset();
371
372 } catch (const std::exception &e) {
373 RCLCPP_ERROR(rclcpp::get_logger("main"), "Exception occurred: %s",
374 e.what());
375 g_emergency_stop = true;
376 } catch (...) {
377 RCLCPP_ERROR(rclcpp::get_logger("main"), "Unknown exception occurred");
378 g_emergency_stop = true;
379 }
380
381 RCLCPP_INFO(rclcpp::get_logger("main"), "Program exited safely");
382 rclcpp::shutdown();
383 return 0;
384}
6.2.12 Keyboard Control of the Robot
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 <csignal>
13#include <curses.h>
14#include <rclcpp/rclcpp.hpp>
15
16using aimdk_msgs::msg::McLocomotionVelocity;
17using std::placeholders::_1;
18
19class KeyboardVelocityController : public rclcpp::Node {
20public:
21 KeyboardVelocityController()
22 : Node("keyboard_velocity_controller"), forward_velocity_(0.0),
23 lateral_velocity_(0.0), angular_velocity_(0.0), step_(0.2),
24 angular_step_(0.1) {
25 pub_ = this->create_publisher<McLocomotionVelocity>(
26 "/aima/mc/locomotion/velocity", 10);
27 client_ = this->create_client<aimdk_msgs::srv::SetMcInputSource>(
28 "/aimdk_5Fmsgs/srv/SetMcInputSource");
29 // Register input source
30 if (!register_input_source()) {
31 RCLCPP_ERROR(this->get_logger(),
32 "Input source registration failed, exiting");
33 throw std::runtime_error("Input source registration failed");
34 }
35 // Initialize ncurses
36 initscr();
37 cbreak();
38 noecho();
39 keypad(stdscr, TRUE);
40 nodelay(stdscr, TRUE);
41
42 timer_ = this->create_wall_timer(
43 std::chrono::milliseconds(50),
44 std::bind(&KeyboardVelocityController::checkKeyAndPublish, this));
45
46 RCLCPP_INFO(this->get_logger(),
47 "Control started: W/S Forward/Backward | A/D Strafe Left/Right "
48 "| Q/E Turn Left/Right | Space Stop | ESC Exit");
49 }
50
51 ~KeyboardVelocityController() {
52 endwin(); // Restore terminal
53 }
54
55private:
56 rclcpp::Publisher<McLocomotionVelocity>::SharedPtr pub_;
57 rclcpp::Client<aimdk_msgs::srv::SetMcInputSource>::SharedPtr client_;
58 rclcpp::TimerBase::SharedPtr timer_;
59
60 float forward_velocity_, lateral_velocity_, angular_velocity_;
61 const float step_, angular_step_;
62
63 bool register_input_source() {
64 const std::chrono::seconds srv_timeout(8);
65 auto start_time = std::chrono::steady_clock::now();
66 while (!client_->wait_for_service(std::chrono::seconds(2))) {
67 if (std::chrono::steady_clock::now() - start_time > srv_timeout) {
68 RCLCPP_ERROR(this->get_logger(), "Waiting for service timed out");
69 return false;
70 }
71 RCLCPP_INFO(this->get_logger(), "Waiting for input source service...");
72 }
73
74 auto request =
75 std::make_shared<aimdk_msgs::srv::SetMcInputSource::Request>();
76 request->action.value = 1001;
77 request->input_source.name = "node";
78 request->input_source.priority = 40;
79 request->input_source.timeout = 1000;
80
81 auto timeout = std::chrono::milliseconds(250);
82
83 for (int i = 0; i < 8; i++) {
84 request->request.header.stamp = this->now();
85 auto future = client_->async_send_request(request);
86 auto retcode = rclcpp::spin_until_future_complete(
87 this->get_node_base_interface(), future, timeout);
88 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
89 // retry as remote peer is NOT handled well by ROS
90 RCLCPP_INFO(this->get_logger(),
91 "trying to register input source... [%d]", i);
92 continue;
93 }
94 // future.done
95 auto response = future.get();
96 int state = response->response.state.value;
97 RCLCPP_INFO(this->get_logger(),
98 "Set input source succeeded: state=%d, task_id=%lu", state,
99 response->response.task_id);
100 return true;
101 }
102 RCLCPP_ERROR(this->get_logger(), "Service call failed or timed out");
103 return false;
104 }
105
106 void checkKeyAndPublish() {
107 int ch = getch(); // non-blocking read
108
109 switch (ch) {
110 case ' ': // Space key
111 forward_velocity_ = 0.0;
112 lateral_velocity_ = 0.0;
113 angular_velocity_ = 0.0;
114 break;
115 case 'w':
116 forward_velocity_ = std::min(forward_velocity_ + step_, 1.0f);
117 break;
118 case 's':
119 forward_velocity_ = std::max(forward_velocity_ - step_, -1.0f);
120 break;
121 case 'a':
122 lateral_velocity_ = std::min(lateral_velocity_ + step_, 1.0f);
123 break;
124 case 'd':
125 lateral_velocity_ = std::max(lateral_velocity_ - step_, -1.0f);
126 break;
127 case 'q':
128 angular_velocity_ = std::min(angular_velocity_ + angular_step_, 1.0f);
129 break;
130 case 'e':
131 angular_velocity_ = std::max(angular_velocity_ - angular_step_, -1.0f);
132 break;
133 case 27: // ESC Key
134 RCLCPP_INFO(this->get_logger(), "Exiting control");
135 rclcpp::shutdown();
136 return;
137 }
138
139 auto msg = std::make_unique<McLocomotionVelocity>();
140 msg->header = aimdk_msgs::msg::MessageHeader();
141 msg->header.stamp = this->now();
142 msg->source = "node";
143 msg->forward_velocity = forward_velocity_;
144 msg->lateral_velocity = lateral_velocity_;
145 msg->angular_velocity = angular_velocity_;
146
147 float fwd = forward_velocity_;
148 float lat = lateral_velocity_;
149 float ang = angular_velocity_;
150
151 pub_->publish(std::move(msg));
152
153 // Screen Output
154 clear();
155 mvprintw(0, 0,
156 "W/S: Forward/Backward | A/D: Left/Right Strafe | Q/E: Turn "
157 "Left/Right | Space: Stop | ESC: Exit");
158 mvprintw(2, 0,
159 "Speed Status: Forward: %.2f m/s | Lateral: %.2f m/s | Angular: "
160 "%.2f rad/s",
161 fwd, lat, ang);
162 refresh();
163 }
164};
165
166int main(int argc, char *argv[]) {
167 rclcpp::init(argc, argv);
168 try {
169 auto node = std::make_shared<KeyboardVelocityController>();
170 rclcpp::spin(node);
171 } catch (const std::exception &e) {
172 RCLCPP_FATAL(rclcpp::get_logger("main"),
173 "Program exited with exception: %s", e.what());
174 }
175 rclcpp::shutdown();
176 return 0;
177}
6.2.13 Take Photo
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/opencv.hpp>
5#include <rclcpp/rclcpp.hpp>
6#include <sensor_msgs/msg/image.hpp>
7#include <string>
8
9class SaveOneRaw : public rclcpp::Node {
10public:
11 SaveOneRaw() : Node("save_one_image"), saved_(false) {
12 topic_ = declare_parameter<std::string>(
13 "image_topic", "/aima/hal/sensor/stereo_head_front_left/rgb_image");
14
15 std::filesystem::create_directories("images");
16
17 auto qos = rclcpp::SensorDataQoS(); // BestEffort/Volatile
18 sub_ = create_subscription<sensor_msgs::msg::Image>(
19 topic_, qos, std::bind(&SaveOneRaw::cb, this, std::placeholders::_1));
20
21 RCLCPP_INFO(get_logger(), "Subscribing (raw): %s", topic_.c_str());
22 }
23
24private:
25 void cb(const sensor_msgs::msg::Image::SharedPtr msg) {
26 if (saved_)
27 return;
28
29 try {
30 // Obtain the Mat without copying by not specifying encoding
31 cv_bridge::CvImageConstPtr cvp = cv_bridge::toCvShare(msg);
32 cv::Mat img = cvp->image;
33
34 // Convert to BGR for uniform saving
35 if (msg->encoding == "rgb8") {
36 cv::cvtColor(img, img, cv::COLOR_RGB2BGR);
37 } else if (msg->encoding == "mono8") {
38 cv::cvtColor(img, img, cv::COLOR_GRAY2BGR);
39 } // bgr8 Use this directly; add more branches as needed to support
40 // additional encodings.
41
42 auto now = std::chrono::system_clock::now();
43 auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
44 now.time_since_epoch())
45 .count();
46 std::string path = "images/frame_" + std::to_string(ms) + ".png";
47
48 if (cv::imwrite(path, img)) {
49 RCLCPP_INFO(get_logger(), "Saved: %s (%dx%d)", path.c_str(), img.cols,
50 img.rows);
51 saved_ = true;
52 rclcpp::shutdown();
53 } else {
54 RCLCPP_ERROR(get_logger(), "cv::imwrite failed: %s", path.c_str());
55 }
56 } catch (const std::exception &e) {
57 RCLCPP_ERROR(get_logger(), "raw decode failed: %s", e.what());
58 // Do not set the saved flag; wait for the next frame
59 }
60 }
61
62 std::string topic_;
63 bool saved_;
64 rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr sub_;
65};
66
67int main(int argc, char **argv) {
68 rclcpp::init(argc, argv);
69 rclcpp::spin(std::make_shared<SaveOneRaw>());
70 return 0;
71}
6.2.14 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 <deque>
2#include <iomanip>
3#include <memory>
4#include <rclcpp/rclcpp.hpp>
5#include <sensor_msgs/msg/camera_info.hpp>
6#include <sensor_msgs/msg/compressed_image.hpp>
7#include <sensor_msgs/msg/image.hpp>
8#include <sstream>
9#include <string>
10#include <vector>
11
12// OpenCV headers for image/video writing
13#include <cv_bridge/cv_bridge.h>
14#include <opencv2/opencv.hpp>
15
16/**
17 * @brief Example of subscribing to multiple topics for the head depth camera
18 *
19 * You can select which topic type to subscribe to via the startup argument
20 * --ros-args -p topic_type:=<type>:
21 * - depth_image: Depth image (sensor_msgs/Image)
22 * - rgb_image: RGB image (sensor_msgs/Image)
23 * - rgb_image_compressed: RGB compressed image (sensor_msgs/CompressedImage)
24 * - rgb_camera_info: Camera intrinsic parameters (sensor_msgs/CameraInfo)
25 * - depth_camera_info: Camera intrinsic parameters (sensor_msgs/CameraInfo)
26 *
27 * Examples:
28 * ros2 run examples echo_camera_rgbd --ros-args -p
29 * topic_type:=rgb_image ros2
30 * run examples echo_camera_rgbd --ros-args -p
31 * topic_type:=rgb_camera_info
32 *
33 * topic_type defaults to "rgb_image"
34 *
35 * See individual callbacks for more detailed comments
36 */
37class CameraTopicEcho : public rclcpp::Node {
38public:
39 CameraTopicEcho() : Node("camera_topic_echo") {
40 // Select which topic type to subscribe to
41 topic_type_ = declare_parameter<std::string>("topic_type", "rgb_image");
42 dump_video_path_ = declare_parameter<std::string>("dump_video_path", "");
43
44 // Subscribed topics and their message layouts
45 // 1. /aima/hal/sensor/rgbd_head_front/depth_image
46 // - topic_type: depth_image
47 // - message type: sensor_msgs::msg::Image
48 // - frame_id: rgbd_head_front
49 // - contents: depth image
50 // 2. /aima/hal/sensor/rgbd_head_front/rgb_image
51 // - topic_type: rgb_image
52 // - message type: sensor_msgs::msg::Image
53 // - frame_id: rgbd_head_front
54 // - contents: RGB image
55 // 3. /aima/hal/sensor/rgbd_head_front/rgb_image/compressed
56 // - topic_type: rgb_image_compressed
57 // - message type: sensor_msgs::msg::CompressedImage
58 // - frame_id: rgbd_head_front
59 // - contents: RGB compressed image
60 // 4. /aima/hal/sensor/rgbd_head_front/rgb_camera_info
61 // - topic_type: camera_info
62 // - message type: sensor_msgs::msg::CameraInfo
63 // - frame_id: rgbd_head_front
64 // - contents: RGB camera intrinsic parameters
65 // 5. /aima/hal/sensor/rgbd_head_front/depth_camera_info
66 // - topic_type: camera_info
67 // - message type: sensor_msgs::msg::CameraInfo
68 // - frame_id: rgbd_head_front
69 // - contents: RGB camera intrinsic parameters
70
71 auto qos = rclcpp::SensorDataQoS();
72
73 // Enable depth image subscription
74 if (topic_type_ == "depth_image") {
75 topic_name_ = "/aima/hal/sensor/rgbd_head_front/depth_image";
76 sub_image_ = create_subscription<sensor_msgs::msg::Image>(
77 topic_name_, qos,
78 std::bind(&CameraTopicEcho::cb_image, this, std::placeholders::_1));
79 RCLCPP_INFO(get_logger(), "✅ Subscribing Depth Image: %s",
80 topic_name_.c_str());
81
82 // Enable RGB image subscription
83 } else if (topic_type_ == "rgb_image") {
84 topic_name_ = "/aima/hal/sensor/rgbd_head_front/rgb_image";
85 sub_image_ = create_subscription<sensor_msgs::msg::Image>(
86 topic_name_, qos,
87 std::bind(&CameraTopicEcho::cb_image, this, std::placeholders::_1));
88 RCLCPP_INFO(get_logger(), "✅ Subscribing RGB Image: %s",
89 topic_name_.c_str());
90 if (!dump_video_path_.empty()) {
91 RCLCPP_INFO(get_logger(), "📝 Will dump received images to video: %s",
92 dump_video_path_.c_str());
93 }
94
95 // Enable RGB compressed image subscription
96 } else if (topic_type_ == "rgb_image_compressed") {
97 topic_name_ = "/aima/hal/sensor/rgbd_head_front/rgb_image/compressed";
98 sub_compressed_ = create_subscription<sensor_msgs::msg::CompressedImage>(
99 topic_name_, qos,
100 std::bind(&CameraTopicEcho::cb_compressed, this,
101 std::placeholders::_1));
102 RCLCPP_INFO(get_logger(), "✅ Subscribing CompressedImage: %s",
103 topic_name_.c_str());
104
105 // Enable rgb camera info subscription
106 } else if (topic_type_ == "rgb_camera_info") {
107 topic_name_ = "/aima/hal/sensor/rgbd_head_front/rgb_camera_info";
108 // RGB-D CameraInfo subscriptions is different with other cameras.
109 // The messages arrive in about 10Hz and SensorDataQoS is enough.
110 sub_camerainfo_ = create_subscription<sensor_msgs::msg::CameraInfo>(
111 topic_name_, qos,
112 std::bind(&CameraTopicEcho::cb_camerainfo, this,
113 std::placeholders::_1));
114 RCLCPP_INFO(get_logger(), "✅ Subscribing RGB CameraInfo: %s",
115 topic_name_.c_str());
116
117 // Enable depth camera info subscription
118 } else if (topic_type_ == "depth_camera_info") {
119 topic_name_ = "/aima/hal/sensor/rgbd_head_front/depth_camera_info";
120 // RGB-D CameraInfo subscriptions is different with other cameras.
121 // The messages arrive in about 10Hz and SensorDataQoS is enough.
122 sub_camerainfo_ = create_subscription<sensor_msgs::msg::CameraInfo>(
123 topic_name_, qos,
124 std::bind(&CameraTopicEcho::cb_camerainfo, this,
125 std::placeholders::_1));
126 RCLCPP_INFO(get_logger(), "✅ Subscribing Depth CameraInfo: %s",
127 topic_name_.c_str());
128
129 // Unknown topic_type error
130 } else {
131 RCLCPP_ERROR(get_logger(), "Unknown topic_type: %s", topic_type_.c_str());
132 throw std::runtime_error("Unknown topic_type");
133 }
134 }
135
136 ~CameraTopicEcho() override {
137 if (video_writer_.isOpened()) {
138 video_writer_.release();
139 RCLCPP_INFO(get_logger(), "Video file closed.");
140 }
141 }
142
143private:
144 // Image callback (depth/RGB image)
145 void cb_image(const sensor_msgs::msg::Image::SharedPtr msg) {
146 update_arrivals();
147
148 if (should_print()) {
149 RCLCPP_INFO(get_logger(),
150 "📸 %s received\n"
151 " • frame_id: %s\n"
152 " • stamp (sec): %.6f\n"
153 " • encoding: %s\n"
154 " • size (WxH): %u x %u\n"
155 " • step (bytes/row):%u\n"
156 " • is_bigendian: %u\n"
157 " • recv FPS (1s): %.1f",
158 topic_type_.c_str(), msg->header.frame_id.c_str(),
159 rclcpp::Time(msg->header.stamp).seconds(),
160 msg->encoding.c_str(), msg->width, msg->height, msg->step,
161 msg->is_bigendian, get_fps());
162 }
163
164 // Video dump is supported only for RGB images
165 if (topic_type_ == "rgb_image" && !dump_video_path_.empty()) {
166 dump_image_to_video(msg);
167 }
168 }
169
170 // CompressedImage callback
171 void cb_compressed(const sensor_msgs::msg::CompressedImage::SharedPtr msg) {
172 update_arrivals();
173
174 if (should_print()) {
175 RCLCPP_INFO(get_logger(),
176 "🗜️ CompressedImage received\n"
177 " • frame_id: %s\n"
178 " • stamp (sec): %.6f\n"
179 " • format: %s\n"
180 " • data size: %zu\n"
181 " • recv FPS (1s): %.1f",
182 msg->header.frame_id.c_str(),
183 rclcpp::Time(msg->header.stamp).seconds(),
184 msg->format.c_str(), msg->data.size(), get_fps());
185 }
186 }
187
188 // CameraInfo callback (camera intrinsic parameters)
189 void cb_camerainfo(const sensor_msgs::msg::CameraInfo::SharedPtr msg) {
190 // CameraInfo is typically published once; print it once
191 std::ostringstream oss;
192 oss << "📷 " << topic_type_ << " received\n"
193 << " • frame_id: " << msg->header.frame_id << "\n"
194 << " • stamp (sec): " << rclcpp::Time(msg->header.stamp).seconds()
195 << "\n"
196 << " • width x height: " << msg->width << " x " << msg->height << "\n"
197 << " • distortion_model:" << msg->distortion_model << "\n"
198 << " • D: [";
199 for (size_t i = 0; i < msg->d.size(); ++i) {
200 oss << msg->d[i];
201 if (i + 1 < msg->d.size())
202 oss << ", ";
203 }
204 oss << "]\n • K: [";
205 for (int i = 0; i < 9; ++i) {
206 oss << msg->k[i];
207 if (i + 1 < 9)
208 oss << ", ";
209 }
210 oss << "]\n • R: [";
211 for (int i = 0; i < 9; ++i) {
212 oss << msg->r[i];
213 if (i + 1 < 9)
214 oss << ", ";
215 }
216 oss << "]\n • P: [";
217 for (int i = 0; i < 12; ++i) {
218 oss << msg->p[i];
219 if (i + 1 < 12)
220 oss << ", ";
221 }
222 oss << "]\n"
223 << " • binning_x: " << msg->binning_x << "\n"
224 << " • binning_y: " << msg->binning_y << "\n"
225 << " • roi: { x_offset: " << msg->roi.x_offset
226 << ", y_offset: " << msg->roi.y_offset
227 << ", height: " << msg->roi.height << ", width: " << msg->roi.width
228 << ", do_rectify: " << (msg->roi.do_rectify ? "true" : "false") << " }";
229 RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
230 }
231
232 // Track arrival timestamps to compute FPS
233 void update_arrivals() {
234 const rclcpp::Time now = this->get_clock()->now();
235 arrivals_.push_back(now);
236 while (!arrivals_.empty() && (now - arrivals_.front()).seconds() > 1.0) {
237 arrivals_.pop_front();
238 }
239 }
240 double get_fps() const { return static_cast<double>(arrivals_.size()); }
241
242 // Control printing frequency
243 bool should_print() {
244 const rclcpp::Time now = this->get_clock()->now();
245 if ((now - last_print_).seconds() >= 1.0) {
246 last_print_ = now;
247 return true;
248 }
249 return false;
250 }
251
252 // Dump received images to a video file (RGB images only)
253 void dump_image_to_video(const sensor_msgs::msg::Image::SharedPtr &msg) {
254 cv::Mat image;
255 try {
256 // Obtain the Mat without copying by not specifying encoding
257 cv_bridge::CvImageConstPtr cvp = cv_bridge::toCvShare(msg);
258 image = cvp->image;
259 // Convert to BGR for uniform saving
260 if (msg->encoding == "rgb8") {
261 cv::cvtColor(image, image, cv::COLOR_RGB2BGR);
262 } else {
263 RCLCPP_WARN(get_logger(), "image encoding not expected: %s",
264 msg->encoding.c_str());
265 return;
266 }
267 } catch (const std::exception &e) {
268 RCLCPP_WARN(get_logger(), "cv_bridge exception: %s", e.what());
269 return;
270 }
271
272 // Initialize VideoWriter
273 if (!video_writer_.isOpened()) {
274 int fourcc = cv::VideoWriter::fourcc('m', 'p', '4', 'v');
275 double fps = std::max(1.0, get_fps());
276 bool ok = video_writer_.open(dump_video_path_, fourcc, fps,
277 cv::Size(image.cols, image.rows), true);
278 if (!ok) {
279 RCLCPP_ERROR(get_logger(), "Failed to open video file: %s",
280 dump_video_path_.c_str());
281 dump_video_path_.clear(); // stop trying
282 return;
283 }
284 RCLCPP_INFO(get_logger(), "VideoWriter started: %s, size=%dx%d, fps=%.1f",
285 dump_video_path_.c_str(), image.cols, image.rows, fps);
286 }
287 video_writer_.write(image);
288 }
289
290 // Member variables
291 std::string topic_type_;
292 std::string topic_name_;
293 std::string dump_video_path_;
294
295 // Subscriptions
296 rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr sub_image_;
297 rclcpp::Subscription<sensor_msgs::msg::CompressedImage>::SharedPtr
298 sub_compressed_;
299 rclcpp::Subscription<sensor_msgs::msg::CameraInfo>::SharedPtr sub_camerainfo_;
300
301 // FPS statistics
302 rclcpp::Time last_print_{0, 0, RCL_ROS_TIME};
303 std::deque<rclcpp::Time> arrivals_;
304
305 // Video writer
306 cv::VideoWriter video_writer_;
307};
308
309int main(int argc, char **argv) {
310 rclcpp::init(argc, argv);
311 auto node = std::make_shared<CameraTopicEcho>();
312 rclcpp::spin(node);
313 rclcpp::shutdown();
314 return 0;
315}
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.avi
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 <deque>
2#include <iomanip>
3#include <memory>
4#include <rclcpp/rclcpp.hpp>
5#include <sensor_msgs/msg/camera_info.hpp>
6#include <sensor_msgs/msg/compressed_image.hpp>
7#include <sensor_msgs/msg/image.hpp>
8#include <sstream>
9#include <string>
10#include <vector>
11
12// OpenCV headers for image/video writing
13#include <cv_bridge/cv_bridge.h>
14#include <opencv2/opencv.hpp>
15
16/**
17 * @brief Example of subscribing to multiple topics for the stereo head camera
18 *
19 * You can select which topic type to subscribe to via the startup argument
20 * --ros-args -p topic_type:=<type>:
21 * - left_rgb_image: left camera RGB image (sensor_msgs/Image)
22 * - left_rgb_image_compressed: left camera RGB compressed image
23 * (sensor_msgs/CompressedImage)
24 * - left_camera_info: left camera intrinsic parameters
25 * (sensor_msgs/CameraInfo)
26 * - right_rgb_image: right camera RGB image (sensor_msgs/Image)
27 * - right_rgb_image_compressed: right camera RGB compressed image
28 * (sensor_msgs/CompressedImage)
29 * - right_camera_info: right camera intrinsic parameters
30 * (sensor_msgs/CameraInfo)
31 *
32 * Examples:
33 * ros2 run examples echo_camera_stereo --ros-args -p
34 * topic_type:=left_rgb_image ros2 run examples echo_camera_stereo --ros-args -p
35 * topic_type:=right_rgb_image ros2 run examples echo_camera_stereo --ros-args
36 * -p topic_type:=left_camera_info
37 *
38 * topic_type defaults to "left_rgb_image"
39 *
40 * See individual callbacks for more detailed comments
41 */
42class StereoCameraTopicEcho : public rclcpp::Node {
43public:
44 StereoCameraTopicEcho() : Node("stereo_camera_topic_echo") {
45 // Select which topic type to subscribe to
46 topic_type_ =
47 declare_parameter<std::string>("topic_type", "left_rgb_image");
48 dump_video_path_ = declare_parameter<std::string>("dump_video_path", "");
49
50 // Subscribed topics and their message layouts
51 // 1. /aima/hal/sensor/stereo_head_front_left/rgb_image
52 // - topic_type: left_rgb_image
53 // - message type: sensor_msgs::msg::Image
54 // - frame_id: stereo_head_front
55 // - child_frame_id: /
56 // - contents: left camera raw image
57 // 2. /aima/hal/sensor/stereo_head_front_left/rgb_image/compressed
58 // - topic_type: left_rgb_image_compressed
59 // - message type: sensor_msgs::msg::CompressedImage
60 // - frame_id: stereo_head_front
61 // - contents: left camera compressed image
62 // 3. /aima/hal/sensor/stereo_head_front_left/camera_info
63 // - topic_type: left_camera_info
64 // - message type: sensor_msgs::msg::CameraInfo
65 // - frame_id: stereo_head_front
66 // - contents: left camera intrinsic parameters
67 // 4. /aima/hal/sensor/stereo_head_front_right/rgb_image
68 // - topic_type: right_rgb_image
69 // - message type: sensor_msgs::msg::Image
70 // - frame_id: stereo_head_front_right
71 // - child_frame_id: /
72 // - contents: right camera raw image
73 // 5. /aima/hal/sensor/stereo_head_front_right/rgb_image/compressed
74 // - topic_type: right_rgb_image_compressed
75 // - message type: sensor_msgs::msg::CompressedImage
76 // - frame_id: stereo_head_front_right
77 // - contents: right camera compressed image
78 // 6. /aima/hal/sensor/stereo_head_front_right/camera_info
79 // - topic_type: right_camera_info
80 // - message type: sensor_msgs::msg::CameraInfo
81 // - frame_id: stereo_head_front_right
82 // - contents: right camera intrinsic parameters
83
84 // Set QoS parameters - use SensorData QoS
85 auto qos = rclcpp::SensorDataQoS();
86
87 // Enable left camera RGB image subscription
88 if (topic_type_ == "left_rgb_image") {
89 topic_name_ = "/aima/hal/sensor/stereo_head_front_left/rgb_image";
90 sub_image_ = create_subscription<sensor_msgs::msg::Image>(
91 topic_name_, qos,
92 std::bind(&StereoCameraTopicEcho::cb_image, this,
93 std::placeholders::_1));
94 RCLCPP_INFO(get_logger(), "✅ Subscribing Left RGB Image: %s",
95 topic_name_.c_str());
96 if (!dump_video_path_.empty()) {
97 RCLCPP_INFO(get_logger(), "📝 Will dump received images to video: %s",
98 dump_video_path_.c_str());
99 }
100
101 // Enable left camera RGB compressed image subscription
102 } else if (topic_type_ == "left_rgb_image_compressed") {
103 topic_name_ =
104 "/aima/hal/sensor/stereo_head_front_left/rgb_image/compressed";
105 sub_compressed_ = create_subscription<sensor_msgs::msg::CompressedImage>(
106 topic_name_, qos,
107 std::bind(&StereoCameraTopicEcho::cb_compressed, this,
108 std::placeholders::_1));
109 RCLCPP_INFO(get_logger(), "✅ Subscribing Left CompressedImage: %s",
110 topic_name_.c_str());
111
112 // Enable left camera info subscription
113 } else if (topic_type_ == "left_camera_info") {
114
115 topic_name_ = "/aima/hal/sensor/stereo_head_front_left/camera_info";
116 // CameraInfo subscriptions must use reliable + transient_local
117 // QoS in order to receive latched/history messages (even if only one
118 // message was published). Here we use keep_last(1) + reliable
119 // + transient_local.
120 sub_camerainfo_ = create_subscription<sensor_msgs::msg::CameraInfo>(
121 topic_name_,
122 rclcpp::QoS(rclcpp::KeepLast(1)).reliable().transient_local(),
123 std::bind(&StereoCameraTopicEcho::cb_camerainfo, this,
124 std::placeholders::_1));
125 RCLCPP_INFO(get_logger(),
126 "✅ Subscribing Left CameraInfo (with transient_local): %s",
127 topic_name_.c_str());
128
129 // Enable right camera RGB image subscription
130 } else if (topic_type_ == "right_rgb_image") {
131 topic_name_ = "/aima/hal/sensor/stereo_head_front_right/rgb_image";
132 sub_image_ = create_subscription<sensor_msgs::msg::Image>(
133 topic_name_, qos,
134 std::bind(&StereoCameraTopicEcho::cb_image, this,
135 std::placeholders::_1));
136 RCLCPP_INFO(get_logger(), "✅ Subscribing Right RGB Image: %s",
137 topic_name_.c_str());
138 if (!dump_video_path_.empty()) {
139 RCLCPP_INFO(get_logger(), "📝 Will dump received images to video: %s",
140 dump_video_path_.c_str());
141 }
142
143 // Enable right camera RGB compressed image subscription
144 } else if (topic_type_ == "right_rgb_image_compressed") {
145 topic_name_ =
146 "/aima/hal/sensor/stereo_head_front_right/rgb_image/compressed";
147 sub_compressed_ = create_subscription<sensor_msgs::msg::CompressedImage>(
148 topic_name_, qos,
149 std::bind(&StereoCameraTopicEcho::cb_compressed, this,
150 std::placeholders::_1));
151 RCLCPP_INFO(get_logger(), "✅ Subscribing Right CompressedImage: %s",
152 topic_name_.c_str());
153
154 // Enable right camera info subscription
155 } else if (topic_type_ == "right_camera_info") {
156 topic_name_ = "/aima/hal/sensor/stereo_head_front_right/camera_info";
157 // CameraInfo subscriptions must use reliable + transient_local
158 // QoS in order to receive latched/history messages (even if only one
159 // message was published). Here we use keep_last(1) + reliable
160 // + transient_local.
161 sub_camerainfo_ = create_subscription<sensor_msgs::msg::CameraInfo>(
162 topic_name_,
163 rclcpp::QoS(rclcpp::KeepLast(1)).reliable().transient_local(),
164 std::bind(&StereoCameraTopicEcho::cb_camerainfo, this,
165 std::placeholders::_1));
166 RCLCPP_INFO(get_logger(),
167 "✅ Subscribing Right CameraInfo (with transient_local): %s",
168 topic_name_.c_str());
169
170 // Unknown topic_type error
171 } else {
172 RCLCPP_ERROR(get_logger(), "Unknown topic_type: %s", topic_type_.c_str());
173 throw std::runtime_error("Unknown topic_type");
174 }
175 }
176
177 ~StereoCameraTopicEcho() override {
178 if (video_writer_.isOpened()) {
179 video_writer_.release();
180 RCLCPP_INFO(get_logger(), "Video file closed.");
181 }
182 }
183
184private:
185 // Image callback (left/right RGB image)
186 void cb_image(const sensor_msgs::msg::Image::SharedPtr msg) {
187 update_arrivals();
188
189 if (should_print()) {
190 RCLCPP_INFO(get_logger(),
191 "📸 %s received\n"
192 " • frame_id: %s\n"
193 " • stamp (sec): %.6f\n"
194 " • encoding: %s\n"
195 " • size (WxH): %u x %u\n"
196 " • step (bytes/row):%u\n"
197 " • is_bigendian: %u\n"
198 " • recv FPS (1s): %.1f",
199 topic_type_.c_str(), msg->header.frame_id.c_str(),
200 rclcpp::Time(msg->header.stamp).seconds(),
201 msg->encoding.c_str(), msg->width, msg->height, msg->step,
202 msg->is_bigendian, get_fps());
203 }
204
205 // Video dump is supported only for RGB images
206 if ((topic_type_ == "left_rgb_image" || topic_type_ == "right_rgb_image") &&
207 !dump_video_path_.empty()) {
208 dump_image_to_video(msg);
209 }
210 }
211
212 // CompressedImage callback (left/right RGB compressed image)
213 void cb_compressed(const sensor_msgs::msg::CompressedImage::SharedPtr msg) {
214 update_arrivals();
215
216 if (should_print()) {
217 RCLCPP_INFO(get_logger(),
218 "🗜️ %s received\n"
219 " • frame_id: %s\n"
220 " • stamp (sec): %.6f\n"
221 " • format: %s\n"
222 " • data size: %zu\n"
223 " • recv FPS (1s): %.1f",
224 topic_type_.c_str(), msg->header.frame_id.c_str(),
225 rclcpp::Time(msg->header.stamp).seconds(),
226 msg->format.c_str(), msg->data.size(), get_fps());
227 }
228 }
229
230 // CameraInfo callback (left/right camera intrinsic parameters)
231 void cb_camerainfo(const sensor_msgs::msg::CameraInfo::SharedPtr msg) {
232 // CameraInfo is typically published once; print it once
233 std::ostringstream oss;
234 oss << "📷 " << topic_type_ << " received\n"
235 << " • frame_id: " << msg->header.frame_id << "\n"
236 << " • stamp (sec): " << rclcpp::Time(msg->header.stamp).seconds()
237 << "\n"
238 << " • width x height: " << msg->width << " x " << msg->height << "\n"
239 << " • distortion_model:" << msg->distortion_model << "\n"
240 << " • D: [";
241 for (size_t i = 0; i < msg->d.size(); ++i) {
242 oss << msg->d[i];
243 if (i + 1 < msg->d.size())
244 oss << ", ";
245 }
246 oss << "]\n • K: [";
247 for (int i = 0; i < 9; ++i) {
248 oss << msg->k[i];
249 if (i + 1 < 9)
250 oss << ", ";
251 }
252 oss << "]\n • R: [";
253 for (int i = 0; i < 9; ++i) {
254 oss << msg->r[i];
255 if (i + 1 < 9)
256 oss << ", ";
257 }
258 oss << "]\n • P: [";
259 for (int i = 0; i < 12; ++i) {
260 oss << msg->p[i];
261 if (i + 1 < 12)
262 oss << ", ";
263 }
264 oss << "]\n"
265 << " • binning_x: " << msg->binning_x << "\n"
266 << " • binning_y: " << msg->binning_y << "\n"
267 << " • roi: { x_offset: " << msg->roi.x_offset
268 << ", y_offset: " << msg->roi.y_offset
269 << ", height: " << msg->roi.height << ", width: " << msg->roi.width
270 << ", do_rectify: " << (msg->roi.do_rectify ? "true" : "false") << " }";
271 RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
272 }
273
274 // Track arrival timestamps to compute FPS
275 void update_arrivals() {
276 const rclcpp::Time now = this->get_clock()->now();
277 arrivals_.push_back(now);
278 while (!arrivals_.empty() && (now - arrivals_.front()).seconds() > 1.0) {
279 arrivals_.pop_front();
280 }
281 }
282 double get_fps() const { return static_cast<double>(arrivals_.size()); }
283
284 // Control printing frequency
285 bool should_print() {
286 const rclcpp::Time now = this->get_clock()->now();
287 if ((now - last_print_).seconds() >= 1.0) {
288 last_print_ = now;
289 return true;
290 }
291 return false;
292 }
293
294 // Dump received images to a video file (RGB images only)
295 void dump_image_to_video(const sensor_msgs::msg::Image::SharedPtr &msg) {
296 cv::Mat image;
297 try {
298 // Obtain the Mat without copying by not specifying encoding
299 cv_bridge::CvImageConstPtr cvp = cv_bridge::toCvShare(msg);
300 image = cvp->image;
301 // Convert to BGR for uniform saving
302 if (msg->encoding == "rgb8") {
303 cv::cvtColor(image, image, cv::COLOR_RGB2BGR);
304 } else {
305 RCLCPP_WARN(get_logger(), "image encoding not expected: %s",
306 msg->encoding.c_str());
307 return;
308 }
309 } catch (const std::exception &e) {
310 RCLCPP_WARN(get_logger(), "cv_bridge exception: %s", e.what());
311 return;
312 }
313
314 // Initialize VideoWriter
315 if (!video_writer_.isOpened()) {
316 int fourcc = cv::VideoWriter::fourcc('M', 'J', 'P', 'G');
317 double fps = std::max(1.0, get_fps());
318 bool ok = video_writer_.open(dump_video_path_, fourcc, fps,
319 cv::Size(image.cols, image.rows), true);
320 if (!ok) {
321 RCLCPP_ERROR(get_logger(), "Failed to open video file: %s",
322 dump_video_path_.c_str());
323 dump_video_path_.clear(); // stop trying
324 return;
325 }
326 RCLCPP_INFO(get_logger(), "VideoWriter started: %s, size=%dx%d, fps=%.1f",
327 dump_video_path_.c_str(), image.cols, image.rows, fps);
328 }
329 video_writer_.write(image);
330 }
331
332 // Member variables
333 std::string topic_type_;
334 std::string topic_name_;
335 std::string dump_video_path_;
336
337 // Subscriptions
338 rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr sub_image_;
339 rclcpp::Subscription<sensor_msgs::msg::CompressedImage>::SharedPtr
340 sub_compressed_;
341 rclcpp::Subscription<sensor_msgs::msg::CameraInfo>::SharedPtr sub_camerainfo_;
342
343 // FPS statistics
344 rclcpp::Time last_print_{0, 0, RCL_ROS_TIME};
345 std::deque<rclcpp::Time> arrivals_;
346
347 // Video writer
348 cv::VideoWriter video_writer_;
349};
350
351int main(int argc, char **argv) {
352 rclcpp::init(argc, argv);
353 auto node = std::make_shared<StereoCameraTopicEcho>();
354 rclcpp::spin(node);
355 rclcpp::shutdown();
356 return 0;
357}
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.avi
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
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 <deque>
2#include <filesystem>
3#include <iomanip>
4#include <memory>
5#include <rclcpp/rclcpp.hpp>
6#include <sensor_msgs/msg/camera_info.hpp>
7#include <sensor_msgs/msg/compressed_image.hpp>
8#include <sensor_msgs/msg/image.hpp>
9#include <sstream>
10#include <string>
11#include <vector>
12
13// OpenCV headers for image/video writing
14#include <cv_bridge/cv_bridge.h>
15#include <opencv2/opencv.hpp>
16
17/**
18 * @brief Example of subscribing to multiple topics for the rear head monocular
19 * camera
20 *
21 * You can select which topic type to subscribe to via the startup argument
22 * --ros-args -p topic_type:=<type>:
23 * - rgb_image: RGB image (sensor_msgs/Image)
24 * - rgb_image_compressed: RGB compressed image (sensor_msgs/CompressedImage)
25 * - camera_info: Camera intrinsic parameters (sensor_msgs/CameraInfo)
26 *
27 * Examples:
28 * ros2 run examples echo_camera_head_rear --ros-args -p topic_type:=rgb_image
29 * ros2 run examples echo_camera_head_rear --ros-args -p
30 * topic_type:=rgb_image_compressed ros2 run examples echo_camera_head_rear
31 * --ros-args -p topic_type:=camera_info
32 *
33 * topic_type defaults to "rgb_image"
34 *
35 * See individual callbacks for more detailed comments
36 */
37class HeadRearCameraTopicEcho : public rclcpp::Node {
38public:
39 HeadRearCameraTopicEcho() : Node("head_rear_camera_topic_echo") {
40 // Select which topic type to subscribe to
41 topic_type_ = declare_parameter<std::string>("topic_type", "rgb_image");
42 dump_video_path_ = declare_parameter<std::string>("dump_video_path", "");
43 with_mask_ = declare_parameter<bool>("with_mask", false);
44
45 // Subscribed topics and their message layouts
46 // 1. /aima/hal/sensor/rgb_head_rear/rgb_image
47 // - topic_type: rgb_image
48 // - message type: sensor_msgs::msg::Image
49 // - frame_id: rgb_head_rear
50 // - child_frame_id: /
51 // - contents: raw image data
52 // 2. /aima/hal/sensor/rgb_head_rear/rgb_image/compressed
53 // - topic_type: rgb_image_compressed
54 // - message type: sensor_msgs::msg::CompressedImage
55 // - frame_id: rgb_head_rear
56 // - contents: compressed image data
57 // 3. /aima/hal/sensor/rgb_head_rear/camera_info
58 // - topic_type: camera_info
59 // - message type: sensor_msgs::msg::CameraInfo
60 // - frame_id: rgb_head_rear
61 // - contents: camera intrinsic parameters
62
63 // Set QoS parameters - use SensorData QoS
64 auto qos = rclcpp::SensorDataQoS();
65
66 if (with_mask_ && !dump_video_path_.empty()) {
67 auto mask_path =
68 std::filesystem::read_symlink("/proc/self/exe").parent_path() /
69 "data" / "rgb_head_rear_mask.png";
70 mask_image_ = cv::imread(mask_path, cv::IMREAD_GRAYSCALE);
71 if (mask_image_.empty()) {
72 RCLCPP_ERROR(get_logger(), "Failed to load mask file from %s",
73 mask_path.c_str());
74 throw std::runtime_error("Failed to load mask file");
75 }
76 }
77
78 // Enable RGB image subscription
79 if (topic_type_ == "rgb_image") {
80 topic_name_ = "/aima/hal/sensor/rgb_head_rear/rgb_image";
81 sub_image_ = create_subscription<sensor_msgs::msg::Image>(
82 topic_name_, qos,
83 std::bind(&HeadRearCameraTopicEcho::cb_image, this,
84 std::placeholders::_1));
85 RCLCPP_INFO(get_logger(), "✅ Subscribing RGB Image: %s",
86 topic_name_.c_str());
87 if (!dump_video_path_.empty()) {
88 RCLCPP_INFO(
89 get_logger(), "📝 Will dump received images %s mask to video: %s",
90 (with_mask_ ? "with" : "without"), dump_video_path_.c_str());
91 }
92 }
93
94 // Enable RGB compressed image subscription
95 else if (topic_type_ == "rgb_image_compressed") {
96 topic_name_ = "/aima/hal/sensor/rgb_head_rear/rgb_image/compressed";
97 sub_compressed_ = create_subscription<sensor_msgs::msg::CompressedImage>(
98 topic_name_, qos,
99 std::bind(&HeadRearCameraTopicEcho::cb_compressed, this,
100 std::placeholders::_1));
101 RCLCPP_INFO(get_logger(), "✅ Subscribing CompressedImage: %s",
102 topic_name_.c_str());
103
104 // Enable camera info subscription
105 } else if (topic_type_ == "camera_info") {
106 topic_name_ = "/aima/hal/sensor/rgb_head_rear/camera_info";
107 // CameraInfo subscriptions must use reliable + transient_local
108 // QoS in order to receive latched/history messages (even if only one
109 // message was published). Here we use keep_last(1) + reliable
110 // + transient_local.
111 sub_camerainfo_ = create_subscription<sensor_msgs::msg::CameraInfo>(
112 topic_name_,
113 rclcpp::QoS(rclcpp::KeepLast(1)).reliable().transient_local(),
114 std::bind(&HeadRearCameraTopicEcho::cb_camerainfo, this,
115 std::placeholders::_1));
116 RCLCPP_INFO(get_logger(),
117 "✅ Subscribing CameraInfo (with transient_local): %s",
118 topic_name_.c_str());
119
120 // Unknown topic_type error
121 } else {
122 RCLCPP_ERROR(get_logger(), "Unknown topic_type: %s", topic_type_.c_str());
123 throw std::runtime_error("Unknown topic_type");
124 }
125 }
126
127 ~HeadRearCameraTopicEcho() override {
128 if (video_writer_.isOpened()) {
129 video_writer_.release();
130 RCLCPP_INFO(get_logger(), "Video file closed.");
131 }
132 }
133
134private:
135 // Image callback (RGB image)
136 void cb_image(const sensor_msgs::msg::Image::SharedPtr msg) {
137 update_arrivals();
138
139 if (should_print()) {
140 RCLCPP_INFO(get_logger(),
141 "📸 %s received\n"
142 " • frame_id: %s\n"
143 " • stamp (sec): %.6f\n"
144 " • encoding: %s\n"
145 " • size (WxH): %u x %u\n"
146 " • step (bytes/row):%u\n"
147 " • is_bigendian: %u\n"
148 " • recv FPS (1s): %.1f",
149 topic_type_.c_str(), msg->header.frame_id.c_str(),
150 rclcpp::Time(msg->header.stamp).seconds(),
151 msg->encoding.c_str(), msg->width, msg->height, msg->step,
152 msg->is_bigendian, get_fps());
153 }
154
155 // Video dump is supported only for RGB images
156 if (topic_type_ == "rgb_image" && !dump_video_path_.empty()) {
157 dump_image_to_video(msg);
158 }
159 }
160
161 // CompressedImage callback (RGB compressed image)
162 void cb_compressed(const sensor_msgs::msg::CompressedImage::SharedPtr msg) {
163 update_arrivals();
164
165 if (should_print()) {
166 RCLCPP_INFO(get_logger(),
167 "🗜️ %s received\n"
168 " • frame_id: %s\n"
169 " • stamp (sec): %.6f\n"
170 " • format: %s\n"
171 " • data size: %zu\n"
172 " • recv FPS (1s): %.1f",
173 topic_type_.c_str(), msg->header.frame_id.c_str(),
174 rclcpp::Time(msg->header.stamp).seconds(),
175 msg->format.c_str(), msg->data.size(), get_fps());
176 }
177 }
178
179 // CameraInfo callback (camera intrinsic parameters)
180 void cb_camerainfo(const sensor_msgs::msg::CameraInfo::SharedPtr msg) {
181 // CameraInfo is typically published once; print it once
182 std::ostringstream oss;
183 oss << "📷 " << topic_type_ << " received\n"
184 << " • frame_id: " << msg->header.frame_id << "\n"
185 << " • stamp (sec): " << rclcpp::Time(msg->header.stamp).seconds()
186 << "\n"
187 << " • width x height: " << msg->width << " x " << msg->height << "\n"
188 << " • distortion_model:" << msg->distortion_model << "\n"
189 << " • D: [";
190 for (size_t i = 0; i < msg->d.size(); ++i) {
191 oss << msg->d[i];
192 if (i + 1 < msg->d.size())
193 oss << ", ";
194 }
195 oss << "]\n • K: [";
196 for (int i = 0; i < 9; ++i) {
197 oss << msg->k[i];
198 if (i + 1 < 9)
199 oss << ", ";
200 }
201 oss << "]\n • R: [";
202 for (int i = 0; i < 9; ++i) {
203 oss << msg->r[i];
204 if (i + 1 < 9)
205 oss << ", ";
206 }
207 oss << "]\n • P: [";
208 for (int i = 0; i < 12; ++i) {
209 oss << msg->p[i];
210 if (i + 1 < 12)
211 oss << ", ";
212 }
213 oss << "]\n"
214 << " • binning_x: " << msg->binning_x << "\n"
215 << " • binning_y: " << msg->binning_y << "\n"
216 << " • roi: { x_offset: " << msg->roi.x_offset
217 << ", y_offset: " << msg->roi.y_offset
218 << ", height: " << msg->roi.height << ", width: " << msg->roi.width
219 << ", do_rectify: " << (msg->roi.do_rectify ? "true" : "false") << " }";
220 RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
221 }
222
223 // Track arrival timestamps to compute FPS
224 void update_arrivals() {
225 const rclcpp::Time now = this->get_clock()->now();
226 arrivals_.push_back(now);
227 while (!arrivals_.empty() && (now - arrivals_.front()).seconds() > 1.0) {
228 arrivals_.pop_front();
229 }
230 }
231 double get_fps() const { return static_cast<double>(arrivals_.size()); }
232
233 // Control printing frequency
234 bool should_print() {
235 const rclcpp::Time now = this->get_clock()->now();
236 if ((now - last_print_).seconds() >= 1.0) {
237 last_print_ = now;
238 return true;
239 }
240 return false;
241 }
242
243 // Dump received images to a video file (RGB images only)
244 void dump_image_to_video(const sensor_msgs::msg::Image::SharedPtr &msg) {
245 cv::Mat image;
246 try {
247 // Obtain the Mat without copying by not specifying encoding
248 cv_bridge::CvImageConstPtr cvp = cv_bridge::toCvShare(msg);
249 image = cvp->image;
250 // Convert to BGR for uniform saving
251 if (msg->encoding == "rgb8") {
252 cv::cvtColor(image, image, cv::COLOR_RGB2BGR);
253 } else {
254 RCLCPP_WARN(get_logger(), "image encoding not expected: %s",
255 msg->encoding.c_str());
256 return;
257 }
258 if (with_mask_) {
259 image.setTo(cv::Scalar(0, 0, 0), mask_image_ == 0);
260 }
261 } catch (const std::exception &e) {
262 RCLCPP_WARN(get_logger(), "cv_bridge exception: %s", e.what());
263 return;
264 }
265
266 // Initialize VideoWriter
267 if (!video_writer_.isOpened()) {
268 int fourcc = cv::VideoWriter::fourcc('M', 'J', 'P', 'G');
269 double fps = std::max(1.0, get_fps());
270 bool ok = video_writer_.open(dump_video_path_, fourcc, fps,
271 cv::Size(image.cols, image.rows), true);
272 if (!ok) {
273 RCLCPP_ERROR(get_logger(), "Failed to open video file: %s",
274 dump_video_path_.c_str());
275 dump_video_path_.clear(); // stop trying
276 return;
277 }
278 RCLCPP_INFO(get_logger(), "VideoWriter started: %s, size=%dx%d, fps=%.1f",
279 dump_video_path_.c_str(), image.cols, image.rows, fps);
280 }
281 video_writer_.write(image);
282 }
283
284 // Member variables
285 std::string topic_type_;
286 std::string topic_name_;
287 std::string dump_video_path_;
288 bool with_mask_;
289 cv::Mat mask_image_;
290
291 // Subscriptions
292 rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr sub_image_;
293 rclcpp::Subscription<sensor_msgs::msg::CompressedImage>::SharedPtr
294 sub_compressed_;
295 rclcpp::Subscription<sensor_msgs::msg::CameraInfo>::SharedPtr sub_camerainfo_;
296
297 // FPS statistics
298 rclcpp::Time last_print_{0, 0, RCL_ROS_TIME};
299 std::deque<rclcpp::Time> arrivals_;
300
301 // Video writer
302 cv::VideoWriter video_writer_;
303};
304
305int main(int argc, char **argv) {
306 rclcpp::init(argc, argv);
307 auto node = std::make_shared<HeadRearCameraTopicEcho>();
308 rclcpp::spin(node);
309 rclcpp::shutdown();
310 return 0;
311}
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.avi
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.avi
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
8class TouchStateSubscriber : public rclcpp::Node {
9public:
10 TouchStateSubscriber() : Node("touch_state_subscriber") {
11 subscription_ = this->create_subscription<aimdk_msgs::msg::TouchState>(
12 "/aima/hal/sensor/touch_head", 10,
13 std::bind(&TouchStateSubscriber::touch_callback, this,
14 std::placeholders::_1));
15
16 RCLCPP_INFO(this->get_logger(), "TouchState subscriber started, listening "
17 "to /aima/hal/sensor/touch_head");
18 }
19
20private:
21 void touch_callback(const aimdk_msgs::msg::TouchState::SharedPtr msg) {
22 // print message info
23 RCLCPP_INFO(this->get_logger(), "Received TouchState message:");
24 RCLCPP_INFO(this->get_logger(), " Timestamp: %d.%09d",
25 msg->header.stamp.sec, msg->header.stamp.nanosec);
26
27 std::string event_str = get_event_type_string(msg->event_type);
28 RCLCPP_INFO(this->get_logger(), " Event Type: %s (%d)", event_str.c_str(),
29 msg->event_type);
30 }
31
32 std::string get_event_type_string(uint8_t event_type) {
33 switch (event_type) {
34 case aimdk_msgs::msg::TouchState::UNKNOWN:
35 return "UNKNOWN";
36 case aimdk_msgs::msg::TouchState::IDLE:
37 return "IDLE";
38 case aimdk_msgs::msg::TouchState::TOUCH:
39 return "TOUCH";
40 case aimdk_msgs::msg::TouchState::SLIDE:
41 return "SLIDE";
42 case aimdk_msgs::msg::TouchState::PAT_ONCE:
43 return "PAT_ONCE";
44 case aimdk_msgs::msg::TouchState::PAT_TWICE:
45 return "PAT_TWICE";
46 case aimdk_msgs::msg::TouchState::PAT_TRIPLE:
47 return "PAT_TRIPLE";
48 default:
49 return "INVALID";
50 }
51 }
52 rclcpp::Subscription<aimdk_msgs::msg::TouchState>::SharedPtr subscription_;
53};
54
55int main(int argc, char *argv[]) {
56 rclcpp::init(argc, argv);
57 auto node = std::make_shared<TouchStateSubscriber>();
58 rclcpp::spin(node);
59 rclcpp::shutdown();
60 return 0;
61}
Usage Instructions:
ros2 run examples echo_head_touch_sensor
Example Output:
[INFO] [1769162721.359354722] [touch_state_subscriber]: Timestamp: 1769162726.863282315
[INFO] [1769162721.359361643] [touch_state_subscriber]: Event Type: IDLE (1)
[INFO] [1769167184.142143492] [touch_state_subscriber]: Timestamp: 1769167189.364879133
[INFO] [1769167184.142147126] [touch_state_subscriber]: Event Type: TOUCH (2)
6.2.16 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 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 auto qos = rclcpp::SensorDataQoS();
37
38 if (topic_type_ == "pointcloud") {
39 topic_name_ = "/aima/hal/sensor/lidar_chest_front/lidar_pointcloud";
40 sub_pointcloud_ = create_subscription<sensor_msgs::msg::PointCloud2>(
41 topic_name_, qos,
42 std::bind(&LidarChestEcho::cb_pointcloud, this,
43 std::placeholders::_1));
44 RCLCPP_INFO(get_logger(), "✅ Subscribing LIDAR PointCloud2: %s",
45 topic_name_.c_str());
46 } else if (topic_type_ == "imu") {
47 topic_name_ = "/aima/hal/sensor/lidar_chest_front/imu";
48 sub_imu_ = create_subscription<sensor_msgs::msg::Imu>(
49 topic_name_, qos,
50 std::bind(&LidarChestEcho::cb_imu, this, std::placeholders::_1));
51 RCLCPP_INFO(get_logger(), "✅ Subscribing LIDAR IMU: %s",
52 topic_name_.c_str());
53 } else {
54 RCLCPP_ERROR(get_logger(), "Unknown topic_type: %s", topic_type_.c_str());
55 throw std::runtime_error("Unknown topic_type");
56 }
57 }
58
59private:
60 // PointCloud2 callback
61 void cb_pointcloud(const sensor_msgs::msg::PointCloud2::SharedPtr msg) {
62 update_arrivals();
63
64 if (should_print()) {
65 std::ostringstream oss;
66 oss << "🟢 LIDAR PointCloud2 received\n"
67 << " • frame_id: " << msg->header.frame_id << "\n"
68 << " • stamp (sec): "
69 << rclcpp::Time(msg->header.stamp).seconds() << "\n"
70 << " • width x height: " << msg->width << " x " << msg->height
71 << "\n"
72 << " • point_step: " << msg->point_step << "\n"
73 << " • row_step: " << msg->row_step << "\n"
74 << " • fields: ";
75 for (const auto &f : msg->fields)
76 oss << f.name << "(" << (int)f.datatype << ") ";
77 oss << "\n • is_bigendian: " << msg->is_bigendian
78 << "\n • is_dense: " << msg->is_dense
79 << "\n • data size: " << msg->data.size()
80 << "\n • recv FPS (1s): " << get_fps();
81 RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
82 }
83 }
84
85 // IMU callback
86 void cb_imu(const sensor_msgs::msg::Imu::SharedPtr msg) {
87 update_arrivals();
88
89 if (should_print()) {
90 std::ostringstream oss;
91 oss << "🟢 LIDAR IMU received\n"
92 << " • frame_id: " << msg->header.frame_id << "\n"
93 << " • stamp (sec): "
94 << rclcpp::Time(msg->header.stamp).seconds() << "\n"
95 << " • orientation: [" << msg->orientation.x << ", "
96 << msg->orientation.y << ", " << msg->orientation.z << ", "
97 << msg->orientation.w << "]\n"
98 << " • angular_velocity:[" << msg->angular_velocity.x << ", "
99 << msg->angular_velocity.y << ", " << msg->angular_velocity.z << "]\n"
100 << " • linear_accel: [" << msg->linear_acceleration.x << ", "
101 << msg->linear_acceleration.y << ", " << msg->linear_acceleration.z
102 << "]\n"
103 << " • recv FPS (1s): " << get_fps();
104 RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
105 }
106 }
107
108 // Update FPS statistics
109 void update_arrivals() {
110 const rclcpp::Time now = this->get_clock()->now();
111 arrivals_.push_back(now);
112 while (!arrivals_.empty() && (now - arrivals_.front()).seconds() > 1.0) {
113 arrivals_.pop_front();
114 }
115 }
116 double get_fps() const { return static_cast<double>(arrivals_.size()); }
117
118 // Control print frequency
119 bool should_print() {
120 const rclcpp::Time now = this->get_clock()->now();
121 if ((now - last_print_).seconds() >= 1.0) {
122 last_print_ = now;
123 return true;
124 }
125 return false;
126 }
127
128 // Member variables
129 std::string topic_type_;
130 std::string topic_name_;
131
132 rclcpp::Subscription<sensor_msgs::msg::PointCloud2>::SharedPtr
133 sub_pointcloud_;
134 rclcpp::Subscription<sensor_msgs::msg::Imu>::SharedPtr sub_imu_;
135
136 rclcpp::Time last_print_{0, 0, RCL_ROS_TIME};
137 std::deque<rclcpp::Time> arrivals_;
138};
139
140int main(int argc, char **argv) {
141 rclcpp::init(argc, argv);
142 auto node = std::make_shared<LidarChestEcho>();
143 rclcpp::spin(node);
144 rclcpp::shutdown();
145 return 0;
146}
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
Example Output:
[INFO] [lidar_chest_echo]: ✅ Subscribing LIDAR PointCloud2: /aima/hal/sensor/lidar_chest_front/lidar_pointcloud
[INFO] [lidar_chest_echo]: 🟢 LIDAR PointCloud2 received
• frame_id: lidar_chest_front
• stamp (sec): 1234567890.123456
• width x height: 1 x 36000
• point_step: 16
• row_step: 16
• fields: x(7) y(7) z(7) intensity(7)
• is_bigendian: False
• is_dense: True
• data size: 576000
• recv FPS (1s): 10.0
6.2.17 Play Video
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 <chrono>
5#include <memory>
6#include <signal.h>
7#include <string>
8
9// Global variable used for signal handling
10std::shared_ptr<rclcpp::Node> g_node = nullptr;
11
12// Signal handler function
13void signal_handler(int signal) {
14 if (g_node) {
15 RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
16 signal);
17 g_node.reset();
18 }
19 rclcpp::shutdown();
20 exit(signal);
21}
22
23class PlayVideoClient : public rclcpp::Node {
24public:
25 PlayVideoClient() : Node("play_video_client") {
26 client_ = this->create_client<aimdk_msgs::srv::PlayVideo>(
27 "/face_ui_proxy/play_video");
28 RCLCPP_INFO(this->get_logger(), "✅ PlayVideo client node started.");
29
30 // Wait for the service to become available
31 while (!client_->wait_for_service(std::chrono::seconds(2))) {
32 RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
33 }
34 RCLCPP_INFO(this->get_logger(),
35 "🟢 Service available, ready to send request.");
36 }
37
38 bool send_request(const std::string &video_path, uint8_t mode,
39 int32_t priority) {
40 try {
41 auto request = std::make_shared<aimdk_msgs::srv::PlayVideo::Request>();
42
43 request->video_path = video_path;
44 request->mode = mode;
45 request->priority = priority;
46
47 RCLCPP_INFO(this->get_logger(),
48 "📨 Sending request to play video: mode=%hhu video=%s", mode,
49 video_path.c_str());
50
51 const std::chrono::milliseconds timeout(250);
52 for (int i = 0; i < 8; i++) {
53 request->header.header.stamp = this->now();
54 auto future = client_->async_send_request(request);
55 auto retcode = rclcpp::spin_until_future_complete(shared_from_this(),
56 future, timeout);
57 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
58 // retry as remote peer is NOT handled well by ROS
59 RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
60 continue;
61 }
62 // future.done
63 auto response = future.get();
64 if (response->success) {
65 RCLCPP_INFO(this->get_logger(),
66 "✅ Request to play video recorded successfully: %s",
67 response->message.c_str());
68 return true;
69 } else {
70 RCLCPP_ERROR(this->get_logger(),
71 "❌ Failed to record play-video request: %s",
72 response->message.c_str());
73 return false;
74 }
75 }
76 RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
77 return false;
78 } catch (const std::exception &e) {
79 RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
80 return false;
81 }
82 }
83
84private:
85 rclcpp::Client<aimdk_msgs::srv::PlayVideo>::SharedPtr client_;
86};
87
88int main(int argc, char **argv) {
89 try {
90 rclcpp::init(argc, argv);
91
92 // Set up signal handlers
93 signal(SIGINT, signal_handler);
94 signal(SIGTERM, signal_handler);
95
96 std::string video_path =
97 "/agibot/data/home/agi/zhiyuan.mp4"; // Default video path; modify as
98 // needed
99 int32_t priority = 5;
100 int mode = 2; // Loop playback
101 std::cout << "Enter video play mode (1: once, 2: loop): ";
102 std::cin >> mode;
103 if (mode < 1 || mode > 2) {
104 RCLCPP_ERROR(rclcpp::get_logger("main"), "Invalid play mode: %d", mode);
105 rclcpp::shutdown();
106 return 1;
107 }
108
109 g_node = std::make_shared<PlayVideoClient>();
110 auto client = std::dynamic_pointer_cast<PlayVideoClient>(g_node);
111
112 if (client) {
113 client->send_request(video_path, mode, priority);
114 }
115
116 // Clean up resources
117 g_node.reset();
118 rclcpp::shutdown();
119
120 return 0;
121 } catch (const std::exception &e) {
122 RCLCPP_ERROR(rclcpp::get_logger("main"),
123 "Program exited with exception: %s", e.what());
124 return 1;
125 }
126}
6.2.18 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, allowing playback priority configuration
Supports custom file paths and playback parameters
Provides complete error handling and playback status feedback
Technical Implementation:
Uses the PlayAudioFile service to play audio files
Supports priority settings (priority: 1–10, priority_weight: 1–100)
Provides detailed playback status feedback
Application Scenarios:
Audio playback and media control
Voice prompts and sound effects 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/srv/play_audio_file.hpp>
2#include <filesystem>
3#include <iostream>
4#include <rclcpp/rclcpp.hpp>
5#include <string>
6
7using PlayAudioFile = aimdk_msgs::srv::PlayAudioFile;
8
9int main(int argc, char **argv) {
10 rclcpp::init(argc, argv);
11 auto node = rclcpp::Node::make_shared("play_audio_file_client_min");
12
13 // 1) Service name
14 const std::string service_name = "/aimdk_5Fmsgs/srv/PlayAudioFile";
15 auto client = node->create_client<PlayAudioFile>(service_name);
16
17 // 2) Input file path (prompt user if not provided as argument)
18 std::string default_file =
19 "/agibot/data/var/interaction/tts_cache/normal/demo.wav";
20 std::string file_name;
21
22 if (argc > 1) {
23 file_name = argv[1];
24 } else {
25 std::cout << "Enter the media file path to play (default: " << default_file
26 << "): ";
27 if (!std::getline(std::cin, file_name) || !rclcpp::ok()) {
28 std::cout << std::endl;
29 rclcpp::shutdown();
30 return 1;
31 }
32 if (file_name.empty()) {
33 file_name = default_file;
34 }
35 }
36
37 // 3) Build the request
38 // Check if Ctrl+C was pressed during user input
39 if (!rclcpp::ok()) {
40 rclcpp::shutdown();
41 return 1;
42 }
43
44 auto req = std::make_shared<PlayAudioFile::Request>();
45 req->file.pkg_name = "demo_client";
46 req->file.file_name = std::filesystem::path(file_name).filename().string();
47 req->file.file_path =
48 std::filesystem::path(file_name).parent_path().string() + "/";
49 req->file.priority = 6;
50 req->file.priority_weight = 0;
51
52 // 4) Wait for service and call
53 RCLCPP_INFO(rclcpp::get_logger("main"), "Waiting for service: %s",
54 service_name.c_str());
55 while (!client->wait_for_service(std::chrono::seconds(2))) {
56 if (!rclcpp::ok()) {
57 RCLCPP_ERROR(node->get_logger(),
58 "Interrupted while waiting for the service. Exiting.");
59 rclcpp::shutdown();
60 return 1;
61 }
62 RCLCPP_INFO(node->get_logger(), "⏳ Service unavailable, waiting...");
63 }
64
65 rclcpp::FutureReturnCode rc = rclcpp::FutureReturnCode::TIMEOUT;
66 std::shared_future<PlayAudioFile::Response::SharedPtr> future;
67 for (int i = 0; i < 5; ++i) {
68 req->request.header.stamp = node->now();
69 future = client->async_send_request(req);
70 rc = rclcpp::spin_until_future_complete(node, future,
71 std::chrono::seconds(1));
72 if (rc == rclcpp::FutureReturnCode::SUCCESS) {
73 break;
74 }
75 if (rc == rclcpp::FutureReturnCode::INTERRUPTED) {
76 // Check if Ctrl+C was pressed during spin_until_future_complete
77 if (rclcpp::ok()) {
78 RCLCPP_WARN(node->get_logger(), "Interrupted while waiting");
79 }
80 rclcpp::shutdown();
81 return 1;
82 }
83 // retry as remote peer is NOT handled well by ROS
84 RCLCPP_INFO(node->get_logger(), "trying ... [%d]", i);
85 }
86
87 if (rc != rclcpp::FutureReturnCode::SUCCESS) {
88 // Check if Ctrl+C was pressed, avoid using node after shutdown
89 if (rclcpp::ok()) {
90 RCLCPP_ERROR(node->get_logger(), "Call timed out or did not complete");
91 }
92 rclcpp::shutdown();
93 return 1;
94 }
95
96 // 5) Handle response (success is in reponse.status)
97 try {
98 const auto resp = future.get();
99 bool success = resp->reponse.status.value == 1;
100
101 if (success) {
102 RCLCPP_INFO(node->get_logger(), "✅ Audio file play request succeeded: %s",
103 file_name.c_str());
104 } else {
105 RCLCPP_ERROR(node->get_logger(), "❌ Audio file play request failed: %s",
106 file_name.c_str());
107 }
108 } catch (const std::exception &e) {
109 RCLCPP_ERROR(node->get_logger(), "Call exception: %s", e.what());
110 }
111
112 rclcpp::shutdown();
113 return 0;
114}
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/interaction/tts_cache/normal/demo.wav
Example Output:
[INFO] [play_audio_file_client_min]: ✅ Audio file play request succeeded
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.19 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 full 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 <iostream>
4#include <rclcpp/rclcpp.hpp>
5#include <string>
6
7using PlayTTS = aimdk_msgs::srv::PlayTts;
8
9int main(int argc, char **argv) {
10 rclcpp::init(argc, argv);
11 auto node = rclcpp::Node::make_shared("play_tts_client_min");
12
13 const std::string service_name = "/aimdk_5Fmsgs/srv/PlayTts";
14 auto client = node->create_client<PlayTTS>(service_name);
15
16 // Get text to speak
17 std::string tts_text;
18 if (argc > 1) {
19 tts_text = argv[1];
20 } else {
21 std::cout << "Enter text to speak: ";
22 if (!std::getline(std::cin, tts_text) || !rclcpp::ok()) {
23 std::cout << std::endl;
24 rclcpp::shutdown();
25 return 1;
26 }
27 if (tts_text.empty()) {
28 tts_text = "Hello, I am AgiBot X2.";
29 }
30 }
31
32 if (!rclcpp::ok()) {
33 rclcpp::shutdown();
34 return 1;
35 }
36
37 auto req = std::make_shared<PlayTTS::Request>();
38 req->header.header.stamp = node->now();
39 req->tts_req.text = tts_text;
40 req->tts_req.domain = "demo_client"; // Required: identifies the caller
41 req->tts_req.trace_id =
42 "demo"; // Optional: request identifier for the TTS request
43 req->tts_req.is_interrupted =
44 true; // Required: whether to interrupt same-priority playback
45 req->tts_req.priority_weight = 0;
46 req->tts_req.priority_level.value = 6;
47
48 if (!client->wait_for_service(
49 std::chrono::duration_cast<std::chrono::seconds>(
50 std::chrono::seconds(5)))) {
51 if (rclcpp::ok()) {
52 RCLCPP_ERROR(node->get_logger(), "Service unavailable: %s",
53 service_name.c_str());
54 }
55 rclcpp::shutdown();
56 return 1;
57 }
58
59 auto future = client->async_send_request(req);
60 if (rclcpp::spin_until_future_complete(
61 node, future,
62 std::chrono::duration_cast<std::chrono::seconds>(
63 std::chrono::seconds(10))) != rclcpp::FutureReturnCode::SUCCESS) {
64 if (rclcpp::ok()) {
65 RCLCPP_ERROR(node->get_logger(), "Call timed out");
66 }
67 rclcpp::shutdown();
68 return 1;
69 }
70
71 const auto resp = future.get();
72 if (resp->tts_resp.is_success) {
73 RCLCPP_INFO(node->get_logger(), "✅ TTS play request succeeded");
74 } else {
75 RCLCPP_ERROR(node->get_logger(), "❌ TTS play request failed");
76 }
77
78 rclcpp::shutdown();
79 return 0;
80}
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] [play_tts_client_min]: ✅ TTS request succeeded
Notes
Ensure the TTS service is running properly
Supports both Chinese and English text-to-speech
Priority settings affect playback queue order
Interruption mechanism can stop the currently playing speech
Interface Reference
Service:
/aimdk_5Fmsgs/srv/PlayTtsMessage:
aimdk_msgs/srv/PlayTts
6.2.20 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
Stores files organized by timestamp and audio stream ID
Supports audio duration calculation and statistical output
VAD State Description:
0: No speech1: Speech start2: Speech processing3: Speech end
1#include <aimdk_msgs/msg/audio_vad_state_type.hpp>
2#include <aimdk_msgs/msg/processed_audio_output.hpp>
3#include <chrono>
4#include <ctime>
5#include <filesystem>
6#include <fstream>
7#include <iomanip>
8#include <rclcpp/rclcpp.hpp>
9#include <sstream>
10#include <string>
11#include <unordered_map>
12#include <vector>
13
14namespace fs = std::filesystem;
15
16class AudioSubscriber : public rclcpp::Node {
17public:
18 AudioSubscriber() : rclcpp::Node("audio_subscriber") {
19 // Audio buffers, stored separately by stream_id
20 // stream_id -> buffer
21 audio_buffers_ = {};
22 recording_state_ = {};
23
24 audio_output_dir_ = "audio_recordings";
25 fs::create_directories(audio_output_dir_);
26
27 // Note: deep queue to avoid missing data in a burst at start of VAD.
28 auto qos = rclcpp::QoS(
29 rclcpp::QoSInitialization::from_rmw(rmw_qos_profile_sensor_data));
30 qos.keep_last(500).best_effort();
31
32 subscription_ =
33 this->create_subscription<aimdk_msgs::msg::ProcessedAudioOutput>(
34 "/agent/process_audio_output", qos,
35 std::bind(&AudioSubscriber::audio_callback, this,
36 std::placeholders::_1));
37
38 RCLCPP_INFO(this->get_logger(),
39 "Starting to subscribe to denoised audio data...");
40 }
41
42private:
43 void
44 audio_callback(const aimdk_msgs::msg::ProcessedAudioOutput::SharedPtr msg) {
45 try {
46 uint32_t stream_id = msg->stream_id;
47 uint8_t vad_state = msg->audio_vad_state.value;
48 const std::vector<uint8_t> &audio_data = msg->audio_data;
49
50 static const std::unordered_map<uint8_t, std::string> vad_state_names = {
51 {0, "No Speech"},
52 {1, "Speech Start"},
53 {2, "Speech Processing"},
54 {3, "Speech End"}};
55 static const std::unordered_map<uint32_t, std::string> stream_names = {
56 {1, "Internal Microphone"}, {2, "External Microphone"}};
57
58 RCLCPP_INFO(this->get_logger(),
59 "Audio data received: stream_id=%u, vad_state=%u(%s), "
60 "audio_size=%zu bytes",
61 stream_id, vad_state,
62 vad_state_names.count(vad_state)
63 ? vad_state_names.at(vad_state).c_str()
64 : "Unknown State",
65 audio_data.size());
66
67 handle_vad_state(stream_id, vad_state, audio_data);
68 } catch (const std::exception &e) {
69 RCLCPP_ERROR(this->get_logger(), "Error processing audio message: %s",
70 e.what());
71 }
72 }
73
74 void handle_vad_state(uint32_t stream_id, uint8_t vad_state,
75 const std::vector<uint8_t> &audio_data) {
76 // Initialize the buffer for this stream_id (if it does not exist)
77 if (audio_buffers_.count(stream_id) == 0) {
78 audio_buffers_[stream_id] = std::vector<uint8_t>();
79 recording_state_[stream_id] = false;
80 }
81
82 static const std::unordered_map<uint8_t, std::string> vad_state_names = {
83 {0, "No Speech"},
84 {1, "Speech Start"},
85 {2, "Speech Processing"},
86 {3, "Speech End"}};
87 static const std::unordered_map<uint32_t, std::string> stream_names = {
88 {1, "Internal Microphone"}, {2, "External Microphone"}};
89
90 RCLCPP_INFO(this->get_logger(), "[%s] VAD Atate: %s Audio Data: %zu bytes",
91 stream_names.count(stream_id)
92 ? stream_names.at(stream_id).c_str()
93 : ("Unknown Stream " + std::to_string(stream_id)).c_str(),
94 vad_state_names.count(vad_state)
95 ? vad_state_names.at(vad_state).c_str()
96 : ("Unknown State" + std::to_string(vad_state)).c_str(),
97 audio_data.size());
98
99 // AUDIO_VAD_STATE_BEGIN
100 if (vad_state == 1) {
101 RCLCPP_INFO(this->get_logger(), "🎤 Speech detected - Start");
102 if (recording_state_[stream_id] == false) {
103 audio_buffers_[stream_id].clear();
104 recording_state_[stream_id] = true;
105 }
106 if (!audio_data.empty()) {
107 audio_buffers_[stream_id].insert(audio_buffers_[stream_id].end(),
108 audio_data.begin(), audio_data.end());
109 }
110
111 // AUDIO_VAD_STATE_PROCESSING
112 } else if (vad_state == 2) {
113 RCLCPP_INFO(this->get_logger(), "🔄 Speech Processing...");
114 if (recording_state_[stream_id] && !audio_data.empty()) {
115 audio_buffers_[stream_id].insert(audio_buffers_[stream_id].end(),
116 audio_data.begin(), audio_data.end());
117 }
118
119 // AUDIO_VAD_STATE_END
120 } else if (vad_state == 3) {
121 RCLCPP_INFO(this->get_logger(), "✅ Speech End");
122 if (recording_state_[stream_id] && !audio_data.empty()) {
123 audio_buffers_[stream_id].insert(audio_buffers_[stream_id].end(),
124 audio_data.begin(), audio_data.end());
125 }
126 if (recording_state_[stream_id] && !audio_buffers_[stream_id].empty()) {
127 save_audio_segment(audio_buffers_[stream_id], stream_id);
128 }
129 recording_state_[stream_id] = false;
130
131 // AUDIO_VAD_STATE_NONE
132 } else if (vad_state == 0) {
133 if (recording_state_[stream_id]) {
134 RCLCPP_INFO(this->get_logger(), "⏹️ Recording state reset");
135 recording_state_[stream_id] = false;
136 }
137 }
138
139 // Output the current buffer status.
140 size_t buffer_size = audio_buffers_[stream_id].size();
141 bool recording = recording_state_[stream_id];
142 RCLCPP_DEBUG(this->get_logger(),
143 "[Stream %u] Buffer size: %zu bytes, Recording state: %s",
144 stream_id, buffer_size, recording ? "true" : "false");
145 }
146
147 void save_audio_segment(const std::vector<uint8_t> &audio_data,
148 uint32_t stream_id) {
149 if (audio_data.empty())
150 return;
151
152 // Get the current timestamp.
153 auto now = std::chrono::system_clock::now();
154 std::time_t t = std::chrono::system_clock::to_time_t(now);
155 auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
156 now.time_since_epoch()) %
157 1000;
158
159 std::ostringstream oss;
160 oss << std::put_time(std::localtime(&t), "%Y%m%d_%H%M%S") << "_"
161 << std::setw(3) << std::setfill('0') << ms.count();
162
163 // Create a subdirectory by stream_id.
164 fs::path stream_dir =
165 fs::path(audio_output_dir_) / ("stream_" + std::to_string(stream_id));
166 fs::create_directories(stream_dir);
167
168 static const std::unordered_map<uint32_t, std::string> stream_names = {
169 {1, "internal_mic"}, {2, "external_mic"}};
170 std::string stream_name = stream_names.count(stream_id)
171 ? stream_names.at(stream_id)
172 : ("stream_" + std::to_string(stream_id));
173 std::string filename = stream_name + "_" + oss.str() + ".pcm";
174 fs::path filepath = stream_dir / filename;
175
176 try {
177 std::ofstream ofs(filepath, std::ios::binary);
178 ofs.write(reinterpret_cast<const char *>(audio_data.data()),
179 audio_data.size());
180 ofs.close();
181 RCLCPP_INFO(this->get_logger(),
182 "Audio segment saved: %s (size: %zu bytes)", filepath.c_str(),
183 audio_data.size());
184
185 // Record audio file duration (assuming 16kHz, 16-bit, mono)
186 int sample_rate = 16000;
187 int bits_per_sample = 16;
188 int channels = 1;
189 int bytes_per_sample = bits_per_sample / 8;
190 size_t total_samples = audio_data.size() / (bytes_per_sample * channels);
191 double duration_seconds =
192 static_cast<double>(total_samples) / sample_rate;
193
194 RCLCPP_INFO(this->get_logger(),
195 "Audio duration: %.2f seconds (%zu samples)",
196 duration_seconds, total_samples);
197 } catch (const std::exception &e) {
198 RCLCPP_ERROR(this->get_logger(), "Failed to save audio file: %s",
199 e.what());
200 }
201 }
202
203 // Member variables
204 std::unordered_map<uint32_t, std::vector<uint8_t>> audio_buffers_;
205 std::unordered_map<uint32_t, bool> recording_state_;
206 std::string audio_output_dir_;
207 rclcpp::Subscription<aimdk_msgs::msg::ProcessedAudioOutput>::SharedPtr
208 subscription_;
209};
210
211int main(int argc, char **argv) {
212 rclcpp::init(argc, argv);
213 auto node = std::make_shared<AudioSubscriber>();
214 RCLCPP_INFO(node->get_logger(),
215 "Listening for denoised audio data, press Ctrl+C to exit...");
216 rclcpp::spin(node);
217 rclcpp::shutdown();
218 return 0;
219}
Usage Instructions:
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/: Internal microphone audiostream_2/: External microphone audio
File naming format:
{stream_name}_{timestamp}.pcmAudio format: 16 kHz, 16-bit, mono PCM
You can play the saved PCM file using the following command:
aplay -r 16000 -f S16_LE -c 1 external_mic_20250909_133649_738.pcm
You should say wake words to make VAD ready to capture voice
Example Output:
[INFO] Start subscribing to denoised audio data...
[INFO] Received audio data: stream_id=2, vad_state=1 (Speech Start), audio_size=320 bytes
[INFO] [External Mic] VAD State: Speech Start, Audio: 320 bytes
[INFO] 🎤 Speech detected
[INFO] Received audio data: stream_id=2, vad_state=2 (In Speech), audio_size=320 bytes
[INFO] [External Mic] VAD State: In Speech, Audio: 320 bytes
[INFO] 🔄 Processing speech...
[INFO] Received audio data: stream_id=2, vad_state=3 (Speech End), audio_size=320 bytes
[INFO] [External Mic] VAD State: Speech End, Audio: 320 bytes
[INFO] ✅ Speech ended
[INFO] Audio segment saved: audio_recordings/stream_2/external_mic_20250909_133649_738.pcm (Size: 960 bytes)
[INFO] Audio duration: 0.06 seconds (480 samples)
Example: Playing a PCM audio file (using aplay on Linux)
Assuming you have recorded and saved the audio file external_mic_20250909_151117_223.pcm, you can play it using the following command:
aplay -r 16000 -f S16_LE -c 1 audio_recordings/stream_2/external_mic_20250909_151117_223.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.
Note: If you recorded audio from the internal microphone, the path should be audio_recordings/stream_1/internal_mic_xxx.pcm
6.2.21 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:
Automatically creates an output directory named by timestamp
Splits multi-channel audio data and saves each channel separately (mic channels / reference channels)
Flushes buffered data to disk once per second
Supports mic-source switch detection
File naming format: channel_{index}_{type}.pcm, e.g.
channel_0_mic.pcm(microphone)channel_4_ref.pcm(reference / echo-cancellation)
1/**
2 * Microphone raw data receiving example
3 *
4 * This example subscribes to the `/aima/hal/audio/capture` topic to receive
5 * the robot's raw audio data. It supports both the built-in microphone and the
6 * external microphone audio streams, and automatically saves complete audio
7 * data as PCM files split by channel.
8 *
9 * Features:
10 * - Automatically saves raw audio as PCM files
11 * - Stores files categorized by timestamp and mic source
12 */
13
14#include <aimdk_msgs/msg/audio_capture.hpp>
15#include <chrono>
16#include <ctime>
17#include <filesystem>
18#include <fstream>
19#include <iomanip>
20#include <rclcpp/rclcpp.hpp>
21#include <sstream>
22#include <string>
23#include <unordered_map>
24#include <vector>
25
26namespace fs = std::filesystem;
27
28class RawAudioSubscriber : public rclcpp::Node {
29public:
30 RawAudioSubscriber() : rclcpp::Node("raw_audio_subscriber") {
31 mic_channels_ = 0;
32 ref_channels_ = 0;
33 inited_ = false;
34
35 // Create audio output directory named by current timestamp
36 auto now = std::chrono::system_clock::now();
37 auto time_t_now = std::chrono::system_clock::to_time_t(now);
38 auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
39 now.time_since_epoch()) %
40 1000;
41 std::ostringstream oss;
42 oss << std::put_time(std::localtime(&time_t_now), "%Y%m%d_%H%M%S_")
43 << std::setw(3) << std::setfill('0') << ms.count();
44 audio_output_dir_ = "audio_recordings/" + oss.str();
45 fs::create_directories(audio_output_dir_);
46
47 // Note: deep queue to avoid missing data
48 auto qos = rclcpp::QoS(rclcpp::KeepLast(500)).best_effort();
49
50 subscription_ = this->create_subscription<aimdk_msgs::msg::AudioCapture>(
51 "/aima/hal/audio/capture", qos,
52 std::bind(&RawAudioSubscriber::audio_callback, this,
53 std::placeholders::_1));
54
55 RCLCPP_INFO(this->get_logger(), "Start subscribing to raw audio data...");
56 }
57
58 void run() {
59 timer_ = this->create_wall_timer(
60 std::chrono::seconds(1),
61 std::bind(&RawAudioSubscriber::save_audio_segments, this));
62 rclcpp::spin(shared_from_this());
63 }
64
65private:
66 void audio_callback(const aimdk_msgs::msg::AudioCapture::SharedPtr msg) {
67 if (!inited_) {
68 mic_channels_ = msg->mic_channels;
69 ref_channels_ = msg->ref_channels;
70 inited_ = true;
71 } else if (mic_channels_ != msg->mic_channels ||
72 ref_channels_ != msg->ref_channels) {
73 // quit as MIC switched
74 RCLCPP_ERROR(
75 this->get_logger(),
76 "MIC channels info changed (mic:%u ref:%u) -> (mic:%u ref:%u)",
77 mic_channels_, ref_channels_, msg->mic_channels, msg->ref_channels);
78 rclcpp::shutdown();
79 return;
80 }
81
82 handle_audio_data(msg->data.data);
83 }
84
85 void handle_audio_data(const std::vector<uint8_t> &audio_data) {
86 // Split S16LE data into channels
87 int channels =
88 static_cast<int>(ref_channels_) + static_cast<int>(mic_channels_);
89 if (channels == 0)
90 return;
91
92 const int bytes_per_channel = 2;
93 const int unit_size = channels * bytes_per_channel;
94 size_t num_frames = audio_data.size() / unit_size;
95
96 for (size_t k = 0; k < num_frames; ++k) {
97 for (int i = 0; i < channels; ++i) {
98 size_t base = k * unit_size + i * bytes_per_channel;
99 audio_buffers_[i].push_back(audio_data[base]);
100 audio_buffers_[i].push_back(audio_data[base + 1]);
101 }
102 }
103 }
104
105 void save_audio_segments() {
106 RCLCPP_INFO(this->get_logger(), "Flashing cached audio data...");
107 int total =
108 static_cast<int>(mic_channels_) + static_cast<int>(ref_channels_);
109 for (int i = 0; i < total; ++i) {
110 bool is_ref = (i >= static_cast<int>(mic_channels_));
111 save_audio_segment(i, is_ref);
112 }
113 }
114
115 void save_audio_segment(int channel, bool is_ref) {
116 auto &buf = audio_buffers_[channel];
117 if (buf.empty())
118 return;
119
120 std::string channel_type = is_ref ? "ref" : "mic";
121 std::string filename =
122 "channel_" + std::to_string(channel) + "_" + channel_type + ".pcm";
123 fs::path filepath = fs::path(audio_output_dir_) / filename;
124
125 try {
126 // Append mode (equivalent to Python 'ab')
127 std::ofstream ofs(filepath, std::ios::binary | std::ios::app);
128 ofs.write(reinterpret_cast<const char *>(buf.data()), buf.size());
129 ofs.close();
130
131 RCLCPP_INFO(this->get_logger(),
132 "Audio segment saved: %s append: %zu bytes", filepath.c_str(),
133 buf.size());
134 buf.clear();
135 } catch (const std::exception &e) {
136 RCLCPP_ERROR(this->get_logger(), "Failed to save audio file: %s",
137 e.what());
138 }
139 }
140
141 bool inited_;
142 uint8_t mic_channels_;
143 uint8_t ref_channels_;
144 std::string audio_output_dir_;
145 std::unordered_map<int, std::vector<uint8_t>> audio_buffers_;
146 rclcpp::Subscription<aimdk_msgs::msg::AudioCapture>::SharedPtr subscription_;
147 rclcpp::TimerBase::SharedPtr timer_;
148};
149
150int main(int argc, char **argv) {
151 rclcpp::init(argc, argv);
152 auto node = std::make_shared<RawAudioSubscriber>();
153 RCLCPP_INFO(node->get_logger(),
154 "Listening to raw audio data, press Ctrl+C to exit...");
155 node->run();
156 rclcpp::shutdown();
157 return 0;
158}
Usage Instructions:
ros2 run examples mic_raw_receiver
Play saved PCM file:
aplay -r 16000 -f S16_LE -c 1 audio_recordings/20250909_133650_123/channel_0_mic.pcm
6.2.22 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 = 6;
176 req->focus_requester = requester;
177
178 RCLCPP_INFO(this->get_logger(), "Sending RequestAudioFocus request");
179 std::shared_future<RequestAudioFocus::Response::SharedPtr> future;
180 for (int i = 0; i < 8; ++i) {
181 future = request_client_->async_send_request(req).future.share();
182 auto status = rclcpp::spin_until_future_complete(
183 shared_from_this(), future, std::chrono::milliseconds(250));
184 if (status == rclcpp::FutureReturnCode::SUCCESS)
185 break;
186 RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
187 }
188
189 auto response = future.get();
190 if (!response) {
191 RCLCPP_ERROR(this->get_logger(), "Service call failed or timed out.");
192 return;
193 }
194 if (response->reponse.status.value ==
195 aimdk_msgs::msg::CommonState::SUCCESS) {
196 focus_ = response->focus_response.focus_gain;
197 RCLCPP_INFO(this->get_logger(), "RequestAudioFocus done: focus %s",
198 focus_ ? "true" : "false");
199 } else {
200 RCLCPP_ERROR(this->get_logger(),
201 "Failed in response of RequestAudioFocus: %s",
202 response->reponse.message.c_str());
203 }
204 }
205
206 void send_focus_release() {
207 auto req = std::make_shared<AbandonAudioFocus::Request>();
208 aimdk_msgs::msg::FocusRequester requester;
209 requester.pkg_name = pkg_name_;
210 req->focus_requester = requester;
211
212 RCLCPP_INFO(this->get_logger(), "Sending AbandonAudioFocus request");
213 std::shared_future<AbandonAudioFocus::Response::SharedPtr> future;
214 for (int i = 0; i < 8; ++i) {
215 future = release_client_->async_send_request(req).future.share();
216 auto status = rclcpp::spin_until_future_complete(
217 shared_from_this(), future, std::chrono::milliseconds(250));
218 if (status == rclcpp::FutureReturnCode::SUCCESS)
219 break;
220 RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
221 }
222
223 auto response = future.get();
224 if (!response) {
225 RCLCPP_ERROR(this->get_logger(), "Service call failed or timed out.");
226 return;
227 }
228 if (response->reponse.status.value ==
229 aimdk_msgs::msg::CommonState::SUCCESS) {
230 // always focus False after abandon
231 focus_ = response->focus_response.focus_gain;
232 RCLCPP_INFO(this->get_logger(), "AbandonAudioFocus done: focus %s",
233 focus_ ? "true" : "false");
234 } else {
235 RCLCPP_ERROR(this->get_logger(),
236 "Failed in response of AbandonAudioFocus: %s",
237 response->reponse.message.c_str());
238 }
239 }
240
241 void
242 focus_event_callback(const aimdk_msgs::msg::FocusResponse::SharedPtr msg) {
243 if (msg->pkg_name == pkg_name_) {
244 RCLCPP_INFO(this->get_logger(),
245 "receive focus out event: focus state: %s",
246 msg->focus_gain ? "true" : "false");
247 focus_force_ = msg->focus_gain;
248 }
249 }
250
251 void run_once() {
252 aimdk_msgs::msg::AudioPlayback msg;
253 msg.pkg_name = pkg_name_;
254 msg.token_id = pkg_name_;
255
256 aimdk_msgs::msg::AudioInfo info;
257 info.channels = static_cast<uint8_t>(mic_device_->get_channel_count());
258 info.sample_rate = static_cast<uint32_t>(mic_device_->get_sample_rate());
259
260 aimdk_msgs::msg::AudioData audio_data;
261 audio_data.data = mic_device_->get_cached_data();
262
263 msg.info = info;
264 msg.data = audio_data;
265 pub_->publish(msg);
266 }
267
268 std::string pkg_name_;
269 bool focus_;
270 bool focus_force_;
271 std::unique_ptr<FakeMicDevice> mic_device_;
272 rclcpp::Client<RequestAudioFocus>::SharedPtr request_client_;
273 rclcpp::Client<AbandonAudioFocus>::SharedPtr release_client_;
274 rclcpp::Subscription<aimdk_msgs::msg::FocusResponse>::SharedPtr sub_;
275 rclcpp::Publisher<aimdk_msgs::msg::AudioPlayback>::SharedPtr pub_;
276};
277
278// ---------------------------------------------------------------------------
279// Signal handling
280// ---------------------------------------------------------------------------
281static std::shared_ptr<AudioStreamPlayer> g_node;
282
283void signal_handler(int sig) {
284 if (g_node) {
285 if (g_node->is_holding_focus()) {
286 g_node->send_request(false);
287 }
288 RCLCPP_INFO(g_node->get_logger(),
289 "Received signal %d, abandon audio focus and shutting down",
290 sig);
291 }
292 rclcpp::shutdown();
293}
294
295int main(int argc, char **argv) {
296 rclcpp::init(argc, argv);
297
298 try {
299 auto node = std::make_shared<AudioStreamPlayer>();
300 g_node = node;
301 std::signal(SIGINT, signal_handler);
302 std::signal(SIGTERM, signal_handler);
303
304 node->spin_until_focus();
305 node->run();
306
307 } catch (const std::exception &e) {
308 RCLCPP_ERROR(rclcpp::get_logger("main"),
309 "Program exited with exception: %s", e.what());
310 }
311
312 if (rclcpp::ok()) {
313 rclcpp::shutdown();
314 }
315 return 0;
316}
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.23 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 <memory>
6#include <signal.h>
7#include <string>
8
9// Global variable used for signal handling
10std::shared_ptr<rclcpp::Node> g_node = nullptr;
11
12// Signal handler function
13void signal_handler(int signal) {
14 if (g_node) {
15 RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
16 signal);
17 g_node.reset();
18 }
19 rclcpp::shutdown();
20 exit(signal);
21}
22
23class PlayEmojiClient : public rclcpp::Node {
24public:
25 PlayEmojiClient() : Node("play_emoji_client") {
26 client_ = this->create_client<aimdk_msgs::srv::PlayEmoji>(
27 "/face_ui_proxy/play_emoji");
28 RCLCPP_INFO(this->get_logger(), "✅ PlayEmoji client node started.");
29
30 // Wait for the service to become available
31 while (!client_->wait_for_service(std::chrono::seconds(2))) {
32 RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
33 }
34 RCLCPP_INFO(this->get_logger(),
35 "🟢 Service available, ready to send request.");
36 }
37
38 bool send_request(uint8_t emoji, uint8_t mode, int32_t priority) {
39 try {
40 auto request = std::make_shared<aimdk_msgs::srv::PlayEmoji::Request>();
41
42 request->emotion_id = emoji;
43 request->mode = mode;
44 request->priority = priority;
45
46 RCLCPP_INFO(
47 this->get_logger(),
48 "📨 Sending request to play emoji: id=%hhu, mode=%hhu, priority=%d",
49 emoji, mode, priority);
50
51 const std::chrono::milliseconds timeout(250);
52 for (int i = 0; i < 8; i++) {
53 request->header.header.stamp = this->now();
54 auto future = client_->async_send_request(request);
55 auto retcode = rclcpp::spin_until_future_complete(shared_from_this(),
56 future, timeout);
57 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
58 // retry as remote peer is NOT handled well by ROS
59 RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
60 continue;
61 }
62 // future.done
63 auto response = future.get();
64 if (response->success) {
65 RCLCPP_INFO(this->get_logger(),
66 "✅ Request to play emoji recorded successfully: %s",
67 response->message.c_str());
68 return true;
69 } else {
70 RCLCPP_ERROR(this->get_logger(),
71 "❌ Failed to record play-emoji request: %s",
72 response->message.c_str());
73 return false;
74 }
75 }
76 RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
77 return false;
78 } catch (const std::exception &e) {
79 RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
80 return false;
81 }
82 }
83
84private:
85 rclcpp::Client<aimdk_msgs::srv::PlayEmoji>::SharedPtr client_;
86};
87
88int main(int argc, char **argv) {
89 try {
90 rclcpp::init(argc, argv);
91
92 // Set up signal handlers
93 signal(SIGINT, signal_handler);
94 signal(SIGTERM, signal_handler);
95
96 int32_t priority = 10;
97
98 int emotion = 1; // Expression type, 1 means Blink
99 std::cout
100 << "Enter expression ID: 1-Blink, 60-Bored, 70-Abnormal, 80-Sleeping, "
101 "90-Happy, 190-Very Angry, 200-Adoration"
102 << std::endl;
103 std::cin >> emotion;
104
105 int mode = 1; // Playback mode, 1 means play once, 2 means loop
106 std::cout << "Enter play mode (1: once, 2: loop): ";
107 std::cin >> mode;
108 if (mode < 1 || mode > 2) {
109 RCLCPP_ERROR(rclcpp::get_logger("main"), "Invalid play mode: %d", mode);
110 rclcpp::shutdown();
111 return 1;
112 }
113
114 g_node = std::make_shared<PlayEmojiClient>();
115 auto client = std::dynamic_pointer_cast<PlayEmojiClient>(g_node);
116
117 if (client) {
118 client->send_request(emotion, mode, priority);
119 }
120
121 // Clean up resources
122 g_node.reset();
123 rclcpp::shutdown();
124
125 return 0;
126 } catch (const std::exception &e) {
127 RCLCPP_ERROR(rclcpp::get_logger("main"),
128 "Program exited with exception: %s", e.what());
129 return 1;
130 }
131}
6.2.24 Slam
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.25 Relocate
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 <nav_msgs/msg/odometry.hpp>
5#include <rclcpp/rclcpp.hpp>
6#include <std_msgs/msg/string.hpp>
7
8using namespace std::chrono_literals;
9
10class RelocalizationNode : public rclcpp::Node {
11public:
12 RelocalizationNode() : Node("relocalization_node") {
13 // /integrated_command subscriber requires TRANSIENT_LOCAL durability
14 auto command_qos = rclcpp::QoS(rclcpp::KeepLast(10));
15 command_qos.transient_local();
16
17 integrated_command_pub_ = this->create_publisher<std_msgs::msg::String>(
18 "/integrated_command", command_qos);
19 relocalization_pose_pub_ = this->create_publisher<geometry_msgs::msg::Pose>(
20 "/relocalization_pose", 10);
21
22 // Create subscriber with BEST_EFFORT QoS
23 auto lidar_loc_qos = rclcpp::QoS(rclcpp::KeepLast(10));
24 lidar_loc_qos.best_effort();
25
26 odometry_sub_ = this->create_subscription<nav_msgs::msg::Odometry>(
27 "/slam/lidar_odom", lidar_loc_qos,
28 std::bind(&RelocalizationNode::odometryCallback, this,
29 std::placeholders::_1));
30
31 // Wait for subscribers before publishing
32 publish_timer_ = this->create_wall_timer(200ms, [this]() {
33 // Wait until both publishers have at least one subscriber
34 if (integrated_command_pub_->get_subscription_count() == 0 ||
35 relocalization_pose_pub_->get_subscription_count() == 0) {
36 RCLCPP_INFO_THROTTLE(
37 this->get_logger(), *this->get_clock(), 2000,
38 "Waiting for subscribers... (command: %zu, pose: %zu)",
39 integrated_command_pub_->get_subscription_count(),
40 relocalization_pose_pub_->get_subscription_count());
41 return;
42 }
43 publish_timer_->cancel();
44
45 // Publish integrated_command
46 auto integrated_command_msg = std_msgs::msg::String();
47 integrated_command_msg.data = "start_relocalization:1774430080403";
48 integrated_command_pub_->publish(integrated_command_msg);
49 RCLCPP_INFO(this->get_logger(), "Published integrated_command");
50
51 // Schedule relocalization_pose publication after delay
52 this->relocalization_pose_timer_ = this->create_wall_timer(1s, [this]() {
53 this->relocalization_pose_timer_->cancel();
54
55 auto relocalization_pose_msg = geometry_msgs::msg::Pose();
56 relocalization_pose_msg.position.x = 273;
57 relocalization_pose_msg.position.y = 200;
58 relocalization_pose_msg.position.z = 0.0;
59 relocalization_pose_msg.orientation.x = 0.0;
60 relocalization_pose_msg.orientation.y = 0.0;
61 relocalization_pose_msg.orientation.z = 0.0;
62 relocalization_pose_msg.orientation.w = 1.0;
63
64 relocalization_pose_pub_->publish(relocalization_pose_msg);
65 RCLCPP_INFO(this->get_logger(), "Published relocalization_pose");
66
67 // Start timeout timer (5 seconds)
68 this->timeout_timer_ = this->create_wall_timer(30s, [this]() {
69 if (!success_received_) {
70 RCLCPP_ERROR(this->get_logger(),
71 "Timeout reached - Relocalization failed!");
72 rclcpp::shutdown();
73 }
74 });
75
76 RCLCPP_INFO(this->get_logger(),
77 "Waiting for robot pose data (timeout: 30s)...");
78 });
79 });
80 }
81
82private:
83 // get robot pose here
84 void odometryCallback(const nav_msgs::msg::Odometry::SharedPtr msg) {
85 if (!success_received_) {
86 success_received_ = true;
87 if (timeout_timer_) {
88 timeout_timer_->cancel();
89 }
90 RCLCPP_INFO(this->get_logger(),
91 "Received odometry data - Relocalization successful!");
92 rclcpp::shutdown();
93 }
94 }
95
96 rclcpp::Publisher<std_msgs::msg::String>::SharedPtr integrated_command_pub_;
97 rclcpp::Publisher<geometry_msgs::msg::Pose>::SharedPtr
98 relocalization_pose_pub_;
99 rclcpp::Subscription<nav_msgs::msg::Odometry>::SharedPtr odometry_sub_;
100
101 // Timers
102 rclcpp::TimerBase::SharedPtr publish_timer_;
103 rclcpp::TimerBase::SharedPtr relocalization_pose_timer_;
104 rclcpp::TimerBase::SharedPtr timeout_timer_;
105
106 bool success_received_ = false;
107};
108
109int main(int argc, char *argv[]) {
110 rclcpp::init(argc, argv);
111 auto node = std::make_shared<RelocalizationNode>();
112 rclcpp::spin(node);
113 rclcpp::shutdown();
114 return 0;
115}
Usage Instructions
ros2 run examples relocate
[INFO] [1772528939.129777359] [relocalization_node]: Published integrated_command
[INFO] [1772528940.130067255] [relocalization_node]: Published relocalization_pose
[INFO] [1772528940.130129806] [relocalization_node]: Waiting for robot pose data (timeout: 5s)...
[INFO] [1772528945.130242770] [relocalization_node]: Received odometry data - Relocalization successful!
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.26 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 <memory>
8
9class MapClient : public rclcpp::Node {
10public:
11 MapClient() : Node("get_map_client") {
12 client_ = this->create_client<aimdk_msgs::srv::GetStoredMapByName>(
13 "/aimdk_5Fmsgs/srv/GetStoredMapByName");
14 }
15
16 void send_request(const std::string &map_name) {
17 while (!client_->wait_for_service(std::chrono::seconds(1))) {
18 if (!rclcpp::ok()) {
19 RCLCPP_ERROR(this->get_logger(),
20 "Interrupted while waiting for service.");
21 return;
22 }
23 RCLCPP_INFO(this->get_logger(),
24 "Service not available, waiting again...");
25 }
26
27 auto request =
28 std::make_shared<aimdk_msgs::srv::GetStoredMapByName::Request>();
29
30 request->header.stamp = this->now();
31 request->header.frame_id = "";
32
33 request->map_name = map_name;
34
35 auto result_future = client_->async_send_request(request);
36
37 // Use a fresh executor to spin, avoiding any pre-existing state.
38 rclcpp::executors::SingleThreadedExecutor exec;
39 exec.add_node(shared_from_this());
40 if (exec.spin_until_future_complete(result_future,
41 std::chrono::seconds(10)) !=
42 rclcpp::FutureReturnCode::SUCCESS) {
43 RCLCPP_ERROR(this->get_logger(), "Service call failed or timed out.");
44 return;
45 }
46
47 auto result = result_future.get();
48
49 if (result->code == 0) {
50 RCLCPP_INFO(this->get_logger(), "Service call succeeded");
51 RCLCPP_INFO(this->get_logger(), "Map path: %s", result->map_path.c_str());
52 RCLCPP_INFO(this->get_logger(), "Map resolution: %f",
53 result->map_info.resolution);
54 RCLCPP_INFO(this->get_logger(), "Map width: %u", result->map_info.width);
55 RCLCPP_INFO(this->get_logger(), "Map height: %u",
56 result->map_info.height);
57 RCLCPP_INFO(this->get_logger(), "Map id: %ld", result->map_id);
58 } else {
59 RCLCPP_ERROR(this->get_logger(), "Service call failed with code: %lu",
60 result->code);
61 }
62 }
63
64private:
65 rclcpp::Client<aimdk_msgs::srv::GetStoredMapByName>::SharedPtr client_;
66};
67
68int main(int argc, char **argv) {
69 rclcpp::init(argc, argv);
70
71 auto client = std::make_shared<MapClient>();
72 client->send_request("map_name");
73
74 rclcpp::shutdown();
75 return 0;
76}
Usage Instructions
ros2 run examples get_map
[INFO] [get_map_client]: Service call succeeded
[INFO] [get_map_client]: Map path: /path/to/test_map.png
[INFO] [get_map_client]: Map resolution: 0.050000
[INFO] [get_map_client]: Map width: 200
[INFO] [get_map_client]: Map height: 200
[INFO] [get_map_client]: Map id: 1
The example requests the map named test_map by default. Modify the argument to send_request in the code to request a different map.
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.27 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 <iostream>
8#include <rclcpp/rclcpp.hpp>
9#include <string>
10#include <vector>
11
12using namespace std::chrono_literals;
13
14class RobotResourceClient : public rclcpp::Node {
15public:
16 RobotResourceClient() : Node("robot_resource_client") {
17 get_resource_client_ =
18 this->create_client<aimdk_msgs::srv::GetRobotResources>(
19 "/aimdk_5Fmsgs/srv/GetRobotResources");
20 play_resource_client_ =
21 this->create_client<aimdk_msgs::srv::ExecuteActionResource>(
22 "/aimdk_5Fmsgs/srv/ExecuteActionResource");
23 }
24
25 bool get_robot_resources() {
26 while (!get_resource_client_->wait_for_service(1s)) {
27 if (!rclcpp::ok()) {
28 return false;
29 }
30 RCLCPP_INFO(this->get_logger(),
31 "Service not available, waiting again...");
32 }
33
34 if (!rclcpp::ok()) {
35 return false;
36 }
37
38 auto request =
39 std::make_shared<aimdk_msgs::srv::GetRobotResources::Request>();
40
41 auto future_result = get_resource_client_->async_send_request(request);
42
43 if (rclcpp::spin_until_future_complete(this->get_node_base_interface(),
44 future_result) !=
45 rclcpp::FutureReturnCode::SUCCESS) {
46 if (rclcpp::ok()) {
47 RCLCPP_ERROR(this->get_logger(), "Failed to call GetRobotResources");
48 }
49 return false;
50 }
51
52 auto response = future_result.get();
53 resources_ = response->robot_resources;
54 RCLCPP_INFO(this->get_logger(), "Found %zu resources", resources_.size());
55 return true;
56 }
57
58 void print_resource_menu() {
59 if (resources_.empty()) {
60 std::cout << "(no resources)\n";
61 return;
62 }
63
64 std::cout << "\n=== Available Robot Resources ===\n";
65 for (size_t i = 0; i < resources_.size(); ++i) {
66 const auto &ver = resources_[i].current_version;
67 std::cout << "[" << i << "] "
68 << (ver.name.empty() ? "<no name>" : ver.name) << "\n";
69 std::cout << " key: " << resources_[i].resource_key << "\n";
70 std::cout << " version: " << ver.version << "\n";
71 }
72 std::cout << "=================================\n\n";
73 }
74
75 int pick_resource() {
76 if (resources_.empty()) {
77 return -1;
78 }
79
80 std::string line;
81 while (true) {
82 std::cout << "Select resource index [0-" << resources_.size() - 1
83 << "] (q to quit): ";
84 if (!std::getline(std::cin, line)) {
85 return -1;
86 }
87 if (line == "q" || line == "quit" || line == "exit") {
88 return -1;
89 }
90 try {
91 int idx = std::stoi(line);
92 if (idx >= 0 && idx < static_cast<int>(resources_.size())) {
93 return idx;
94 }
95 std::cout << "Index out of range (0-" << resources_.size() - 1
96 << ").\n";
97 } catch (...) {
98 std::cout << "Please enter a number.\n";
99 }
100 }
101 }
102
103 void play_resource(int idx) {
104 while (!play_resource_client_->wait_for_service(1s)) {
105 if (!rclcpp::ok()) {
106 RCLCPP_ERROR(this->get_logger(),
107 "Interrupted while waiting for service.");
108 return;
109 }
110 RCLCPP_INFO(this->get_logger(), "Service not available, waiting...");
111 }
112
113 if (!rclcpp::ok()) {
114 return;
115 }
116
117 const auto &resource = resources_[idx];
118 auto request =
119 std::make_shared<aimdk_msgs::srv::ExecuteActionResource::Request>();
120
121 request->resource_key = resource.resource_key;
122 request->resource_version = resource.current_version.version;
123
124 if (request->resource_key.find("onnx") != std::string::npos) {
125 request->meta = R"({"resource_type": "BODY_MONTION"})";
126 } else {
127 request->meta = R"({"resource_type": "ARM_MONTION"})";
128 }
129
130 RCLCPP_INFO(this->get_logger(), "Sending request:");
131 RCLCPP_INFO(this->get_logger(), " resource_key: %s",
132 request->resource_key.c_str());
133 RCLCPP_INFO(this->get_logger(), " resource_version: %s",
134 request->resource_version.c_str());
135 RCLCPP_INFO(this->get_logger(), " meta: %s", request->meta.c_str());
136
137 auto future = play_resource_client_->async_send_request(request);
138
139 if (rclcpp::spin_until_future_complete(this->get_node_base_interface(),
140 future) ==
141 rclcpp::FutureReturnCode::SUCCESS) {
142 handle_response(future.get());
143 } else {
144 if (rclcpp::ok()) {
145 RCLCPP_ERROR(this->get_logger(),
146 "Failed to call ExecuteActionResource");
147 }
148 }
149 }
150
151private:
152 rclcpp::Client<aimdk_msgs::srv::GetRobotResources>::SharedPtr
153 get_resource_client_;
154 rclcpp::Client<aimdk_msgs::srv::ExecuteActionResource>::SharedPtr
155 play_resource_client_;
156 std::vector<aimdk_msgs::msg::RobotResource> resources_;
157
158 void handle_response(
159 const aimdk_msgs::srv::ExecuteActionResource::Response::SharedPtr
160 response) {
161 RCLCPP_INFO(this->get_logger(), "Service response received:");
162 RCLCPP_INFO(this->get_logger(), "Response Header:");
163 RCLCPP_INFO(this->get_logger(), " stamp: %d.%09u",
164 response->header.header.stamp.sec,
165 response->header.header.stamp.nanosec);
166 RCLCPP_INFO(this->get_logger(), " code: %ld",
167 response->header.header.code);
168 RCLCPP_INFO(this->get_logger(), "Status:");
169 RCLCPP_INFO(this->get_logger(), " value: %d",
170 response->header.status.value);
171 RCLCPP_INFO(this->get_logger(), " message: %s",
172 response->header.message.c_str());
173 }
174};
175
176int main(int argc, char **argv) {
177 rclcpp::init(argc, argv);
178 auto client_node = std::make_shared<RobotResourceClient>();
179
180 if (!client_node->get_robot_resources()) {
181 rclcpp::shutdown();
182 return 1;
183 }
184
185 client_node->print_resource_menu();
186 int chosen = client_node->pick_resource();
187 if (chosen < 0) {
188 RCLCPP_INFO(client_node->get_logger(), "No resource selected, exiting.");
189 rclcpp::shutdown();
190 return 0;
191 }
192
193 client_node->play_resource(chosen);
194 rclcpp::shutdown();
195 return 0;
196}
Usage Instructions
ros2 run examples play_linkcraft
[INFO] [1772529153.517267476] [robot_resource_client]: Service call succeeded
[INFO] [1772529153.517416880] [robot_resource_client]: Robot Resources (0):
[INFO] [1772529153.517473238] [robot_resource_client]: Sending request:
[INFO] [1772529153.517488347] [robot_resource_client]: resource_key: linkcraft_resource_onnx_01KBM2BHNFM93Z0DK6ES9F6DJA
[INFO] [1772529153.517497834] [robot_resource_client]: resource_version: 0.0.1
[INFO] [1772529153.517505889] [robot_resource_client]: meta: {"resource_type": "BODY_MONTION"}
[INFO] [1772529153.553673433] [robot_resource_client]: Service response received:
[INFO] [1772529153.553697021] [robot_resource_client]: Response Header:
[INFO] [1772529153.553700410] [robot_resource_client]: stamp: 0.000000000
[INFO] [1772529153.553704130] [robot_resource_client]: code: 0
[INFO] [1772529153.553707001] [robot_resource_client]: Status:
[INFO] [1772529153.553709853] [robot_resource_client]: value: 1
[INFO] [1772529153.553712662] [robot_resource_client]: message: 1/1 task(s) failed. Details: localhost: failed (status code: 400) - Failed with status code: 400
6.2.28 LED 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 <chrono>
4#include <memory>
5#include <rclcpp/rclcpp.hpp>
6#include <signal.h>
7#include <string>
8
9std::shared_ptr<rclcpp::Node> g_node = nullptr;
10
11void signal_handler(int signal) {
12 if (g_node) {
13 RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
14 signal);
15 g_node.reset();
16 }
17 rclcpp::shutdown();
18 exit(signal);
19}
20
21class PlayLightsClient : public rclcpp::Node {
22public:
23 PlayLightsClient() : Node("play_lights_client") {
24 client_ = this->create_client<aimdk_msgs::srv::SetPmuLed>(
25 "/aimdk_5Fmsgs/srv/SetPmuLed");
26 RCLCPP_INFO(this->get_logger(), "✅ PlayLights client node started.");
27
28 // Wait for the service to become available
29 while (!client_->wait_for_service(std::chrono::seconds(2))) {
30 RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
31 }
32 RCLCPP_INFO(this->get_logger(),
33 "🟢 Service available, ready to send request.");
34 }
35
36 bool send_request(uint8_t led_mode, uint8_t r, uint8_t g, uint8_t b,
37 int32_t priority = 6, bool reset_priority = false) {
38 try {
39 auto request = std::make_shared<aimdk_msgs::srv::SetPmuLed::Request>();
40
41 request->led_strip_mode = led_mode;
42 request->r = r;
43 request->g = g;
44 request->b = b;
45 request->priority = priority;
46 request->reset_priority = reset_priority;
47 request->trace_id = "play_lights";
48
49 RCLCPP_INFO(this->get_logger(),
50 "📨 Sending request to control led strip: mode=%hhu, "
51 "RGB=(%hhu, %hhu, %hhu), priority=%d, reset_priority=%s",
52 led_mode, r, g, b, priority,
53 reset_priority ? "true" : "false");
54
55 // LED strip is slow to response (up to ~5s)
56 const std::chrono::milliseconds timeout(5000);
57 for (int i = 0; i < 4; i++) {
58 request->request.header.stamp = this->now();
59 auto future = client_->async_send_request(request);
60 auto retcode = rclcpp::spin_until_future_complete(
61 this->shared_from_this(), future, timeout);
62
63 if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
64 // retry as remote peer is NOT handled well by ROS
65 RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
66 continue;
67 }
68 // future.done
69 auto response = future.get();
70 if (response->status_code == 0) {
71 RCLCPP_INFO(this->get_logger(),
72 "✅ LED strip command sent successfully.");
73 return true;
74 } else {
75 RCLCPP_ERROR(this->get_logger(),
76 "❌ LED strip command failed with status: %d",
77 response->status_code);
78 return false;
79 }
80 }
81 RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
82 return false;
83 } catch (const std::exception &e) {
84 RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
85 return false;
86 }
87 }
88
89private:
90 rclcpp::Client<aimdk_msgs::srv::SetPmuLed>::SharedPtr client_;
91};
92
93int main(int argc, char **argv) {
94 try {
95 rclcpp::init(argc, argv);
96 signal(SIGINT, signal_handler);
97 signal(SIGTERM, signal_handler);
98
99 g_node = std::make_shared<PlayLightsClient>();
100 auto client_node = std::dynamic_pointer_cast<PlayLightsClient>(g_node);
101
102 int led_mode = 0; // LED Strip Mode
103 int r = 255, g = 0, b = 0; // RGB values
104 int priority = 6; // Priority
105 int reset_priority_input = 0; // Reset priority flag
106
107 std::cout << "=== LED Strip Control Example ===" << std::endl;
108 std::cout << "Select LED strip mode:" << std::endl;
109 std::cout << "0 - Steady On" << std::endl;
110 std::cout << "1 - Breathing (4s period, sinusoidal brightness)"
111 << std::endl;
112 std::cout << "2 - Blinking (1s period, 0.5s on, 0.5s off)" << std::endl;
113 std::cout << "3 - Flow (2s period, lights turn on left to right)"
114 << std::endl;
115 std::cout << "Enter mode (0-3): ";
116 std::cin >> led_mode;
117
118 std::cout << "\nSet RGB color values (0-255):" << std::endl;
119 std::cout << "Red component (R): ";
120 std::cin >> r;
121 std::cout << "Green component (G): ";
122 std::cin >> g;
123 std::cout << "Blue component (B): ";
124 std::cin >> b;
125
126 std::cout << "\nSet priority (higher value = higher priority, default=6): ";
127 std::cin >> priority;
128
129 std::cout << "Reset priority after command? (1=yes, 0=no, default=0): ";
130 std::cin >> reset_priority_input;
131
132 // clamp mode to range 0-3
133 led_mode = std::max(0, std::min(3, led_mode));
134 // clamp r/g/b to range 0-255
135 r = std::max(0, std::min(255, r));
136 g = std::max(0, std::min(255, g));
137 b = std::max(0, std::min(255, b));
138 // clamp priority to non-negative
139 priority = std::max(0, priority);
140
141 if (client_node) {
142 client_node->send_request(led_mode, r, g, b, priority,
143 reset_priority_input != 0);
144 }
145
146 g_node.reset();
147 rclcpp::shutdown();
148
149 return 0;
150 } catch (const std::exception &e) {
151 RCLCPP_ERROR(rclcpp::get_logger("main"),
152 "Program terminated with exception: %s", e.what());
153 return 1;
154 }
155}
Usage Instructions:
# Build
colcon build --packages-select examples
# Run
ros2 run examples play_lights
Output Example:
=== LED Strip Control Example ===
Select LED strip mode:
0 - Steady On
1 - Breathing (4s period, sinusoidal brightness)
2 - Blinking (1s period, 0.5s on, 0.5s off)
3 - Flow (2s period, lights turn on left to right)
Enter mode (0-3): 1
Set RGB color values (0-255):
Red component (R): 255
Green component (G): 0
Blue component (B): 0
Set priority (higher value = higher priority, default=6): 6
Reset priority after command? (1=yes, 0=no, default=0): 0
[INFO] [play_lights_client]: 📨 Sending request to control led strip: mode=1, RGB=(255, 0, 0), priority=6, reset_priority=false
[INFO] [play_lights_client]: ✅ LED strip command sent successfully.
Technical Features:
Supports 4 LED display modes
Customizable RGB colors
Priority control (higher value = higher priority)
Priority reset support
Asynchronous service calls
Input parameter validation
User-friendly interaction interface
6.2.29 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) {
61 RCLCPP_INFO(this->get_logger(), "✅ MIC source get successfully.");
62 RCLCPP_INFO(this->get_logger(), "MIC id: %d", response->mic_source);
63 } else {
64 RCLCPP_ERROR(this->get_logger(), "❌ Failed to get MIC source: %s",
65 response->header.message.c_str());
66 }
67 return;
68 }
69 RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
70 } catch (const std::exception &e) {
71 RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
72 }
73 }
74
75private:
76 rclcpp::Client<aimdk_msgs::srv::GetMicSourceRequest>::SharedPtr client_;
77};
78
79int main(int argc, char *argv[]) {
80 try {
81 rclcpp::init(argc, argv);
82 signal(SIGINT, signal_handler);
83 signal(SIGTERM, signal_handler);
84
85 g_node = std::make_shared<GetMicSourceRequestClient>();
86 auto client = std::dynamic_pointer_cast<GetMicSourceRequestClient>(g_node);
87
88 if (client) {
89 client->send_request();
90 }
91
92 g_node.reset();
93 rclcpp::shutdown();
94 return 0;
95 } catch (const std::exception &e) {
96 RCLCPP_ERROR(rclcpp::get_logger("main"),
97 "Program exited with exception: %s", e.what());
98 return 1;
99 }
100}
6.2.30 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) {
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