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.

Definition of 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.

Motion Mode Definitions

  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/GetMcAction

  • Message: 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_DEFAULT mode

  • Mode switching may take several seconds to complete

Interface Reference

  • Service: /aimdk_5Fmsgs/srv/SetMcAction

  • Message: 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:

  1. Joint model definition

  2. Trajectory interpolation using Ruckig

  3. Multi-joint coordinated control

  4. Real-time position, velocity, and acceleration control

Dependencies

  • ROS2

  • Ruckig library

  • aimdk_msgs package

Build Instructions

  1. Place the code in the src directory of your ROS2 workspace

  2. Add 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
)
  1. Add dependencies in package.xml:

<depend>rclcpp</depend>
<depend>aimdk_msgs</depend>
<depend>ruckig</depend>

Example Function Overview

  1. Four controller nodes are created to control:

    • Legs × 2 (12 joints)

    • Waist × 1 (3 joints)

    • Arms × 2 (14 joints)

    • Head × 1 (2 joints)

  2. 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

  1. Add new control logic:

    • Modify the SetTargetPosition function

    • Add new control callback functions

  2. 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:

  1. Subscribe to RGB image data:

    ros2 run examples echo_camera_rgbd --ros-args -p topic_type:=rgb_image
    
  2. 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
    
  3. 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:

  1. Subscribe to left camera RGB image:

    ros2 run examples echo_camera_stereo --ros-args -p topic_type:=left_rgb_image
    
  2. Subscribe to right camera RGB image:

    ros2 run examples echo_camera_stereo --ros-args -p topic_type:=right_rgb_image
    
  3. Subscribe to left camera intrinsic parameters:

    ros2 run examples echo_camera_stereo --ros-args -p topic_type:=left_camera_info
    
  4. 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:

  1. Subscribe to RGB image data:

    ros2 run examples echo_camera_head_rear --ros-args -p topic_type:=rgb_image
    
  2. Subscribe to compressed image data:

    ros2 run examples echo_camera_head_rear --ros-args -p topic_type:=rgb_image_compressed
    
  3. Subscribe to camera intrinsic parameters:

    ros2 run examples echo_camera_head_rear --ros-args -p topic_type:=camera_info
    
  4. 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
    
  5. 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 .mp4 video file, then use it as input for play_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.mp4

Step 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.mp4

Step 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/PlayTts

  • Message: 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 speech

  • 1: Speech start

  • 2: Speech processing

  • 3: 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:

  1. After running the node, an audio_recordings directory will be created automatically

  2. Audio files are stored by stream_id (currently not distinguished; stream_1 is always used):

    • stream_1/: Internal microphone audio

    • stream_2/: External microphone audio

  3. File naming format: {stream_name}_{timestamp}.pcm

  4. Audio format: 16 kHz, 16-bit, mono PCM

  5. 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
    
  6. 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