6.2 C++ Interface Usage Example

This section will guide you through implementing the functions listed in the index

Build & Run Instructions

  • Enter the top-level AimDK directory (~/aimdk on the robot) and run the following commands

    source /opt/ros/humble/setup.bash
    colcon build
    source install/local_setup.bash
    ros2 run examples 'function name, e.g., get_mc_action'
    

📝 Code Notes

The full implementation includes complete mechanisms for error handling, signal handling, timeout handling, and more, ensuring the robustness of the program. Please check/modify in the examples directory

Caution

As standard ROS DO NOT handle cross-host service (request-response) well, please refer to SDK examples to use open interfaces in a robust way (with protection mechanisms e.g. exception safety and retransmission)

While the robot is in Stable Standing Mode or Locomotion Mode, DO NOT launch ROS nodes in rapid bulk (no more than 2 nodes per second is recommended), as a large number of nodes joining DDS discovery within a short period causes communication congestion and degrades motion control real-time performance, which may cause the robot to lose balance and fall

6.2.1 Developer Mode

Invoke the 'GetSystemState' service to obtain the robot’s current system status, and use 'MigrateSystemState' to access the corresponding developer mode.

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) { // SUCCESS (1)
128              migrate_retry_count_ = 0;
129              ++migrate_request_id_;
130              RCLCPP_INFO(this->get_logger(),
131                          "Migration request accepted, starting to monitor "
132                          "state...");
133              start_state_monitoring();
134            } else {
135              ++migrate_request_id_;
136              RCLCPP_ERROR(this->get_logger(),
137                           "Migration request failed with status: %d, message: "
138                           "%s",
139                           response->header.status.value,
140                           response->header.message.c_str());
141              rclcpp::shutdown();
142            }
143          } catch (const std::exception &e) {
144            ++migrate_request_id_;
145            RCLCPP_ERROR(this->get_logger(),
146                         "Migration service call failed: %s", e.what());
147            rclcpp::shutdown();
148          }
149        });
150  }
151
152  /**
153   * @brief Start monitoring system state
154   */
155  void start_state_monitoring() {
156    RCLCPP_INFO(this->get_logger(), "Starting to monitor system state...");
157
158    // Create a timer to periodically check system state
159    monitor_timer_ = this->create_wall_timer(
160        1s, std::bind(&SystemStateMigrator::check_system_state, this));
161  }
162
163  /**
164   * @brief Check current system state with retry
165   */
166  void check_system_state() {
167    if (get_state_pending_) {
168      return;
169    }
170    if (get_state_retry_count_ >= max_get_state_retries_) {
171      RCLCPP_ERROR(this->get_logger(),
172                   "Get system state failed after %d attempts, will retry on "
173                   "next monitor cycle",
174                   max_get_state_retries_);
175      get_state_retry_count_ = 0;
176      return;
177    }
178
179    auto request = std::make_shared<aimdk_msgs::srv::GetSystemState::Request>();
180
181    auto now = this->now();
182    request->header.header.stamp.sec = now.seconds();
183    request->header.header.stamp.nanosec = now.nanoseconds() % 1000000000;
184
185    get_state_retry_count_++;
186    get_state_pending_ = true;
187    uint64_t req_id = ++get_state_request_id_;
188
189    get_state_timeout_timer_ = this->create_wall_timer(10s, [this, req_id]() {
190      if (req_id != get_state_request_id_) {
191        return;
192      }
193      get_state_timeout_timer_.reset();
194      get_state_pending_ = false;
195      RCLCPP_WARN(this->get_logger(),
196                  "GetSystemState request timeout (attempt %d/%d), "
197                  "retrying...",
198                  get_state_retry_count_, max_get_state_retries_);
199      check_system_state();
200    });
201
202    auto future = get_state_client_->async_send_request(
203        request,
204        [this,
205         req_id](rclcpp::Client<aimdk_msgs::srv::GetSystemState>::SharedFuture
206                     future) {
207          if (req_id != get_state_request_id_) {
208            return;
209          }
210          if (get_state_timeout_timer_) {
211            get_state_timeout_timer_->cancel();
212            get_state_timeout_timer_.reset();
213          }
214          get_state_pending_ = false;
215
216          try {
217            auto response = future.get();
218            get_state_retry_count_ = 0;
219            ++get_state_request_id_;
220
221            RCLCPP_INFO(
222                this->get_logger(), "Current State: %s, System Status: %u",
223                response->cur_state.c_str(), response->curr_status.value);
224
225            if (is_migration_complete(response)) {
226              RCLCPP_INFO(this->get_logger(),
227                          "Migration to %s completed successfully!",
228                          target_state_.c_str());
229              monitor_timer_->cancel();
230              rclcpp::shutdown();
231            } else {
232              RCLCPP_INFO(this->get_logger(),
233                          "Migration in progress, will check again in 1 "
234                          "second...");
235            }
236          } catch (const std::exception &e) {
237            RCLCPP_ERROR(this->get_logger(),
238                         "GetSystemState service call failed: %s", e.what());
239          }
240        });
241  }
242
243  /**
244   * @brief Check if migration to target state is complete
245   * @param response Response from GetSystemState service
246   * @return true if migration is complete, false otherwise
247   */
248  bool is_migration_complete(
249      const aimdk_msgs::srv::GetSystemState::Response::SharedPtr response) {
250    // Convert current state and target state to lowercase for case-insensitive
251    // comparison
252    std::string current_state_lower = response->cur_state;
253    std::transform(current_state_lower.begin(), current_state_lower.end(),
254                   current_state_lower.begin(), ::tolower);
255
256    std::string target_state_lower = target_state_;
257    std::transform(target_state_lower.begin(), target_state_lower.end(),
258                   target_state_lower.begin(), ::tolower);
259
260    // Check conditions:
261    // 1. Current state equals target state (case-insensitive)
262    //    Special case: Ready auto-transitions to Business, so also accept
263    //    Business when the target is Ready
264    // 2. System status equals 1 (IN_READY), or the target is Ready and
265    //    the system has already auto-transitioned to Business (IN_MOVE)
266    bool state_match =
267        (current_state_lower == target_state_lower) ||
268        (target_state_lower == "ready" && current_state_lower == "business");
269    bool ready_auto_transitioned =
270        (target_state_lower == "ready" && current_state_lower == "business");
271    bool status_match = ready_auto_transitioned ||
272                        (response->curr_status.value ==
273                         aimdk_msgs::msg::SystemStatus::IN_READY); // READY (1)
274
275    if (state_match && status_match) {
276      return true;
277    }
278
279    // Log detailed information if not complete
280    if (!state_match) {
281      RCLCPP_INFO(this->get_logger(),
282                  "State mismatch: current='%s', expected='%s'",
283                  response->cur_state.c_str(), target_state_.c_str());
284    }
285    if (!status_match) {
286      RCLCPP_INFO(this->get_logger(),
287                  "Status mismatch: current= %u, expected=1 (IN_READY)",
288                  response->curr_status.value);
289    }
290    RCLCPP_ERROR(this->get_logger(), "State Migrate Fail");
291
292    return false;
293  }
294
295  // Target state to migrate to
296  std::string target_state_;
297
298  // Service clients
299  rclcpp::Client<aimdk_msgs::srv::MigrateSystemState>::SharedPtr
300      migrate_client_;
301  rclcpp::Client<aimdk_msgs::srv::GetSystemState>::SharedPtr get_state_client_;
302
303  // Timers
304  rclcpp::TimerBase::SharedPtr check_timer_;
305  rclcpp::TimerBase::SharedPtr monitor_timer_;
306
307  // Retry counters and timeout timers for migration
308  int migrate_retry_count_ = 0;
309  const int max_migrate_retries_ = 5;
310  uint64_t migrate_request_id_ = 0;
311  bool migrate_pending_ = false;
312  rclcpp::TimerBase::SharedPtr migrate_timeout_timer_;
313
314  // Retry counters and timeout timers for get state
315  int get_state_retry_count_ = 0;
316  const int max_get_state_retries_ = 3;
317  uint64_t get_state_request_id_ = 0;
318  bool get_state_pending_ = false;
319  rclcpp::TimerBase::SharedPtr get_state_timeout_timer_;
320};
321
322/**
323 * @brief Print usage information with detailed mode descriptions
324 */
325void print_usage(const char *program_name) {
326  std::cout << "Usage: " << program_name << " <target_state>" << std::endl;
327  std::cout << std::endl;
328  std::cout << "Available target states:" << std::endl;
329  std::cout << "  " << program_name << " Ready" << std::endl;
330  std::cout << "  " << program_name << " Develop_Nav" << std::endl;
331  std::cout << "  " << program_name << " Develop_Audio_Linux" << std::endl;
332  std::cout << "  " << program_name << " Develop_Audio_ROS" << std::endl;
333  std::cout << "  " << program_name << " Develop_MC" << std::endl;
334  std::cout << std::endl;
335
336  std::cout << "Mode Descriptions:" << std::endl;
337  std::cout << "==================" << std::endl;
338
339  // Ready mode description
340  std::cout << "1. Ready - System Default Mode" << std::endl;
341  std::cout
342      << "   • Description: System enters this mode by default after startup"
343      << std::endl;
344  std::cout << "   • Purpose: Normal operation mode, ready for general tasks"
345            << std::endl;
346  std::cout
347      << "   • Use case: Return to normal operation after development work"
348      << std::endl;
349  std::cout << std::endl;
350
351  // Develop_Nav mode description
352  std::cout << "2. Develop_Nav - Navigation Development Mode" << std::endl;
353  std::cout
354      << "   • Description: Special mode for navigation system development"
355      << std::endl;
356  std::cout << "   • Purpose: Enables navigation-related debugging and testing"
357            << std::endl;
358  std::cout << "   • Use case: When developing or testing navigation "
359               "algorithms, SLAM, path planning"
360            << std::endl;
361  std::cout << "   • Features: May provide access to raw sensor data, "
362               "navigation debug topics"
363            << std::endl;
364  std::cout << std::endl;
365
366  // Develop_Audio_Linux mode description
367  std::cout << "3. Develop_Audio_Linux - System-Level Audio Development Mode"
368            << std::endl;
369  std::cout << "   • Description: Mode for low-level audio system development"
370            << std::endl;
371  std::cout << "   • Purpose: Access to system audio streams at Linux level"
372            << std::endl;
373  std::cout << "   • Use case: When developing audio drivers, audio processing "
374               "at system level"
375            << std::endl;
376  std::cout << "   • Features: Direct access to audio hardware, "
377               "ALSA/PulseAudio interfaces"
378            << std::endl;
379  std::cout << std::endl;
380
381  // Develop_Audio_ROS mode description
382  std::cout << "4. Develop_Audio_ROS - ROS Audio Development Mode" << std::endl;
383  std::cout
384      << "   • Description: Mode for ROS-based audio application development"
385      << std::endl;
386  std::cout << "   • Purpose: Access to audio data through ROS topics"
387            << std::endl;
388  std::cout << "   • Use case: When developing ROS nodes that process audio, "
389               "speech recognition"
390            << std::endl;
391  std::cout << "   • Features: Audio data published as ROS topics, ROS message "
392               "interfaces"
393            << std::endl;
394  std::cout << std::endl;
395
396  // Develop_MC mode description
397  std::cout << "5. Develop_MC - Motion Control Development Mode" << std::endl;
398  std::cout << "   • Description: Mode for motion control system development"
399            << std::endl;
400  std::cout
401      << "   • Purpose: Enables direct control and testing of motion systems"
402      << std::endl;
403  std::cout << "   • Use case: When developing motor control algorithms, "
404               "testing motion hardware"
405            << std::endl;
406  std::cout << "   • Features: Low-level motor control access, motion system "
407               "debugging tools"
408            << std::endl;
409  std::cout << std::endl;
410
411  std::cout << "Program Workflow:" << std::endl;
412  std::cout << "=================" << std::endl;
413  std::cout
414      << "1. Calls MigrateSystemState service with the specified target state"
415      << std::endl;
416  std::cout << "2. Continuously monitors GetSystemState service every second"
417            << std::endl;
418  std::cout << "3. Exits when both conditions are met:" << std::endl;
419  std::cout << "   • Current State equals target state (case-insensitive)"
420            << std::endl;
421  std::cout << "   • System Status equals 1 (IN_READY)" << std::endl;
422  std::cout << std::endl;
423  std::cout << "Note: The program will automatically exit when migration is "
424               "complete or on error."
425            << std::endl;
426}
427
428/**
429 * @brief Validate if the target state is a known development mode
430 * @param state The state to validate
431 * @return true if state is valid, false otherwise
432 */
433bool is_valid_state(const std::string &state) {
434  // List of known valid states
435  const std::vector<std::string> valid_states = {
436      "Ready", "Develop_Nav", "Develop_Audio_Linux", "Develop_Audio_ROS",
437      "Develop_MC"};
438
439  // Convert input to lowercase for case-insensitive comparison
440  std::string state_lower = state;
441  std::transform(state_lower.begin(), state_lower.end(), state_lower.begin(),
442                 ::tolower);
443
444  // Check if state is in the list of valid states
445  for (const auto &valid_state : valid_states) {
446    std::string valid_state_lower = valid_state;
447    std::transform(valid_state_lower.begin(), valid_state_lower.end(),
448                   valid_state_lower.begin(), ::tolower);
449
450    if (state_lower == valid_state_lower) {
451      return true;
452    }
453  }
454
455  return false;
456}
457
458/**
459 * @brief Main function
460 * @param argc Argument count
461 * @param argv Argument vector
462 * @return Exit code
463 */
464int main(int argc, char **argv) {
465  // Check command line arguments
466  if (argc != 2) {
467    print_usage(argv[0]);
468    return 1;
469  }
470
471  // Get target state from command line argument
472  std::string target_state = argv[1];
473
474  // Validate target state
475  if (target_state.empty()) {
476    std::cerr << "Error: Target state cannot be empty" << std::endl;
477    print_usage(argv[0]);
478    return 1;
479  }
480
481  // Optional: Validate against known states
482  if (!is_valid_state(target_state)) {
483    std::cout << "Warning: '" << target_state
484              << "' is not in the list of known states." << std::endl;
485    std::cout << "The program will still attempt to migrate to this state."
486              << std::endl;
487    std::cout << "Continue? (y/n): ";
488
489    std::string response;
490    std::getline(std::cin, response);
491
492    if (response != "y" && response != "Y") {
493      std::cout << "Operation cancelled." << std::endl;
494      return 0;
495    }
496  }
497
498  // Initialize ROS2
499  rclcpp::init(argc, argv);
500
501  // Create migrator node with target state
502  auto node = std::make_shared<SystemStateMigrator>(target_state);
503
504  // Create single-threaded executor
505  auto executor = std::make_shared<rclcpp::executors::SingleThreadedExecutor>();
506
507  // Add node to executor
508  executor->add_node(node);
509
510  // Run executor (blocking call)
511  executor->spin();
512
513  // Cleanup
514  executor->remove_node(node);
515  rclcpp::shutdown();
516
517  return 0;
518}

Usage Instructions

ros2 run examples migrate_system_state Develop_MC

Output Example

[INFO] [1770125761.202173770] [system_state_migrator]: Target state: Develop_MC
[INFO] [1770125762.202825297] [system_state_migrator]: Both services are available, starting migration to Develop_MC...
[INFO] [1770125762.202949414] [system_state_migrator]: Sending migration request to Develop_MC state (attempt 1/5)...
[INFO] [1770125762.264580770] [system_state_migrator]: Migration request accepted, starting to monitor state...
[INFO] [1770125762.264608479] [system_state_migrator]: Starting to monitor system state...
[INFO] [1770125763.268422431] [system_state_migrator]: Current State: Business, System Status: 2
[INFO] [1770125763.268513271] [system_state_migrator]: State mismatch: current='Business', expected='Develop_MC'
[INFO] [1770125763.268531394] [system_state_migrator]: Status mismatch: current= 2, expected=1 (IN_READY)
[INFO] [1770125763.268545617] [system_state_migrator]: Migration in progress, will check again in 1 second...
[INFO] [1770125764.266388052] [system_state_migrator]: Current State: Business, System Status: 2
[INFO] [1770125764.266492040] [system_state_migrator]: State mismatch: current='Business', expected='Develop_MC'
[INFO] [1770125764.266509640] [system_state_migrator]: Status mismatch: current= 2, expected=1 (IN_READY)
[INFO] [1770125764.266532986] [system_state_migrator]: Migration in progress, will check again in 1 second...
[INFO] [1770125765.266452308] [system_state_migrator]: Current State: Develop_MC, System Status: 1
[INFO] [1770125765.266562888] [system_state_migrator]: Migration to Develop_MC completed successfully!

6.2.2 Get Robot Mode

Retrieve the robot’s current operating mode by calling the GetMcAction service, including the description, and status information.

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] [1779173975.378396584] [get_mc_action_client]: ✅ GetMcAction client node created.
[INFO] [1779173975.961720731] [get_mc_action_client]: 🟢 Service available, ready to send request.
[INFO] [1779173975.961868725] [get_mc_action_client]: 📨 Sending request to get robot mode
[INFO] [1779173975.965180630] [get_mc_action_client]: ✅ Robot mode get successfully.
[INFO] [1779173975.965282450] [get_mc_action_client]: Mode name: PASSIVE_DEFAULT
[INFO] [1779173975.965307345] [get_mc_action_client]: Mode status: 100

Interface Reference

  • Service: /aimdk_5Fmsgs/srv/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 diagram, other transitions will be rejected
Locomotion Mode (LOCOMOTION_DEFAULT) and Stable Standing Mode (STAND_DEFAULT) are unified and will auto switch internally, so switching manually to the nearer one is enough

  1#include "aimdk_msgs/srv/set_mc_action.hpp"
  2#include "aimdk_msgs/msg/common_response.hpp"
  3#include "aimdk_msgs/msg/common_state.hpp"
  4#include "aimdk_msgs/msg/mc_action.hpp"
  5#include "aimdk_msgs/msg/mc_action_command.hpp"
  6#include "aimdk_msgs/msg/request_header.hpp"
  7#include "rclcpp/rclcpp.hpp"
  8#include <chrono>
  9#include <iomanip>
 10#include <iostream>
 11#include <memory>
 12#include <signal.h>
 13#include <unordered_map>
 14#include <vector>
 15
 16// Global variable used for signal handling
 17std::shared_ptr<rclcpp::Node> g_node = nullptr;
 18
 19// Signal handler function
 20void signal_handler(int signal) {
 21  if (g_node) {
 22    RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
 23                signal);
 24    g_node.reset();
 25  }
 26  rclcpp::shutdown();
 27  exit(signal);
 28}
 29
 30class SetMcActionClient : public rclcpp::Node {
 31public:
 32  SetMcActionClient() : Node("set_mc_action_client") {
 33
 34    client_ = this->create_client<aimdk_msgs::srv::SetMcAction>(
 35        "/aimdk_5Fmsgs/srv/SetMcAction");
 36    RCLCPP_INFO(this->get_logger(), "✅ SetMcAction client node created.");
 37
 38    // Wait for the service to become available
 39    while (!client_->wait_for_service(std::chrono::seconds(2))) {
 40      RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
 41    }
 42    RCLCPP_INFO(this->get_logger(),
 43                "🟢 Service available, ready to send request.");
 44  }
 45
 46  bool send_request(std::string &action_name) {
 47    try {
 48      auto request = std::make_shared<aimdk_msgs::srv::SetMcAction::Request>();
 49      request->header = aimdk_msgs::msg::RequestHeader();
 50
 51      request->source = "node.set_mc_action";
 52
 53      // Set robot mode
 54      aimdk_msgs::msg::McActionCommand command;
 55      command.action_desc = action_name;
 56      request->command = command;
 57
 58      RCLCPP_INFO(this->get_logger(), "📨 Sending request to set robot mode: %s",
 59                  action_name.c_str());
 60
 61      // Set Service Call Timeout
 62      const std::chrono::milliseconds timeout(250);
 63      for (int i = 0; i < 8; i++) {
 64        request->header.stamp = this->now();
 65        auto future = client_->async_send_request(request);
 66        auto retcode = rclcpp::spin_until_future_complete(shared_from_this(),
 67                                                          future, timeout);
 68        if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
 69          // retry as remote peer is NOT handled well by ROS
 70          RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
 71          continue;
 72        }
 73        // future.done
 74        auto response = future.get();
 75        if (response->response.status.value ==
 76            aimdk_msgs::msg::CommonState::SUCCESS) { // SUCCESS (1)
 77          RCLCPP_INFO(this->get_logger(), "✅ Robot mode set successfully.");
 78          return true;
 79        } else {
 80          RCLCPP_ERROR(this->get_logger(), "❌ Failed (code=%ld): %s",
 81                       static_cast<long>(response->response.header.code),
 82                       response->response.message.c_str());
 83          return false;
 84        }
 85      }
 86      RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
 87      return false;
 88    } catch (const std::exception &e) {
 89      RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
 90      return false;
 91    }
 92  }
 93
 94private:
 95  rclcpp::Client<aimdk_msgs::srv::SetMcAction>::SharedPtr client_;
 96};
 97
 98static std::unordered_map<std::string, std::vector<std::string>> g_action_info =
 99    {
100        {"PASSIVE_DEFAULT", {"PD", "joints with zero torque"}},
101        {"DAMPING_DEFAULT", {"DD", "joints in damping mode"}},
102        {"JOINT_DEFAULT", {"JD", "Position Control Stand (joints locked)"}},
103        {"STAND_DEFAULT", {"SD", "Stable Stand (auto-balance)"}},
104        {"LOCOMOTION_DEFAULT", {"LD", "locomotion mode (walk or run)"}},
105        {"HEAD_ONLY", {"HO", "head-only control"}},
106        {"UPPERBODY_REMOTE_SPLIT",
107         {"URS", "upper body remote split (head + arms + hands)"}},
108};
109
110int main(int argc, char *argv[]) {
111  try {
112    rclcpp::init(argc, argv);
113
114    // Set up signal handlers
115    signal(SIGINT, signal_handler);
116    signal(SIGTERM, signal_handler);
117
118    // Create node
119    g_node = std::make_shared<SetMcActionClient>();
120    auto client = std::dynamic_pointer_cast<SetMcActionClient>(g_node);
121
122    if (client) {
123      std::unordered_map<std::string, std::string> choices;
124      std::string motion;
125
126      // Prefer command-line argument; otherwise prompt user
127      if (argc > 1) {
128        motion = argv[1];
129        RCLCPP_INFO(g_node->get_logger(),
130                    "Using abbr of motion mode from cmdline: %s", argv[1]);
131      } else {
132        std::cout << std::left << std::setw(4) << "abbr"
133                  << " - " << std::setw(20) << "robot mode"
134                  << " : "
135                  << "description" << std::endl;
136        for (auto &it : g_action_info) {
137          std::cout << std::left << std::setw(4) << it.second[0] << " - "
138                    << std::setw(20) << it.first << " : " << it.second[1]
139                    << std::endl;
140        }
141        std::cout << "Enter abbr of motion mode:";
142        std::cin >> motion;
143      }
144      for (auto &it : g_action_info) {
145        choices[it.second[0]] = it.first;
146      }
147
148      auto m = choices.find(motion);
149      if (m != choices.end()) {
150        auto &action_name = m->second;
151        client->send_request(action_name);
152      } else {
153        RCLCPP_ERROR(g_node->get_logger(), "Invalid abbr of robot mode: %s",
154                     motion.c_str());
155      }
156    }
157
158    // Clean up resources
159    g_node.reset();
160    rclcpp::shutdown();
161
162    return 0;
163  } catch (const std::exception &e) {
164    RCLCPP_ERROR(rclcpp::get_logger("main"),
165                 "Program exited with exception: %s", e.what());
166    return 1;
167  }
168}

Usage Instructions

# Use command-line arguments to set the mode (recommended)
ros2 run py_examples set_mc_action JD  # Zero-Torque >> Position-Control Standing
ros2 run py_examples set_mc_action SD  # Ensure your robot's feet on the ground, Position-Control Standing >> Stable Standing
# Stable Standing >> Locomotion Mode. auto done internally, don't switch manually

# Or run without arguments and the program will prompt for input
ros2 run py_examples set_mc_action

Output Example

...
[INFO] [1764066567.502968540] [set_mc_action_client]: ✅ Robot mode set successfully.

Notes

  • Ensure the robot is standing and its feet are on the ground before switching to STAND_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_input_action.hpp"
  6#include "aimdk_msgs/msg/mc_preset_motion.hpp"
  7#include "aimdk_msgs/msg/request_header.hpp"
  8#include "aimdk_msgs/srv/set_mc_input_source.hpp"
  9#include "aimdk_msgs/srv/set_mc_preset_motion.hpp"
 10#include "rclcpp/rclcpp.hpp"
 11#include <chrono>
 12#include <cinttypes>
 13#include <iostream>
 14#include <memory>
 15#include <signal.h>
 16
 17namespace {
 18constexpr const char *kInputSourceName = "node.preset_motion";
 19constexpr int32_t kInputSourcePriority = 40;
 20constexpr int32_t kInputSourceTimeout = 1000;
 21} // namespace
 22
 23std::shared_ptr<rclcpp::Node> g_node = nullptr;
 24
 25void signal_handler(int signal) {
 26  if (g_node) {
 27    RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
 28                signal);
 29    g_node.reset();
 30  }
 31  rclcpp::shutdown();
 32  exit(signal);
 33}
 34
 35class PresetMotionClient : public rclcpp::Node {
 36public:
 37  PresetMotionClient() : Node("preset_motion_client") {
 38    client_ = this->create_client<aimdk_msgs::srv::SetMcPresetMotion>(
 39        "/aimdk_5Fmsgs/srv/SetMcPresetMotion");
 40    input_source_client_ =
 41        this->create_client<aimdk_msgs::srv::SetMcInputSource>(
 42            "/aimdk_5Fmsgs/srv/SetMcInputSource");
 43
 44    RCLCPP_INFO(this->get_logger(), "✅ SetMcPresetMotion client node created.");
 45
 46    // Wait for the service to become available
 47    while (!client_->wait_for_service(std::chrono::seconds(2))) {
 48      RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
 49    }
 50    RCLCPP_INFO(this->get_logger(),
 51                "🟢 Service available, ready to send request.");
 52  }
 53
 54  bool register_input_source() {
 55    if (!input_source_client_->wait_for_service(std::chrono::seconds(8))) {
 56      RCLCPP_ERROR(this->get_logger(),
 57                   "❌ SetMcInputSource service unavailable.");
 58      return false;
 59    }
 60
 61    // Try ADD first; if it fails (likely already registered), fall back to
 62    // ENABLE
 63    if (call_input_source( // ADD (1001)
 64            aimdk_msgs::msg::McInputAction::INPUTACTION_ADD)) {
 65      RCLCPP_INFO(this->get_logger(), "✅ Input source \"%s\" registered (ADD).",
 66                  kInputSourceName);
 67      return true;
 68    }
 69    RCLCPP_INFO(this->get_logger(),
 70                "ADD failed (likely already registered), trying ENABLE...");
 71    if (call_input_source( // ENABLE (2001)
 72            aimdk_msgs::msg::McInputAction::INPUTACTION_ENABLE)) {
 73      RCLCPP_INFO(this->get_logger(), "✅ Input source \"%s\" enabled.",
 74                  kInputSourceName);
 75      return true;
 76    }
 77    RCLCPP_ERROR(this->get_logger(),
 78                 "❌ Failed to register input source via ADD or ENABLE.");
 79    return false;
 80  }
 81
 82  bool send_request(int area_id, int motion_id) {
 83    try {
 84      auto request =
 85          std::make_shared<aimdk_msgs::srv::SetMcPresetMotion::Request>();
 86      request->header = aimdk_msgs::msg::RequestHeader();
 87
 88      request->input_source.name = kInputSourceName;
 89      request->input_source.priority = kInputSourcePriority;
 90      request->input_source.timeout = kInputSourceTimeout;
 91
 92      aimdk_msgs::msg::McPresetMotion motion;
 93      aimdk_msgs::msg::McControlArea area;
 94
 95      motion.value = motion_id; // Preset motion ID
 96      area.value = area_id;     // Control area ID
 97      request->motion = motion;
 98      request->area = area;
 99      request->interrupt = false; // Not interrupt current motion
100
101      RCLCPP_INFO(this->get_logger(),
102                  "📨 Sending request to set preset motion: motion=%d, area=%d",
103                  motion_id, area_id);
104
105      const std::chrono::milliseconds timeout(250);
106      for (int i = 0; i < 8; i++) {
107        request->header.stamp = this->now();
108        auto future = client_->async_send_request(request);
109        auto retcode = rclcpp::spin_until_future_complete(shared_from_this(),
110                                                          future, timeout);
111        if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
112          // retry as remote peer is NOT handled well by ROS
113          RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
114          continue;
115        }
116        // future.done
117        auto response = future.get();
118        if (response->response.header.code == 0) {
119          RCLCPP_INFO(this->get_logger(),
120                      "✅ Preset motion set successfully: %" PRIu64,
121                      response->response.task_id);
122          return true;
123        } else if (response->response.state.value ==
124                   aimdk_msgs::msg::CommonState::RUNNING) { // RUNNING (400)
125          RCLCPP_INFO(this->get_logger(), "⏳ Preset motion executing: %" PRIu64,
126                      response->response.task_id);
127          return true;
128        } else {
129          RCLCPP_WARN(this->get_logger(),
130                      "❌ Failed to set preset motion: %" PRIu64,
131                      response->response.task_id);
132          return false;
133        }
134      }
135      RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
136      return false;
137    } catch (const std::exception &e) {
138      RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
139      return false;
140    }
141  }
142
143private:
144  bool call_input_source(int32_t action_value) {
145    auto req = std::make_shared<aimdk_msgs::srv::SetMcInputSource::Request>();
146    req->action.value = action_value;
147    req->input_source.name = kInputSourceName;
148    req->input_source.priority = kInputSourcePriority;
149    req->input_source.timeout = kInputSourceTimeout;
150
151    const std::chrono::milliseconds timeout(250);
152    for (int i = 0; i < 8; i++) {
153      req->request.header.stamp = this->now();
154      auto future = input_source_client_->async_send_request(req);
155      auto retcode = rclcpp::spin_until_future_complete(shared_from_this(),
156                                                        future, timeout);
157      if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
158        // retry as remote peer is NOT handled well by ROS
159        RCLCPP_INFO(
160            this->get_logger(), "trying input source action %s (%d)... [%d]",
161            action_value == aimdk_msgs::msg::McInputAction::INPUTACTION_ADD
162                ? "ADD"
163                : "ENABLE",
164            action_value, i);
165        continue;
166      }
167      auto response = future.get();
168      return response->response.header.code == 0;
169    }
170    return false;
171  }
172
173  rclcpp::Client<aimdk_msgs::srv::SetMcPresetMotion>::SharedPtr client_;
174  rclcpp::Client<aimdk_msgs::srv::SetMcInputSource>::SharedPtr
175      input_source_client_;
176};
177
178int main(int argc, char *argv[]) {
179  try {
180    rclcpp::init(argc, argv);
181    signal(SIGINT, signal_handler);
182    signal(SIGTERM, signal_handler);
183
184    g_node = std::make_shared<PresetMotionClient>();
185    // Cast g_node (std::shared_ptr<rclcpp::Node>) to a derived
186    // PresetMotionClient pointer (std::shared_ptr<PresetMotionClient>)
187    auto client = std::dynamic_pointer_cast<PresetMotionClient>(g_node);
188
189    int area = 1;
190    int motion = 1003;
191    std::cout << "Enter arm area ID (1-left, 2-right): ";
192    std::cin >> area;
193    std::cout
194        << "Enter preset motion ID (1001-raise, 1002-wave, 1003-handshake, "
195           "1004-airkiss): ";
196    std::cin >> motion;
197    if (client) {
198      if (!client->register_input_source()) {
199        RCLCPP_ERROR(g_node->get_logger(),
200                     "❌ Input source registration failed, exiting");
201      } else {
202        client->send_request(area, motion);
203      }
204    }
205
206    // Clean up resources
207    g_node.reset();
208    rclcpp::shutdown();
209
210    return 0;
211  } catch (const std::exception &e) {
212    RCLCPP_ERROR(rclcpp::get_logger("main"),
213                 "Program exited with exception: %s", e.what());
214    return 1;
215  }
216}

6.2.5 Gripper Control

This example uses hand_control. By publishing messages to the topic /aima/hal/joint/hand/command, you can control the movement of the gripper.

Attention

Warning ⚠️ : Before running this example, you must stop the native motion control module using aima em stop-app mc on the robot’s Motion Control Computing Unit (PC1) to obtain control authority. Ensure robot safety at all times.

 1#include "aimdk_msgs/msg/hand_command.hpp"
 2#include "aimdk_msgs/msg/hand_command_array.hpp"
 3#include "aimdk_msgs/msg/hand_type.hpp"
 4#include "aimdk_msgs/msg/message_header.hpp"
 5#include "rclcpp/rclcpp.hpp"
 6#include <chrono>
 7#include <memory>
 8#include <vector>
 9
10class HandControl : public rclcpp::Node {
11public:
12  HandControl()
13      : Node("hand_control"), position_pairs_({
14                                  {1.0, 1.0},
15                                  {0.0, 0.0},
16                                  {0.5, 0.5},
17                                  {0.2, 0.8},
18                                  {0.7, 0.3},
19                              }),
20        current_index_(0) {
21    publisher_ = this->create_publisher<aimdk_msgs::msg::HandCommandArray>(
22        "/aima/hal/joint/hand/command", 10);
23
24    timer_ = this->create_wall_timer(
25        std::chrono::milliseconds(20), // 50Hz
26        std::bind(&HandControl::publish_hand_commands, this));
27
28    last_switch_time_ = now();
29    RCLCPP_INFO(this->get_logger(), "The hand control node has been started!");
30  }
31
32  void publish_hand_commands() {
33    // 1. Determine if it's time to switch parameters.
34    auto now_time = this->now();
35    if ((now_time - last_switch_time_).seconds() >= 2.0) {
36      current_index_ = (current_index_ + 1) % position_pairs_.size();
37      last_switch_time_ = now_time;
38      RCLCPP_INFO(this->get_logger(),
39                  "Switched to the next parameter group, index=%zu (left=%.2f, "
40                  "right=%.2f)",
41                  current_index_, position_pairs_[current_index_].first,
42                  position_pairs_[current_index_].second);
43    }
44
45    auto msg = std::make_unique<aimdk_msgs::msg::HandCommandArray>();
46    msg->header = aimdk_msgs::msg::MessageHeader();
47
48    float left_position = position_pairs_[current_index_].first;
49    float right_position = position_pairs_[current_index_].second;
50
51    aimdk_msgs::msg::HandCommand left_hands;
52    left_hands.name = "left_hand";
53    left_hands.position = left_position;
54    left_hands.velocity = 1.0;
55    left_hands.acceleration = 1.0;
56    left_hands.deceleration = 1.0;
57    left_hands.effort = 1.0;
58
59    aimdk_msgs::msg::HandCommand right_hands;
60    right_hands.name = "right_hand";
61    right_hands.position = right_position;
62    right_hands.velocity = 1.0;
63    right_hands.acceleration = 1.0;
64    right_hands.deceleration = 1.0;
65    right_hands.effort = 1.0;
66
67    msg->left_hands.push_back(left_hands);
68    msg->right_hands.push_back(right_hands);
69    msg->left_hand_type.value = aimdk_msgs::msg::HandType::CLAW; // grippers (2)
70    msg->right_hand_type.value =
71        aimdk_msgs::msg::HandType::CLAW; // grippers (2)
72
73    publisher_->publish(std::move(msg));
74  }
75
76private:
77  rclcpp::Publisher<aimdk_msgs::msg::HandCommandArray>::SharedPtr publisher_;
78  rclcpp::TimerBase::SharedPtr timer_;
79
80  std::vector<std::pair<float, float>> position_pairs_;
81  size_t current_index_;
82
83  rclcpp::Time last_switch_time_;
84};
85
86int main(int argc, char *argv[]) {
87  rclcpp::init(argc, argv);
88  auto hand_control_node = std::make_shared<HandControl>();
89  rclcpp::spin(hand_control_node);
90  rclcpp::shutdown();
91  return 0;
92}

6.2.6 Dexterous Hand Control

This example uses omnihand_control. By publishing messages to the topic /aima/hal/joint/hand/command, you can control the movement of the omnihand.

Attention

Warning ⚠️ : Before running this example, you must stop the native motion control module using aima em stop-app mc on the robot’s Motion Control Computing Unit (PC1) to obtain control authority. Ensure robot safety at all times.

  1#include <aimdk_msgs/msg/hand_command_array.hpp>
  2#include <aimdk_msgs/msg/hand_type.hpp>
  3#include <array>
  4#include <chrono>
  5#include <memory>
  6#include <rclcpp/rclcpp.hpp>
  7
  8using namespace std::chrono_literals;
  9
 10class HandCommandPublisher : public rclcpp::Node {
 11public:
 12  HandCommandPublisher() : Node("hand_command_publisher") {
 13    publisher_ = this->create_publisher<aimdk_msgs::msg::HandCommandArray>(
 14        "/aima/hal/joint/hand/command", 10);
 15
 16    // Create a timer to publish once per second
 17    timer_ = this->create_wall_timer(
 18        1s, std::bind(&HandCommandPublisher::publish_command, this));
 19
 20    RCLCPP_INFO(this->get_logger(), "Hand control node started!");
 21  }
 22
 23private:
 24  void publish_command() {
 25    auto message = aimdk_msgs::msg::HandCommandArray();
 26
 27    // Set header
 28    message.header.stamp = this->now();
 29    message.header.frame_id = "hand_command";
 30
 31    // Set the hand type
 32    message.left_hand_type.value =
 33        aimdk_msgs::msg::HandType::NIMBLE_HANDS; // dexterous hands (1)
 34    message.right_hand_type.value =
 35        aimdk_msgs::msg::HandType::NIMBLE_HANDS; // dexterous hands (1)
 36
 37    const char *left_names[10] = {"L_thumb_roll_joint", "L_thumb_abad_joint",
 38                                  "L_thumb_mcp_joint",  "L_index_abad_joint",
 39                                  "L_index_pip_joint",  "L_middle_pip_joint",
 40                                  "L_ring_abad_joint",  "L_ring_pip_joint",
 41                                  "L_pinky_abad_joint", "L_pinky_pip_joint"};
 42    const char *right_names[10] = {"R_thumb_roll_joint", "R_thumb_abad_joint",
 43                                   "R_thumb_mcp_joint",  "R_index_abad_joint",
 44                                   "R_index_pip_joint",  "R_middle_pip_joint",
 45                                   "R_ring_abad_joint",  "R_ring_pip_joint",
 46                                   "R_pinky_abad_joint", "R_pinky_pip_joint"};
 47
 48    // Create left hand command array
 49    message.left_hands.resize(10);
 50    for (int i = 0; i < 10; i++) {
 51      message.left_hands[i].name = left_names[i];
 52      message.left_hands[i].position = 0.0;
 53      message.left_hands[i].velocity = 0.1;
 54      message.left_hands[i].acceleration = 0.0;
 55      message.left_hands[i].deceleration = 0.0;
 56      message.left_hands[i].effort = 0.0;
 57    }
 58
 59    // Create right hand command array
 60    message.right_hands.resize(10);
 61    for (int i = 0; i < 10; i++) {
 62      message.right_hands[i].name = right_names[i];
 63      message.right_hands[i].position = 0.0;
 64      message.right_hands[i].velocity = 0.1;
 65      message.right_hands[i].acceleration = 0.0;
 66      message.right_hands[i].deceleration = 0.0;
 67      message.right_hands[i].effort = 0.0;
 68    }
 69
 70    // Absolute joint targets in rad = half-grip
 71    // Joint order: [thumb_roll, thumb_abad, thumb_mcp, index_abad, index_pip,
 72    //               middle_pip, ring_abad, ring_pip, pinky_abad, pinky_pip]
 73    const std::array<double, 10> left_hand_target = {
 74        -0.6, 0.8, -0.4, 0.1, 0.7, 0.7, -0.1, 0.7, -0.1, 0.7};
 75    const std::array<double, 10> right_hand_target = {
 76        0.6, -0.8, 0.4, -0.1, 0.7, 0.7, 0.1, 0.7, 0.1, 0.7};
 77
 78    if (target_finger < 10) {
 79      message.right_hands[target_finger].position =
 80          right_hand_target[target_finger];
 81    } else {
 82      int target_finger_ = target_finger - 10;
 83      message.left_hands[target_finger_].position =
 84          left_hand_target[target_finger_];
 85    }
 86
 87    // Publish the message
 88    publisher_->publish(message);
 89
 90    RCLCPP_INFO(this->get_logger(),
 91                "Published hand command with target_finger: %d", target_finger);
 92
 93    update_target_finger();
 94  }
 95
 96  void update_target_finger() {
 97    if (increasing_) {
 98      target_finger += step_;
 99      if (target_finger >= 19) {
100        target_finger = 19;
101        increasing_ = false;
102      }
103    } else {
104      target_finger -= step_;
105      if (target_finger <= 0) {
106        target_finger = 0;
107        increasing_ = true;
108      }
109    }
110  }
111
112  rclcpp::Publisher<aimdk_msgs::msg::HandCommandArray>::SharedPtr publisher_;
113  rclcpp::TimerBase::SharedPtr timer_;
114
115  int target_finger = 0;
116  int step_ = 1;
117  bool increasing_ = true;
118};
119
120int main(int argc, char **argv) {
121  rclcpp::init(argc, argv);
122  auto node = std::make_shared<HandCommandPublisher>();
123  rclcpp::spin(node);
124  rclcpp::shutdown();
125  return 0;
126}

6.2.7 Dexterous Hand Tactile Data (T2.1 Support)

This example uses omnihand_touch. By subscribing to the /aima/hal/joint/hand/state, you can monitor the pressure of the omnihand finger touch sensor.

Attention

Warning ⚠️ : Before running this example, you must stop the native motion control module using aima em stop-app mc on the robot’s Motion Control Computing Unit (PC1) to obtain control authority. Ensure robot safety at all times.

  1//
  2// Created by agiuser on 2026/1/27.
  3//
  4#include <aimdk_msgs/msg/hand_command_array.hpp>
  5#include <aimdk_msgs/msg/hand_state_array.hpp>
  6#include <aimdk_msgs/msg/hand_type.hpp>
  7#include <chrono>
  8#include <iomanip>
  9#include <iostream>
 10#include <rclcpp/rclcpp.hpp>
 11
 12using namespace std::chrono_literals;
 13
 14class HandStateSubscriber : public rclcpp::Node {
 15public:
 16  HandStateSubscriber() : Node("hand_state_subscriber") {
 17    publisher_ = this->create_publisher<aimdk_msgs::msg::HandCommandArray>(
 18        "/aima/hal/joint/hand/command", 10);
 19    rclcpp::QoS qos_profile(rclcpp::KeepLast(10));
 20    qos_profile.best_effort(); // Set reliability to BEST_EFFORT
 21    // Create subscriber
 22    subscription_ = this->create_subscription<aimdk_msgs::msg::HandStateArray>(
 23        "/aima/hal/joint/hand/state", qos_profile,
 24        std::bind(&HandStateSubscriber::topic_callback, this,
 25                  std::placeholders::_1));
 26    // Create a timer to publish once per second
 27    timer_ = this->create_wall_timer(
 28        1s, std::bind(&HandStateSubscriber::publish_command, this));
 29    RCLCPP_INFO(
 30        this->get_logger(),
 31        "Subscriber started, listening to /aima/hal/joint/hand/state topic...");
 32  }
 33
 34private:
 35  void topic_callback(const aimdk_msgs::msg::HandStateArray::SharedPtr msg) {
 36    // Print message header information
 37    RCLCPP_INFO(this->get_logger(),
 38                "Message received - Sequence: %u, Timestamp: %d.%09d",
 39                msg->header.sequence, msg->header.stamp.sec,
 40                msg->header.stamp.nanosec);
 41
 42    // Print left hand touch sensor data
 43    print_touch_sensor_data("Left Hand", msg->left_touch_sensors);
 44
 45    // Print right hand touch sensor data
 46    print_touch_sensor_data("Right Hand", msg->right_touch_sensors);
 47
 48    std::cout << std::endl;
 49  }
 50
 51  /**
 52   * @brief Print touch sensor data for a specific hand
 53   * @param hand_name Name of the hand (Left Hand/Right Hand)
 54   * @param sensor_data Touch sensor data structure
 55   */
 56  void print_touch_sensor_data(
 57      const std::string &hand_name,
 58      const aimdk_msgs::msg::HandTouchSensorData &sensor_data) {
 59    std::cout << "=== " << hand_name << " Touch Sensor Data ===" << std::endl;
 60
 61    // Print palm touch data
 62    std::cout << "Palm Touch Data (36 elements): ";
 63    print_array(sensor_data.palm_touch_data);
 64
 65    // Print back of hand touch data
 66    std::cout << "Back of Hand Touch Data (36 elements): ";
 67    print_array(sensor_data.back_of_hand_touch_data);
 68
 69    // Print finger touch data
 70    std::cout << "Thumb Touch Data (16 elements): ";
 71    print_array(sensor_data.thumb_touch_data);
 72
 73    std::cout << "Index Finger Touch Data (16 elements): ";
 74    print_array(sensor_data.index_finger_touch_data);
 75
 76    std::cout << "Middle Finger Touch Data (16 elements): ";
 77    print_array(sensor_data.middle_finger_touch_data);
 78
 79    std::cout << "Ring Finger Touch Data (16 elements): ";
 80    print_array(sensor_data.ring_finger_touch_data);
 81
 82    std::cout << "Little Finger Touch Data (16 elements): ";
 83    print_array(sensor_data.little_finger_touch_data);
 84  }
 85
 86  /**
 87   * @brief Print array of 36 uint8_t elements
 88   * @param arr Array to print
 89   */
 90  void print_array(const std::array<uint8_t, 36> &arr) {
 91    std::cout << "[";
 92    for (size_t i = 0; i < arr.size(); ++i) {
 93      std::cout << std::setw(3) << static_cast<int>(arr[i]);
 94      if (i < arr.size() - 1)
 95        std::cout << " ";
 96    }
 97    std::cout << "]" << std::endl;
 98  }
 99
100  /**
101   * @brief Print array of 16 uint8_t elements
102   * @param arr Array to print
103   */
104  void print_array(const std::array<uint8_t, 16> &arr) {
105    std::cout << "[";
106    for (size_t i = 0; i < arr.size(); ++i) {
107      std::cout << std::setw(3) << static_cast<int>(arr[i]);
108      if (i < arr.size() - 1)
109        std::cout << " ";
110    }
111    std::cout << "]" << std::endl;
112  }
113
114  void publish_command() {
115    auto message = aimdk_msgs::msg::HandCommandArray();
116
117    // Set header
118    message.header.stamp = this->now();
119    message.header.frame_id = "hand_command";
120
121    // Set the hand type
122    message.left_hand_type.value =
123        aimdk_msgs::msg::HandType::NIMBLE_HANDS; // dexterous hands (1)
124    message.right_hand_type.value =
125        aimdk_msgs::msg::HandType::NIMBLE_HANDS; // dexterous hands (1)
126
127    // Joint names for readability, mirroring the /aima/hal/joint/hand/state
128    // feedback (URDF names). The driver routes by array index/order (see
129    // endeffector.md), not by these name strings.
130    const char *left_names[10] = {"L_thumb_roll_joint", "L_thumb_abad_joint",
131                                  "L_thumb_mcp_joint",  "L_index_abad_joint",
132                                  "L_index_pip_joint",  "L_middle_pip_joint",
133                                  "L_ring_abad_joint",  "L_ring_pip_joint",
134                                  "L_pinky_abad_joint", "L_pinky_pip_joint"};
135    const char *right_names[10] = {"R_thumb_roll_joint", "R_thumb_abad_joint",
136                                   "R_thumb_mcp_joint",  "R_index_abad_joint",
137                                   "R_index_pip_joint",  "R_middle_pip_joint",
138                                   "R_ring_abad_joint",  "R_ring_pip_joint",
139                                   "R_pinky_abad_joint", "R_pinky_pip_joint"};
140
141    // Create left hand command array
142    message.left_hands.resize(10);
143    for (int i = 0; i < 10; i++) {
144      message.left_hands[i].name = left_names[i];
145      message.left_hands[i].position = 0.0;
146      message.left_hands[i].velocity = 0.1;
147      message.left_hands[i].acceleration = 0.0;
148      message.left_hands[i].deceleration = 0.0;
149      message.left_hands[i].effort = 0.0;
150    }
151
152    // Create right hand command array
153    message.right_hands.resize(10);
154    for (int i = 0; i < 10; i++) {
155      message.right_hands[i].name = right_names[i];
156      message.right_hands[i].position = 0.0;
157      message.right_hands[i].velocity = 0.1;
158      message.right_hands[i].acceleration = 0.0;
159      message.right_hands[i].deceleration = 0.0;
160      message.right_hands[i].effort = 0.0;
161    }
162
163    // Publish the message
164    publisher_->publish(message);
165
166    RCLCPP_INFO(this->get_logger(), "Published hand command");
167  }
168
169  rclcpp::Publisher<aimdk_msgs::msg::HandCommandArray>::SharedPtr publisher_;
170  rclcpp::Subscription<aimdk_msgs::msg::HandStateArray>::SharedPtr
171      subscription_;
172  rclcpp::TimerBase::SharedPtr timer_;
173};
174
175int main(int argc, char *argv[]) {
176  rclcpp::init(argc, argv);
177  auto node = std::make_shared<HandStateSubscriber>();
178  rclcpp::spin(node);
179  rclcpp::shutdown();
180  return 0;
181}

The pressure sensors are located at the fingertips of all five fingers, as well as on the palm and the back of the hand. At least one motion command needs to be sent to the dexterous hand in order to receive the corresponding pressure data report.

6.2.8 Register Secondary Development Input Source

For versions after v0.7, an input source must be registered before controlling the MC. In this example, the /aimdk_5Fmsgs/srv/SetMcInputSource service is used to register the secondary development input source, so that the MC can recognize it. Only after registration can robot velocity control be performed.

  1#include "aimdk_msgs/srv/set_mc_input_source.hpp"
  2#include "aimdk_msgs/msg/common_request.hpp"
  3#include "aimdk_msgs/msg/common_response.hpp"
  4#include "aimdk_msgs/msg/common_state.hpp"
  5#include "aimdk_msgs/msg/common_task_response.hpp"
  6#include "aimdk_msgs/msg/mc_input_action.hpp"
  7#include "rclcpp/rclcpp.hpp"
  8#include <chrono>
  9#include <cinttypes>
 10#include <memory>
 11#include <signal.h>
 12
 13namespace {
 14const char *action_name(int32_t value) {
 15  using A = aimdk_msgs::msg::McInputAction;
 16  switch (value) {
 17  case A::INPUTACTION_ADD:
 18    return "ADD";
 19  case A::INPUTACTION_MODIFY:
 20    return "MODIFY";
 21  case A::INPUTACTION_DELETE:
 22    return "DELETE";
 23  case A::INPUTACTION_ENABLE:
 24    return "ENABLE";
 25  case A::INPUTACTION_DISABLE:
 26    return "DISABLE";
 27  default:
 28    return "UNKNOWN";
 29  }
 30}
 31} // namespace
 32
 33std::shared_ptr<rclcpp::Node> g_node = nullptr;
 34
 35void signal_handler(int signal) {
 36  if (g_node) {
 37    RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
 38                signal);
 39    g_node.reset();
 40  }
 41  rclcpp::shutdown();
 42  exit(signal);
 43}
 44
 45class McInputClient : public rclcpp::Node {
 46public:
 47  McInputClient() : Node("set_mc_input_source_client") {
 48    client_ = this->create_client<aimdk_msgs::srv::SetMcInputSource>(
 49        "/aimdk_5Fmsgs/srv/SetMcInputSource");
 50
 51    RCLCPP_INFO(this->get_logger(), "✅ SetMcInputSource client node created.");
 52
 53    // Wait for the service to become available
 54    while (!client_->wait_for_service(std::chrono::seconds(2))) {
 55      RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
 56    }
 57    RCLCPP_INFO(this->get_logger(),
 58                "🟢 Service available, ready to send request.");
 59  }
 60
 61  bool send_request() {
 62    try {
 63      auto request =
 64          std::make_shared<aimdk_msgs::srv::SetMcInputSource::Request>();
 65
 66      // Set request data
 67      request->action.value =
 68          aimdk_msgs::msg::McInputAction::INPUTACTION_ADD; // ADD (1001)
 69      request->input_source.name = "node.set_mc_input_source";
 70      request->input_source.priority = 40;
 71      request->input_source.timeout = 1000;
 72
 73      RCLCPP_INFO(this->get_logger(), "📨 Sending input source request: %s (%d)",
 74                  action_name(request->action.value), request->action.value);
 75
 76      auto timeout = std::chrono::milliseconds(250);
 77      for (int i = 0; i < 8; i++) {
 78        // Set header timestamp
 79        request->request.header.stamp = this->now(); // use Node::now()
 80        auto future = client_->async_send_request(request);
 81        auto retcode = rclcpp::spin_until_future_complete(
 82            this->shared_from_this(), future, timeout);
 83        if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
 84          // retry as remote peer is NOT handled well by ROS
 85          RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
 86          continue;
 87        }
 88        // future.done
 89        auto response = future.get();
 90        auto code = response->response.header.code;
 91        if (code == 0) {
 92          RCLCPP_INFO(this->get_logger(),
 93                      "✅ Input source set successfully. task_id=%" PRIu64,
 94                      response->response.task_id);
 95          return true;
 96        } else {
 97          RCLCPP_ERROR(
 98              this->get_logger(),
 99              "❌ Input source set failed. ret_code=%" PRId64
100              ", task_id=%" PRIu64 " "
101              "(duplicated ADD? or MODIFY/ENABLE/DISABLE for unknown source?)",
102              code, response->response.task_id);
103          return false;
104        }
105      }
106      RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
107      return false;
108    } catch (const std::exception &e) {
109      RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
110      return false;
111    }
112  }
113
114private:
115  rclcpp::Client<aimdk_msgs::srv::SetMcInputSource>::SharedPtr client_;
116};
117
118int main(int argc, char *argv[]) {
119  try {
120    rclcpp::init(argc, argv);
121    signal(SIGINT, signal_handler);
122    signal(SIGTERM, signal_handler);
123
124    g_node = std::make_shared<McInputClient>();
125    auto client = std::dynamic_pointer_cast<McInputClient>(g_node);
126
127    if (client) {
128      client->send_request();
129    }
130
131    g_node.reset();
132    rclcpp::shutdown();
133
134    return 0;
135  } catch (const std::exception &e) {
136    RCLCPP_ERROR(rclcpp::get_logger("main"),
137                 "Program terminated with exception: %s", e.what());
138    return 1;
139  }
140}

6.2.9 Get Current Input Source

This example uses the GetCurrentInputSource service, which is used to obtain information about the currently registered input source, including the input source name, priority, and timeout settings.

  1#include "aimdk_msgs/srv/get_current_input_source.hpp"
  2#include "aimdk_msgs/msg/common_request.hpp"
  3#include "aimdk_msgs/msg/response_header.hpp"
  4#include "rclcpp/rclcpp.hpp"
  5#include <chrono>
  6#include <cinttypes>
  7#include <memory>
  8#include <signal.h>
  9
 10// Global node object
 11std::shared_ptr<rclcpp::Node> g_node = nullptr;
 12
 13// Signal handler
 14void signal_handler(int signal) {
 15  if (g_node) {
 16    RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
 17                signal);
 18    g_node.reset();
 19  }
 20  rclcpp::shutdown();
 21  exit(signal);
 22}
 23
 24// Client Class
 25class GetCurrentInputSourceClient : public rclcpp::Node {
 26public:
 27  GetCurrentInputSourceClient() : Node("get_current_input_source_client") {
 28
 29    client_ = this->create_client<aimdk_msgs::srv::GetCurrentInputSource>(
 30        "/aimdk_5Fmsgs/srv/GetCurrentInputSource");
 31
 32    RCLCPP_INFO(this->get_logger(),
 33                "✅ GetCurrentInputSource client node created.");
 34
 35    // Wait for the service to become available
 36    while (!client_->wait_for_service(std::chrono::seconds(2))) {
 37      RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
 38    }
 39    RCLCPP_INFO(this->get_logger(),
 40                "🟢 Service available, ready to send request.");
 41  }
 42
 43  void send_request() {
 44    try {
 45      auto request =
 46          std::make_shared<aimdk_msgs::srv::GetCurrentInputSource::Request>();
 47      request->request = aimdk_msgs::msg::CommonRequest();
 48
 49      RCLCPP_INFO(this->get_logger(),
 50                  "📨 Sending request to get current input source");
 51
 52      auto timeout = std::chrono::milliseconds(250);
 53
 54      for (int i = 0; i < 8; i++) {
 55        request->request.header.stamp = this->now();
 56        auto future = client_->async_send_request(request);
 57        auto retcode = rclcpp::spin_until_future_complete(
 58            this->shared_from_this(), future, timeout);
 59        if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
 60          // retry as remote peer is NOT handled well by ROS
 61          RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
 62          continue;
 63        }
 64        // future.done
 65        auto response = future.get();
 66        if (response->response.header.code == 0) {
 67          RCLCPP_INFO(this->get_logger(),
 68                      "✅ Current input source get successfully:");
 69          RCLCPP_INFO(this->get_logger(), "Name: %s",
 70                      response->input_source.name.c_str());
 71          RCLCPP_INFO(this->get_logger(), "Priority: %d",
 72                      response->input_source.priority);
 73          RCLCPP_INFO(this->get_logger(), "Timeout: %d",
 74                      response->input_source.timeout);
 75        } else {
 76          RCLCPP_WARN(
 77              this->get_logger(),
 78              "❌ Current input source get failed, return code: %" PRId64,
 79              response->response.header.code);
 80        }
 81        return;
 82      }
 83      RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
 84    } catch (const std::exception &e) {
 85      RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
 86    }
 87  }
 88
 89private:
 90  rclcpp::Client<aimdk_msgs::srv::GetCurrentInputSource>::SharedPtr client_;
 91};
 92
 93int main(int argc, char *argv[]) {
 94  try {
 95    rclcpp::init(argc, argv);
 96
 97    signal(SIGINT, signal_handler);
 98    signal(SIGTERM, signal_handler);
 99
100    g_node = std::make_shared<GetCurrentInputSourceClient>();
101    auto client =
102        std::dynamic_pointer_cast<GetCurrentInputSourceClient>(g_node);
103
104    if (client) {
105      client->send_request();
106    }
107
108    g_node.reset();
109    rclcpp::shutdown();
110    return 0;
111  } catch (const std::exception &e) {
112    RCLCPP_ERROR(rclcpp::get_logger("main"),
113                 "Program exited with exception: %s", e.what());
114    return 1;
115  }
116}

Usage Instructions

# Get current input source information
ros2 run examples get_current_input_source

Output Example

[INFO] [1779174540.534817548] [get_current_input_source_client]: ✅ GetCurrentInputSource client node created.
[INFO] [1779174541.371095227] [get_current_input_source_client]: 🟢 Service available, ready to send request.
[INFO] [1779174541.371216694] [get_current_input_source_client]: 📨 Sending request to get current input source
[INFO] [1779174541.382602338] [get_current_input_source_client]: ✅ Current input source get successfully:
[INFO] [1779174541.382692894] [get_current_input_source_client]: Name: node
[INFO] [1779174541.382713533] [get_current_input_source_client]: Priority: 40
[INFO] [1779174541.382726205] [get_current_input_source_client]: Timeout: 1000

Notes

  • Ensure the GetCurrentInputSource service is running properly

  • Valid information can only be obtained after an input source has been registered

  • A status code of 0 indicates a successful query

6.2.10 Robot Locomotion Control

This example uses mc_locomotion_velocity. The following example controls the robot’s walking by publishing to the /aima/mc/locomotion/velocity topic. For versions after v0.7, an input source must be registered before enabling velocity control (this example already includes input source registration). Refer to the code for detailed registration steps.

Start the node after switching to Stable Standing Mode:

  1#include "aimdk_msgs/msg/mc_locomotion_velocity.hpp"
  2#include "aimdk_msgs/msg/common_request.hpp"
  3#include "aimdk_msgs/msg/common_response.hpp"
  4#include "aimdk_msgs/msg/common_state.hpp"
  5#include "aimdk_msgs/msg/common_task_response.hpp"
  6#include "aimdk_msgs/msg/mc_input_action.hpp"
  7#include "aimdk_msgs/msg/message_header.hpp"
  8#include "aimdk_msgs/srv/set_mc_input_source.hpp"
  9
 10#include "rclcpp/rclcpp.hpp"
 11#include <chrono>
 12#include <cmath>
 13#include <iostream>
 14#include <memory>
 15#include <signal.h>
 16#include <thread>
 17
 18class DirectVelocityControl : public rclcpp::Node {
 19public:
 20  DirectVelocityControl() : Node("direct_velocity_control") {
 21    // Create publisher
 22    publisher_ = this->create_publisher<aimdk_msgs::msg::McLocomotionVelocity>(
 23        "/aima/mc/locomotion/velocity", 10);
 24    // Create service client
 25    client_ = this->create_client<aimdk_msgs::srv::SetMcInputSource>(
 26        "/aimdk_5Fmsgs/srv/SetMcInputSource");
 27
 28    // Maximum speed limits
 29    max_forward_speed_ = 1.0; // m/s
 30    max_lateral_speed_ = 1.0; // m/s
 31    max_angular_speed_ = 1.0; // rad/s
 32    // Minimum speed limits (0 is also OK)
 33    min_forward_speed_ = 0.2; // m/s
 34    min_lateral_speed_ = 0.2; // m/s
 35    min_angular_speed_ = 0.1; // rad/s
 36
 37    RCLCPP_INFO(this->get_logger(), "Direct velocity control node started.");
 38  }
 39
 40  void start_publish() {
 41    if (timer_ != nullptr) {
 42      return;
 43    }
 44    // Set timer to periodically publish velocity messages (50Hz)
 45    timer_ = this->create_wall_timer(
 46        std::chrono::milliseconds(20),
 47        std::bind(&DirectVelocityControl::publish_velocity, this));
 48  }
 49
 50  bool register_input_source() {
 51    const std::chrono::seconds timeout(8);
 52    auto start_time = std::chrono::steady_clock::now();
 53    while (!client_->wait_for_service(std::chrono::seconds(2))) {
 54      if (std::chrono::steady_clock::now() - start_time > timeout) {
 55        RCLCPP_ERROR(this->get_logger(), "Waiting for service timed out");
 56        return false;
 57      }
 58      RCLCPP_INFO(this->get_logger(), "Waiting for input source service...");
 59    }
 60
 61    // Try ADD first; if it fails (likely already registered), fall back to
 62    // ENABLE
 63    if (call_input_source( // ADD (1001)
 64            aimdk_msgs::msg::McInputAction::INPUTACTION_ADD)) {
 65      RCLCPP_INFO(this->get_logger(), "✅ Input source registered (ADD).");
 66      return true;
 67    }
 68    RCLCPP_INFO(this->get_logger(),
 69                "ADD failed (likely already registered), trying ENABLE...");
 70    if (call_input_source( // ENABLE (2001)
 71            aimdk_msgs::msg::McInputAction::INPUTACTION_ENABLE)) {
 72      RCLCPP_INFO(this->get_logger(), "✅ Input source enabled.");
 73      return true;
 74    }
 75    RCLCPP_ERROR(this->get_logger(),
 76                 "❌ Failed to register input source via ADD or ENABLE.");
 77    return false;
 78  }
 79
 80  bool call_input_source(int32_t action_value) {
 81    auto request =
 82        std::make_shared<aimdk_msgs::srv::SetMcInputSource::Request>();
 83    request->action.value = action_value;
 84    request->input_source.name = "node.locomotion_velocity";
 85    request->input_source.priority = 40;
 86    request->input_source.timeout = 1000;
 87
 88    auto timeout2 = std::chrono::milliseconds(250);
 89
 90    for (int i = 0; i < 8; i++) {
 91      request->request.header.stamp = this->now();
 92      auto future = client_->async_send_request(request);
 93      auto retcode = rclcpp::spin_until_future_complete(
 94          this->shared_from_this(), future, timeout2);
 95      if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
 96        // retry as remote peer is NOT handled well by ROS
 97        RCLCPP_INFO(
 98            this->get_logger(), "trying input source action %s (%d)... [%d]",
 99            action_value == aimdk_msgs::msg::McInputAction::INPUTACTION_ADD
100                ? "ADD"
101                : "ENABLE",
102            action_value, i);
103        continue;
104      }
105      auto response = future.get();
106      return response->response.header.code == 0;
107    }
108    return false;
109  }
110
111  void publish_velocity() {
112    auto msg = std::make_unique<aimdk_msgs::msg::McLocomotionVelocity>();
113    msg->header = aimdk_msgs::msg::MessageHeader();
114    msg->header.stamp = this->now();
115    msg->source = "node.locomotion_velocity"; // Set message source
116    msg->forward_velocity = forward_velocity_;
117    msg->lateral_velocity = lateral_velocity_;
118    msg->angular_velocity = angular_velocity_;
119
120    publisher_->publish(std::move(msg));
121    RCLCPP_INFO(this->get_logger(),
122                "Published velocity: Forward %.2f m/s, Lateral %.2f m/s, "
123                "Angular %.2f rad/s",
124                forward_velocity_, lateral_velocity_, angular_velocity_);
125  }
126
127  void clear_velocity() {
128    forward_velocity_ = 0.0;
129    lateral_velocity_ = 0.0;
130    angular_velocity_ = 0.0;
131  }
132
133  bool set_forward(double forward) {
134    // check value range, mc has thresholds to start movement
135    if (std::abs(forward) < 0.005) {
136      forward_velocity_ = 0.0;
137      return true;
138    } else if ((std::abs(forward) > max_forward_speed_) ||
139               (std::abs(forward) < min_forward_speed_)) {
140      RCLCPP_ERROR(this->get_logger(), "input value out of range, exiting");
141      return false;
142    } else {
143      forward_velocity_ = forward;
144      return true;
145    }
146  }
147
148  bool set_lateral(double lateral) {
149    // check value range, mc has thresholds to start movement
150    if (std::abs(lateral) < 0.005) {
151      lateral_velocity_ = 0.0;
152      return true;
153    } else if ((std::abs(lateral) > max_lateral_speed_) ||
154               (std::abs(lateral) < min_lateral_speed_)) {
155      RCLCPP_ERROR(this->get_logger(), "input value out of range, exiting");
156      return false;
157    } else {
158      lateral_velocity_ = lateral;
159      return true;
160    }
161  }
162
163  bool set_angular(double angular) {
164    // check value range, mc has thresholds to start movement
165    if (std::abs(angular) < 0.005) {
166      angular_velocity_ = 0.0;
167      return true;
168    } else if ((std::abs(angular) > max_angular_speed_) ||
169               (std::abs(angular) < min_angular_speed_)) {
170      RCLCPP_ERROR(this->get_logger(), "input value out of range, exiting");
171      return false;
172    } else {
173      angular_velocity_ = angular;
174      return true;
175    }
176  }
177
178private:
179  rclcpp::Publisher<aimdk_msgs::msg::McLocomotionVelocity>::SharedPtr
180      publisher_;
181  rclcpp::Client<aimdk_msgs::srv::SetMcInputSource>::SharedPtr client_;
182  rclcpp::TimerBase::SharedPtr timer_;
183
184  double forward_velocity_;
185  double lateral_velocity_;
186  double angular_velocity_;
187
188  double max_forward_speed_;
189  double max_lateral_speed_;
190  double max_angular_speed_;
191
192  double min_forward_speed_;
193  double min_lateral_speed_;
194  double min_angular_speed_;
195};
196
197// Signal Processing
198std::shared_ptr<DirectVelocityControl> global_node = nullptr;
199void signal_handler(int sig) {
200  if (global_node) {
201    global_node->clear_velocity();
202    RCLCPP_INFO(global_node->get_logger(),
203                "Received signal %d: clearing velocity and shutting down", sig);
204  }
205  rclcpp::shutdown();
206  exit(sig);
207}
208
209int main(int argc, char *argv[]) {
210  rclcpp::init(argc, argv);
211  signal(SIGINT, signal_handler);
212  signal(SIGTERM, signal_handler);
213
214  global_node = std::make_shared<DirectVelocityControl>();
215  auto node = global_node;
216
217  if (!node->register_input_source()) {
218    RCLCPP_ERROR(node->get_logger(),
219                 "❌ Input source registration failed, exiting");
220    global_node.reset();
221    rclcpp::shutdown();
222    return 1;
223  }
224
225  // get and check control values
226  // notice that mc has thresholds to start movement
227  double forward, lateral, angular;
228  std::cout << "Enter forward speed 0 or ±(0.2 ~ 1.0) m/s: ";
229  std::cin >> forward;
230  if (!node->set_forward(forward)) {
231    return 2;
232  }
233  std::cout << "Enter lateral speed 0 or ±(0.2 ~ 1.0) m/s: ";
234  std::cin >> lateral;
235  if (!node->set_lateral(lateral)) {
236    return 2;
237  }
238  std::cout << "Enter angular speed 0 or ±(0.1 ~ 1.0) rad/s: ";
239  std::cin >> angular;
240  if (!node->set_angular(angular)) {
241    return 2;
242  }
243
244  RCLCPP_INFO(node->get_logger(), "Setting velocity; moving for 5 seconds");
245
246  node->start_publish();
247
248  auto start_time = node->now();
249  while ((node->now() - start_time).seconds() < 5.0) {
250    rclcpp::spin_some(node);
251    std::this_thread::sleep_for(std::chrono::milliseconds(1));
252  }
253
254  node->clear_velocity();
255  RCLCPP_INFO(node->get_logger(), "5 seconds elapsed; robot stopped");
256
257  rclcpp::spin(node);
258  rclcpp::shutdown();
259  return 0;
260}

6.2.11 Joint Motor Control

This example demonstrates how to use ROS2 and the Ruckig library to control the robot’s joint movements.

Attention

Warning ⚠️ : Before running this example, you must stop the native motion control module using aima em stop-app mc on the robot’s Motion Control Computing Unit (PC1) to obtain control authority. Ensure robot safety at all times.

! This example directly controls the underlying motors (i.e. the HAL layer). Before running the program, please verify that the joint safety limits in the code match the actual robot model, and ensure safety!

Robot Joint Control Example

This example demonstrates how to control the robot’s joints using ROS2 and the Ruckig library. It includes the following features:

  1. Robot 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 timer period (currently 3s) and the ruckig dt (currently 0.002s)

  1#include <aimdk_msgs/msg/joint_command_array.hpp>
  2#include <aimdk_msgs/msg/joint_state_array.hpp>
  3#include <atomic>
  4#include <cmath>
  5#include <cstdlib>
  6#include <map>
  7#include <memory>
  8#include <rclcpp/rclcpp.hpp>
  9#include <ruckig/ruckig.hpp>
 10#include <signal.h>
 11#include <string>
 12#include <thread>
 13#include <vector>
 14
 15/**
 16 * @brief Global variables and signal handling
 17 */
 18// Global variables to control program state
 19std::atomic<bool> g_running(true);
 20std::atomic<bool> g_emergency_stop(false);
 21
 22// Signal handler function
 23void signal_handler(int) {
 24  g_running = false;
 25  RCLCPP_INFO(rclcpp::get_logger("main"),
 26              "Received termination signal, shutting down...");
 27}
 28
 29/**
 30 * @brief Robot model definition
 31 */
 32enum class JointArea {
 33  HEAD,  // Head joints
 34  ARM,   // Arm joints
 35  WAIST, // Waist joints
 36  LEG,   // Leg joints
 37};
 38
 39/**
 40 * @brief Joint information structure
 41 */
 42struct JointInfo {
 43  std::string name;   // Joint name
 44  double lower_limit; // Joint angle lower limit
 45  double upper_limit; // Joint angle upper limit
 46  double kp;          // Position control gain
 47  double kd;          // Velocity control gain
 48};
 49
 50/**
 51 * @brief Robot model configuration
 52 * Contains parameters for all joints, enabling or disabling specific joints as
 53 * needed
 54 */
 55std::map<JointArea, std::vector<JointInfo>> robot_model = {
 56    {JointArea::LEG,
 57     {
 58         // Left leg joint configuration
 59         {"left_hip_pitch_joint", -2.704, 2.556, 40.0, 4.0},
 60         {"left_hip_roll_joint", -0.235, 2.906, 40.0, 4.0},
 61         {"left_hip_yaw_joint", -1.684, 3.430, 30.0, 3.0},
 62         {"left_knee_joint", 0.0000, 2.4073, 80.0, 8.0},
 63         {"left_ankle_pitch_joint", -0.803, 0.453, 40.0, 4.0},
 64         {"left_ankle_roll_joint", -0.2625, 0.2625, 20.0, 2.0},
 65         // Right leg joint configuration
 66         {"right_hip_pitch_joint", -2.704, 2.556, 40.0, 4.0},
 67         {"right_hip_roll_joint", -2.906, 0.235, 40.0, 4.0},
 68         {"right_hip_yaw_joint", -3.430, 1.684, 30.0, 3.0},
 69         {"right_knee_joint", 0.0000, 2.4073, 80.0, 8.0},
 70         {"right_ankle_pitch_joint", -0.803, 0.453, 40.0, 4.0},
 71         {"right_ankle_roll_joint", -0.2625, 0.2625, 20.0, 2.0},
 72     }},
 73
 74    {JointArea::WAIST,
 75     {
 76         // Waist joint configuration
 77         {"waist_yaw_joint", -3.43, 2.382, 20.0, 4.0},
 78         {"waist_pitch_joint", -0.314, 0.314, 20.0, 4.0},
 79         {"waist_roll_joint", -0.488, 0.488, 20.0, 4.0},
 80     }},
 81    {JointArea::ARM,
 82     {
 83         // Left arm joint configuration
 84         {"left_shoulder_pitch_joint", -3.08, 2.04, 20.0, 2.0},
 85         {"left_shoulder_roll_joint", -0.061, 2.993, 20.0, 2.0},
 86         {"left_shoulder_yaw_joint", -2.556, 2.556, 20.0, 2.0},
 87         {"left_elbow_joint", -2.3556, 0.0, 20.0, 2.0},
 88         {"left_wrist_yaw_joint", -2.556, 2.556, 20.0, 2.0},
 89         {"left_wrist_pitch_joint", -0.558, 0.558, 20.0, 2.0},
 90         {"left_wrist_roll_joint", -1.571, 0.724, 20.0, 2.0},
 91         // Right arm joint configuration
 92         {"right_shoulder_pitch_joint", -3.08, 2.04, 20.0, 2.0},
 93         {"right_shoulder_roll_joint", -2.993, 0.061, 20.0, 2.0},
 94         {"right_shoulder_yaw_joint", -2.556, 2.556, 20.0, 2.0},
 95         {"right_elbow_joint", -2.3556, 0.0000, 20.0, 2.0},
 96         {"right_wrist_yaw_joint", -2.556, 2.556, 20.0, 2.0},
 97         {"right_wrist_pitch_joint", -0.558, 0.558, 20.0, 2.0},
 98         {"right_wrist_roll_joint", -0.724, 1.571, 20.0, 2.0},
 99     }},
100    {JointArea::HEAD,
101     {
102         // Head joint configuration
103         {"head_yaw_joint", -0.366, 0.366, 20.0, 2.0},
104         {"head_pitch_joint", -0.3838, 0.3838, 20.0, 2.0},
105     }},
106};
107
108/**
109 * @brief Joint controller node class
110 * @tparam DOFs Degrees of freedom
111 * @tparam Area Joint area
112 */
113template <int DOFs, JointArea Area>
114class JointControllerNode : public rclcpp::Node {
115public:
116  /**
117   * @brief Constructor
118   * @param node_name Node name
119   * @param sub_topic Subscription topic name
120   * @param pub_topic Publication topic name
121   * @param qos QoS configuration
122   */
123  JointControllerNode(std::string node_name, std::string sub_topic,
124                      std::string pub_topic,
125                      rclcpp::QoS qos = rclcpp::SensorDataQoS())
126      : Node(node_name), ruckig(0.002) {
127    joint_info_ = robot_model[Area];
128    if (joint_info_.size() != DOFs) {
129      RCLCPP_ERROR(this->get_logger(), "Joint count mismatch.");
130      exit(1);
131    }
132
133    // Set motion constraints for Ruckig trajectory planner
134    for (int i = 0; i < DOFs; ++i) {
135      input.max_velocity[i] = 1.0;     // Max velocity limit
136      input.max_acceleration[i] = 1.0; // Max acceleration limit
137      input.max_jerk[i] = 25.0; // Max jerk (change of acceleration) limit
138    }
139
140    // Create joint state subscriber
141    sub_ = this->create_subscription<aimdk_msgs::msg::JointStateArray>(
142        sub_topic, qos,
143        std::bind(&JointControllerNode::JointStateCallback, this,
144                  std::placeholders::_1));
145
146    // Create joint command publisher
147    pub_ = this->create_publisher<aimdk_msgs::msg::JointCommandArray>(pub_topic,
148                                                                      qos);
149  }
150
151private:
152  // Ruckig trajectory planner variables
153  ruckig::Ruckig<DOFs> ruckig;          // Trajectory planner instance
154  ruckig::InputParameter<DOFs> input;   // Input parameters
155  ruckig::OutputParameter<DOFs> output; // Output parameters
156  bool ruckig_initialized_ = false;   // Trajectory planner initialization flag
157  std::vector<JointInfo> joint_info_; // Joint information list
158
159  // ROS communication variables
160  rclcpp::Subscription<aimdk_msgs::msg::JointStateArray>::SharedPtr
161      sub_; // State subscriber
162  rclcpp::Publisher<aimdk_msgs::msg::JointCommandArray>::SharedPtr
163      pub_; // Command publisher
164
165  /**
166   * @brief Joint state callback function
167   * @param msg Joint state message
168   */
169  void
170  JointStateCallback(const aimdk_msgs::msg::JointStateArray::SharedPtr msg) {
171    // Initialize trajectory planner on first state reception
172    if (!ruckig_initialized_) {
173      for (int i = 0; i < DOFs; ++i) {
174        input.current_position[i] = msg->joints[i].position;
175        input.current_velocity[i] = msg->joints[i].velocity;
176        input.current_acceleration[i] = 0.0;
177      }
178      ruckig_initialized_ = true;
179      RCLCPP_INFO(this->get_logger(),
180                  "Ruckig trajectory planner initialization complete");
181    }
182  }
183
184public:
185  /**
186   * @brief Set target joint position
187   * @param joint_name Joint name
188   * @param target_position Target position
189   * @return Whether the target position was successfully set
190   */
191  bool SetTargetPosition(std::string joint_name, double target_position) {
192    if (!ruckig_initialized_) {
193      RCLCPP_WARN(this->get_logger(),
194                  "Ruckig trajectory planner not initialized");
195      return false;
196    }
197
198    // Find target joint and set its position
199    int target_joint = -1;
200    for (int i = 0; i < DOFs; ++i) {
201      if (joint_info_[i].name == joint_name) {
202        // Check if target position is within limits
203        if (target_position < joint_info_[i].lower_limit ||
204            target_position > joint_info_[i].upper_limit) {
205          RCLCPP_ERROR(
206              this->get_logger(),
207              "Target position %.3f exceeds limit for joint %s [%.3f, %.3f]",
208              target_position, joint_name.c_str(), joint_info_[i].lower_limit,
209              joint_info_[i].upper_limit);
210          return false;
211        }
212        input.target_position[i] = target_position;
213        input.target_velocity[i] = 0.0;
214        input.target_acceleration[i] = 0.0;
215        target_joint = i;
216      } else {
217        input.target_position[i] = input.current_position[i];
218        input.target_velocity[i] = 0.0;
219        input.target_acceleration[i] = 0.0;
220      }
221    }
222
223    if (target_joint == -1) {
224      RCLCPP_ERROR(this->get_logger(), "Joint %s not found",
225                   joint_name.c_str());
226      return false;
227    }
228
229    // Perform trajectory planning and send command using Ruckig
230    const double tolerance = 1e-6;
231    while (g_running && rclcpp::ok() && !g_emergency_stop) {
232      auto result = ruckig.update(input, output);
233      if (result != ruckig::Result::Working &&
234          result != ruckig::Result::Finished) {
235        RCLCPP_WARN(this->get_logger(), "Trajectory planning failed");
236        break;
237      }
238
239      // Update current state
240      for (int i = 0; i < DOFs; ++i) {
241        input.current_position[i] = output.new_position[i];
242        input.current_velocity[i] = output.new_velocity[i];
243        input.current_acceleration[i] = output.new_acceleration[i];
244      }
245
246      // Check if target position is reached
247      if (std::abs(output.new_position[target_joint] - target_position) <
248          tolerance) {
249        RCLCPP_INFO(this->get_logger(), "Joint %s reached target position",
250                    joint_name.c_str());
251        break;
252      }
253
254      // Create and send joint command
255      aimdk_msgs::msg::JointCommandArray cmd;
256      cmd.joints.resize(DOFs);
257      for (int i = 0; i < DOFs; ++i) {
258        auto &joint = joint_info_[i];
259        cmd.joints[i].name = joint.name;
260        cmd.joints[i].position = output.new_position[i];
261        cmd.joints[i].velocity = output.new_velocity[i];
262        cmd.joints[i].stiffness = joint.kp;
263        cmd.joints[i].damping = joint.kd;
264      }
265      pub_->publish(cmd);
266
267      // Short delay to avoid excessive CPU usage
268      std::this_thread::sleep_for(std::chrono::milliseconds(2));
269    }
270
271    return true;
272  }
273
274  /**
275   * @brief Safely stop all joints
276   */
277  void safe_stop() {
278    if (!ruckig_initialized_) {
279      RCLCPP_WARN(this->get_logger(), "Ruckig trajectory planner not "
280                                      "initialized, cannot perform safe stop");
281      return;
282    }
283
284    RCLCPP_INFO(this->get_logger(), "Performing safe stop...");
285
286    // Set all joint target positions to current positions
287    for (int i = 0; i < DOFs; ++i) {
288      input.target_position[i] = input.current_position[i];
289      input.target_velocity[i] = 0.0;
290      input.target_acceleration[i] = 0.0;
291    }
292
293    // Send final command to ensure joints stop
294    aimdk_msgs::msg::JointCommandArray cmd;
295    cmd.joints.resize(DOFs);
296    for (int i = 0; i < DOFs; ++i) {
297      auto &joint = joint_info_[i];
298      cmd.joints[i].name = joint.name;
299      cmd.joints[i].position = input.current_position[i];
300      cmd.joints[i].velocity = 0.0;
301      cmd.joints[i].stiffness = joint.kp;
302      cmd.joints[i].damping = joint.kd;
303    }
304    pub_->publish(cmd);
305
306    RCLCPP_INFO(this->get_logger(), "Safe stop complete");
307  }
308
309  /**
310   * @brief Emergency stop for all joints
311   */
312  void emergency_stop() {
313    g_emergency_stop = true;
314    safe_stop();
315    RCLCPP_ERROR(this->get_logger(), "Emergency stop triggered");
316  }
317};
318
319/**
320 * @brief Main function
321 */
322int main(int argc, char *argv[]) {
323  rclcpp::init(argc, argv);
324
325  // Set up signal handling
326  signal(SIGINT, signal_handler);
327  signal(SIGTERM, signal_handler);
328
329  try {
330    // Create leg controller node
331    auto leg_node = std::make_shared<JointControllerNode<12, JointArea::LEG>>(
332        "leg_node", "/aima/hal/joint/leg/state", "/aima/hal/joint/leg/command");
333
334    // Create timer node
335    rclcpp::Node::SharedPtr timer_node =
336        rclcpp::Node::make_shared("timer_node");
337    double position = 0.8;
338
339    // Create timer callback function
340    auto timer = timer_node->create_wall_timer(std::chrono::seconds(3), [&]() {
341      if (!g_running || g_emergency_stop)
342        return; // If the program is shutting down or emergency stopped, do not
343                // execute new actions
344      position = -position;
345      position = 1.3 + position;
346      if (!leg_node->SetTargetPosition("left_knee_joint", position)) {
347        RCLCPP_ERROR(rclcpp::get_logger("main"),
348                     "Failed to set target position");
349      }
350    });
351
352    // Create executor
353    rclcpp::executors::MultiThreadedExecutor executor;
354    executor.add_node(leg_node);
355    executor.add_node(timer_node);
356
357    // Main loop
358    while (g_running && rclcpp::ok() && !g_emergency_stop) {
359      executor.spin_once(std::chrono::milliseconds(100));
360    }
361
362    // Safely stop all joints
363    RCLCPP_INFO(rclcpp::get_logger("main"), "Safely stopping all joints...");
364    leg_node->safe_stop();
365
366    // Wait a short time to ensure command transmission is complete
367    std::this_thread::sleep_for(std::chrono::milliseconds(100));
368
369    // Clean up resources
370    RCLCPP_INFO(rclcpp::get_logger("main"), "Cleaning up resources...");
371    leg_node.reset();
372    timer_node.reset();
373
374  } catch (const std::exception &e) {
375    RCLCPP_ERROR(rclcpp::get_logger("main"), "Exception occurred: %s",
376                 e.what());
377    g_emergency_stop = true;
378  } catch (...) {
379    RCLCPP_ERROR(rclcpp::get_logger("main"), "Unknown exception occurred");
380    g_emergency_stop = true;
381  }
382
383  RCLCPP_INFO(rclcpp::get_logger("main"), "Program exited safely");
384  rclcpp::shutdown();
385  return 0;
386}

6.2.12 Keyboard Robot Control

This example enables controlling the robot’s forward, backward, and turning movements using PC keyboard input.

Use W A S D to control the walking direction, increase/decrease linear velocity (±0.2 m/s), use Q / E to increase/decrease angular velocity (±0.1 rad/s), ESC exits the program and releases terminal resources, and Space immediately resets the velocity to zero to perform an emergency stop.

Caution

Note: Before running this example, use the controller to switch the robot to Stable Standing Mode. (Standing Preparation Mode (Position-Control Standing Mode) / Locomotion Mode, press R2 + X; for other modes, refer to the mode routing diagram). Then, in the robot’s terminal, run aima em stop-app rc to disable the remote controller and prevent channel occupation.

Before enabling keyboard control, an input source must be registered (already implemented in this example).
The curse module needs to be installed before running:

  sudo apt install libncurses-dev
  1#include "aimdk_msgs/msg/common_request.hpp"
  2#include "aimdk_msgs/msg/common_response.hpp"
  3#include "aimdk_msgs/msg/common_state.hpp"
  4#include "aimdk_msgs/msg/common_task_response.hpp"
  5#include "aimdk_msgs/msg/mc_input_action.hpp"
  6#include "aimdk_msgs/msg/mc_locomotion_velocity.hpp"
  7#include "aimdk_msgs/msg/message_header.hpp"
  8#include "aimdk_msgs/srv/set_mc_input_source.hpp"
  9
 10#include <algorithm>
 11#include <chrono>
 12#include <curses.h>
 13#include <rclcpp/rclcpp.hpp>
 14
 15using aimdk_msgs::msg::McLocomotionVelocity;
 16using std::placeholders::_1;
 17
 18class KeyboardVelocityController : public rclcpp::Node {
 19public:
 20  KeyboardVelocityController()
 21      : Node("keyboard_velocity_controller"), forward_velocity_(0.0),
 22        lateral_velocity_(0.0), angular_velocity_(0.0), step_(0.2),
 23        angular_step_(0.1) {
 24    pub_ = this->create_publisher<McLocomotionVelocity>(
 25        "/aima/mc/locomotion/velocity", 10);
 26    client_ = this->create_client<aimdk_msgs::srv::SetMcInputSource>(
 27        "/aimdk_5Fmsgs/srv/SetMcInputSource");
 28    // Register input source
 29    if (!register_input_source()) {
 30      RCLCPP_ERROR(this->get_logger(),
 31                   "❌ Input source registration failed, exiting");
 32      throw std::runtime_error("Input source registration failed");
 33    }
 34    // Initialize ncurses
 35    initscr();
 36    cbreak();
 37    noecho();
 38    keypad(stdscr, TRUE);
 39    nodelay(stdscr, TRUE);
 40
 41    timer_ = this->create_wall_timer(
 42        std::chrono::milliseconds(50),
 43        std::bind(&KeyboardVelocityController::checkKeyAndPublish, this));
 44
 45    RCLCPP_INFO(this->get_logger(),
 46                "Control started: W/S Forward/Backward | A/D Strafe Left/Right "
 47                "| Q/E Turn Left/Right | Space Stop | ESC Exit");
 48  }
 49
 50  ~KeyboardVelocityController() {
 51    endwin(); // Restore terminal
 52  }
 53
 54private:
 55  rclcpp::Publisher<McLocomotionVelocity>::SharedPtr pub_;
 56  rclcpp::Client<aimdk_msgs::srv::SetMcInputSource>::SharedPtr client_;
 57  rclcpp::TimerBase::SharedPtr timer_;
 58
 59  float forward_velocity_, lateral_velocity_, angular_velocity_;
 60  const float step_, angular_step_;
 61
 62  bool register_input_source() {
 63    const std::chrono::seconds srv_timeout(8);
 64    auto start_time = std::chrono::steady_clock::now();
 65    while (!client_->wait_for_service(std::chrono::seconds(2))) {
 66      if (std::chrono::steady_clock::now() - start_time > srv_timeout) {
 67        RCLCPP_ERROR(this->get_logger(), "Waiting for service timed out");
 68        return false;
 69      }
 70      RCLCPP_INFO(this->get_logger(), "Waiting for input source service...");
 71    }
 72
 73    // Try ADD first; if it fails (likely already registered), fall back to
 74    // ENABLE
 75    if (call_input_source( // ADD (1001)
 76            aimdk_msgs::msg::McInputAction::INPUTACTION_ADD)) {
 77      RCLCPP_INFO(this->get_logger(), "✅ Input source registered (ADD).");
 78      return true;
 79    }
 80    RCLCPP_INFO(this->get_logger(),
 81                "ADD failed (likely already registered), trying ENABLE...");
 82    if (call_input_source( // ENABLE (2001)
 83            aimdk_msgs::msg::McInputAction::INPUTACTION_ENABLE)) {
 84      RCLCPP_INFO(this->get_logger(), "✅ Input source enabled.");
 85      return true;
 86    }
 87    RCLCPP_ERROR(this->get_logger(),
 88                 "❌ Failed to register input source via ADD or ENABLE.");
 89    return false;
 90  }
 91
 92  bool call_input_source(int32_t action_value) {
 93    auto request =
 94        std::make_shared<aimdk_msgs::srv::SetMcInputSource::Request>();
 95    request->action.value = action_value;
 96    request->input_source.name = "node.keyboard";
 97    request->input_source.priority = 40;
 98    request->input_source.timeout = 1000;
 99
100    auto timeout = std::chrono::milliseconds(250);
101
102    for (int i = 0; i < 8; i++) {
103      request->request.header.stamp = this->now();
104      auto future = client_->async_send_request(request);
105      auto retcode = rclcpp::spin_until_future_complete(
106          this->get_node_base_interface(), future, timeout);
107      if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
108        // retry as remote peer is NOT handled well by ROS
109        RCLCPP_INFO(
110            this->get_logger(), "trying input source action %s (%d)... [%d]",
111            action_value == aimdk_msgs::msg::McInputAction::INPUTACTION_ADD
112                ? "ADD"
113                : "ENABLE",
114            action_value, i);
115        continue;
116      }
117      auto response = future.get();
118      return response->response.header.code == 0;
119    }
120    return false;
121  }
122
123  void checkKeyAndPublish() {
124    int ch = getch(); // non-blocking read
125
126    switch (ch) {
127    case ' ': // Space key
128      forward_velocity_ = 0.0;
129      lateral_velocity_ = 0.0;
130      angular_velocity_ = 0.0;
131      break;
132    case 'w':
133      forward_velocity_ = std::min(forward_velocity_ + step_, 1.0f);
134      break;
135    case 's':
136      forward_velocity_ = std::max(forward_velocity_ - step_, -1.0f);
137      break;
138    case 'a':
139      lateral_velocity_ = std::min(lateral_velocity_ + step_, 1.0f);
140      break;
141    case 'd':
142      lateral_velocity_ = std::max(lateral_velocity_ - step_, -1.0f);
143      break;
144    case 'q':
145      angular_velocity_ = std::min(angular_velocity_ + angular_step_, 1.0f);
146      break;
147    case 'e':
148      angular_velocity_ = std::max(angular_velocity_ - angular_step_, -1.0f);
149      break;
150    case 27: // ESC Key
151      RCLCPP_INFO(this->get_logger(), "Exiting control");
152      rclcpp::shutdown();
153      return;
154    }
155
156    auto msg = std::make_unique<McLocomotionVelocity>();
157    msg->header = aimdk_msgs::msg::MessageHeader();
158    msg->header.stamp = this->now();
159    msg->source = "node.keyboard";
160    msg->forward_velocity = forward_velocity_;
161    msg->lateral_velocity = lateral_velocity_;
162    msg->angular_velocity = angular_velocity_;
163
164    float fwd = forward_velocity_;
165    float lat = lateral_velocity_;
166    float ang = angular_velocity_;
167
168    pub_->publish(std::move(msg));
169
170    // Screen Output
171    clear();
172    mvprintw(0, 0,
173             "W/S: Forward/Backward | A/D: Left/Right Strafe | Q/E: Turn "
174             "Left/Right | Space: Stop | ESC: Exit");
175    mvprintw(2, 0,
176             "Speed Status: Forward: %.2f m/s | Lateral: %.2f m/s | Angular: "
177             "%.2f rad/s",
178             fwd, lat, ang);
179    refresh();
180  }
181};
182
183int main(int argc, char *argv[]) {
184  rclcpp::init(argc, argv);
185  try {
186    auto node = std::make_shared<KeyboardVelocityController>();
187    rclcpp::spin(node);
188  } catch (const std::exception &e) {
189    RCLCPP_FATAL(rclcpp::get_logger("main"),
190                 "Program exited with exception: %s", e.what());
191  }
192  rclcpp::shutdown();
193  return 0;
194}

6.2.13 Take Photo

This example uses take_photo. Before running the node, modify the camera topic from which to capture the image. After starting the node, an /images/ directory will be created, and the current frame will be saved into this directory.

 1#include <chrono>
 2#include <cv_bridge/cv_bridge.h>
 3#include <filesystem>
 4#include <opencv2/core.hpp>
 5#include <opencv2/imgcodecs.hpp>
 6#include <opencv2/imgproc.hpp>
 7#include <rclcpp/rclcpp.hpp>
 8#include <sensor_msgs/msg/image.hpp>
 9#include <string>
10
11class SaveOneRaw : public rclcpp::Node {
12public:
13  SaveOneRaw() : Node("save_one_image"), saved_(false) {
14    topic_ = declare_parameter<std::string>(
15        "image_topic", "/aima/hal/sensor/stereo_head_front_left/rgb_image");
16
17    std::filesystem::create_directories("images");
18
19    auto qos = rclcpp::SensorDataQoS(); // BestEffort/Volatile
20    sub_ = create_subscription<sensor_msgs::msg::Image>(
21        topic_, qos, std::bind(&SaveOneRaw::cb, this, std::placeholders::_1));
22
23    RCLCPP_INFO(get_logger(), "Subscribing (raw): %s", topic_.c_str());
24  }
25
26private:
27  void cb(const sensor_msgs::msg::Image::SharedPtr msg) {
28    if (saved_)
29      return;
30
31    try {
32      // Obtain the Mat without copying by not specifying encoding
33      cv_bridge::CvImageConstPtr cvp = cv_bridge::toCvShare(msg);
34      cv::Mat img = cvp->image;
35
36      // Convert to BGR for uniform saving
37      if (msg->encoding == "rgb8") {
38        cv::cvtColor(img, img, cv::COLOR_RGB2BGR);
39      } else if (msg->encoding == "mono8") {
40        cv::cvtColor(img, img, cv::COLOR_GRAY2BGR);
41      } // bgr8 Use this directly; add more branches as needed to support
42        // additional encodings.
43
44      auto now = std::chrono::system_clock::now();
45      auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
46                    now.time_since_epoch())
47                    .count();
48      std::string path = "images/frame_" + std::to_string(ms) + ".png";
49
50      if (cv::imwrite(path, img)) {
51        RCLCPP_INFO(get_logger(), "Saved: %s  (%dx%d)", path.c_str(), img.cols,
52                    img.rows);
53        saved_ = true;
54        rclcpp::shutdown();
55      } else {
56        RCLCPP_ERROR(get_logger(), "cv::imwrite failed: %s", path.c_str());
57      }
58    } catch (const std::exception &e) {
59      RCLCPP_ERROR(get_logger(), "raw decode failed: %s", e.what());
60      // Do not set the saved flag; wait for the next frame
61    }
62  }
63
64  std::string topic_;
65  bool saved_;
66  rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr sub_;
67};
68
69int main(int argc, char **argv) {
70  rclcpp::init(argc, argv);
71  rclcpp::spin(std::make_shared<SaveOneRaw>());
72  return 0;
73}

6.2.14 Camera Streaming Example Collection

This example set provides multiple camera data subscription and processing functions, supporting data streams from depth cameras, stereo cameras, and monocular cameras.
These camera subscription examples do not provide actual application-level logic; they only print basic camera data information. If you are familiar with ROS2, you may notice that ros2 topic echo + ros2 topic hz can also achieve similar functionality. You may directly check the topic list in the SDK interface manual to start developing your own module, or you may use these camera examples as scaffolding to integrate your own business logic. All published sensor data is raw and unprocessed (e.g., without undistortion). If you need detailed sensor information (such as resolution, focal length, etc.), please refer to the camera_info topic.

Depth Camera Data Subscription

This example uses echo_camera_rgbd, subscribing to the /aima/hal/sensor/rgbd_head_front/ topic to receive depth camera data, supporting multiple data types including depth images, RGB images, compressed RGB images, and camera intrinsic parameters.

Features:

  • Supports multiple data types (depth image, RGB image, compressed image, camera intrinsics)

  • Real-time FPS statistics and data display

  • Supports RGB video recording

  • Configurable topic type selection

Supported Data Types:

  • depth_image: Depth image (sensor_msgs/Image)

  • rgb_image: RGB image (sensor_msgs/Image)

  • rgb_image_compressed: Compressed RGB image (sensor_msgs/CompressedImage)

  • camera_info: Camera intrinsic parameters (sensor_msgs/CameraInfo)

  1#include <algorithm>
  2#include <deque>
  3#include <iomanip>
  4#include <memory>
  5#include <rclcpp/rclcpp.hpp>
  6#include <sensor_msgs/msg/camera_info.hpp>
  7#include <sensor_msgs/msg/compressed_image.hpp>
  8#include <sensor_msgs/msg/image.hpp>
  9#include <sstream>
 10#include <string>
 11#include <vector>
 12
 13// OpenCV headers for image/video writing
 14#include <cv_bridge/cv_bridge.h>
 15#include <opencv2/core.hpp>
 16#include <opencv2/imgproc.hpp>
 17#include <opencv2/videoio.hpp>
 18
 19/**
 20 * @brief Example of subscribing to multiple topics for the head depth camera
 21 *
 22 * You can select which topic type to subscribe to via the startup argument
 23 * --ros-args -p topic_type:=<type>:
 24 *   - depth_image: Depth image (sensor_msgs/Image)
 25 *   - rgb_image: RGB image (sensor_msgs/Image)
 26 *   - rgb_image_compressed: RGB compressed image (sensor_msgs/CompressedImage)
 27 *   - rgb_camera_info: Camera intrinsic parameters (sensor_msgs/CameraInfo)
 28 *   - depth_camera_info: Camera intrinsic parameters (sensor_msgs/CameraInfo)
 29 *
 30 * Examples:
 31 *   ros2 run examples echo_camera_rgbd --ros-args -p
 32 * topic_type:=rgb_image ros2
 33 *   run examples echo_camera_rgbd --ros-args -p
 34 * topic_type:=rgb_camera_info
 35 *
 36 * topic_type defaults to "rgb_image"
 37 *
 38 * See individual callbacks for more detailed comments
 39 */
 40class CameraTopicEcho : public rclcpp::Node {
 41public:
 42  CameraTopicEcho() : Node("camera_topic_echo") {
 43    // Select which topic type to subscribe to
 44    topic_type_ = declare_parameter<std::string>("topic_type", "rgb_image");
 45    dump_video_path_ = declare_parameter<std::string>("dump_video_path", "");
 46    dump_video_fps_ = declare_parameter<double>("dump_video_fps", 30.0);
 47
 48    // Subscribed topics and their message layouts
 49    // 1. /aima/hal/sensor/rgbd_head_front/depth_image
 50    //    - topic_type: depth_image
 51    //    - message type: sensor_msgs::msg::Image
 52    //    - frame_id: rgbd_head_front
 53    //    - contents: depth image
 54    // 2. /aima/hal/sensor/rgbd_head_front/rgb_image
 55    //    - topic_type: rgb_image
 56    //    - message type: sensor_msgs::msg::Image
 57    //    - frame_id: rgbd_head_front
 58    //    - contents: RGB image
 59    // 3. /aima/hal/sensor/rgbd_head_front/rgb_image/compressed
 60    //    - topic_type: rgb_image_compressed
 61    //    - message type: sensor_msgs::msg::CompressedImage
 62    //    - frame_id: rgbd_head_front
 63    //    - contents: RGB compressed image
 64    // 4. /aima/hal/sensor/rgbd_head_front/rgb_camera_info
 65    //    - topic_type: camera_info
 66    //    - message type: sensor_msgs::msg::CameraInfo
 67    //    - frame_id: rgbd_head_front
 68    //    - contents: RGB camera intrinsic parameters
 69    // 5. /aima/hal/sensor/rgbd_head_front/depth_camera_info
 70    //    - topic_type: camera_info
 71    //    - message type: sensor_msgs::msg::CameraInfo
 72    //    - frame_id: rgbd_head_front
 73    //    - contents: RGB camera intrinsic parameters
 74
 75    auto qos = rclcpp::SensorDataQoS();
 76
 77    // Enable depth image subscription
 78    if (topic_type_ == "depth_image") {
 79      topic_name_ = "/aima/hal/sensor/rgbd_head_front/depth_image";
 80      sub_image_ = create_subscription<sensor_msgs::msg::Image>(
 81          topic_name_, qos,
 82          std::bind(&CameraTopicEcho::cb_image, this, std::placeholders::_1));
 83      RCLCPP_INFO(get_logger(), "✅ Subscribing Depth Image: %s",
 84                  topic_name_.c_str());
 85
 86      // Enable RGB image subscription
 87    } else if (topic_type_ == "rgb_image") {
 88      topic_name_ = "/aima/hal/sensor/rgbd_head_front/rgb_image";
 89      sub_image_ = create_subscription<sensor_msgs::msg::Image>(
 90          topic_name_, qos,
 91          std::bind(&CameraTopicEcho::cb_image, this, std::placeholders::_1));
 92      RCLCPP_INFO(get_logger(), "✅ Subscribing RGB Image: %s",
 93                  topic_name_.c_str());
 94      if (!dump_video_path_.empty()) {
 95        RCLCPP_INFO(get_logger(), "📝 Will dump received images to video: %s",
 96                    dump_video_path_.c_str());
 97      }
 98
 99      // Enable RGB compressed image subscription
100    } else if (topic_type_ == "rgb_image_compressed") {
101      topic_name_ = "/aima/hal/sensor/rgbd_head_front/rgb_image/compressed";
102      sub_compressed_ = create_subscription<sensor_msgs::msg::CompressedImage>(
103          topic_name_, qos,
104          std::bind(&CameraTopicEcho::cb_compressed, this,
105                    std::placeholders::_1));
106      RCLCPP_INFO(get_logger(), "✅ Subscribing CompressedImage: %s",
107                  topic_name_.c_str());
108
109      // Enable rgb camera info subscription
110    } else if (topic_type_ == "rgb_camera_info") {
111      topic_name_ = "/aima/hal/sensor/rgbd_head_front/rgb_camera_info";
112      // RGB-D CameraInfo subscriptions are different from other cameras.
113      // The messages arrive at about 30Hz and SensorDataQoS is sufficient.
114      sub_camerainfo_ = create_subscription<sensor_msgs::msg::CameraInfo>(
115          topic_name_, qos,
116          std::bind(&CameraTopicEcho::cb_camerainfo, this,
117                    std::placeholders::_1));
118      RCLCPP_INFO(get_logger(), "✅ Subscribing RGB CameraInfo: %s",
119                  topic_name_.c_str());
120
121      // Enable depth camera info subscription
122    } else if (topic_type_ == "depth_camera_info") {
123      topic_name_ = "/aima/hal/sensor/rgbd_head_front/depth_camera_info";
124      // RGB-D CameraInfo subscriptions are different from other cameras.
125      // The messages arrive at about 30Hz and SensorDataQoS is sufficient.
126      sub_camerainfo_ = create_subscription<sensor_msgs::msg::CameraInfo>(
127          topic_name_, qos,
128          std::bind(&CameraTopicEcho::cb_camerainfo, this,
129                    std::placeholders::_1));
130      RCLCPP_INFO(get_logger(), "✅ Subscribing Depth CameraInfo: %s",
131                  topic_name_.c_str());
132
133      // Unknown topic_type error
134    } else {
135      RCLCPP_ERROR(get_logger(), "Unknown topic_type: %s", topic_type_.c_str());
136      throw std::runtime_error("Unknown topic_type");
137    }
138  }
139
140  ~CameraTopicEcho() override {
141    if (video_writer_.isOpened()) {
142      video_writer_.release();
143      RCLCPP_INFO(get_logger(), "Video file closed.");
144    }
145  }
146
147private:
148  // Image callback (depth/RGB image)
149  void cb_image(const sensor_msgs::msg::Image::SharedPtr msg) {
150    update_arrivals();
151
152    if (should_print()) {
153      RCLCPP_INFO(get_logger(),
154                  "📸 %s received\n"
155                  "  • frame_id:        %s\n"
156                  "  • stamp (sec):     %.6f\n"
157                  "  • encoding:        %s\n"
158                  "  • size (WxH):      %u x %u\n"
159                  "  • step (bytes/row):%u\n"
160                  "  • is_bigendian:    %u\n"
161                  "  • recv FPS (1s):   %.1f",
162                  topic_type_.c_str(), msg->header.frame_id.c_str(),
163                  rclcpp::Time(msg->header.stamp).seconds(),
164                  msg->encoding.c_str(), msg->width, msg->height, msg->step,
165                  msg->is_bigendian, get_fps());
166    }
167
168    // Video dump is supported only for RGB images
169    if (topic_type_ == "rgb_image" && !dump_video_path_.empty()) {
170      dump_image_to_video(msg);
171    }
172  }
173
174  // CompressedImage callback
175  void cb_compressed(const sensor_msgs::msg::CompressedImage::SharedPtr msg) {
176    update_arrivals();
177
178    if (should_print()) {
179      RCLCPP_INFO(get_logger(),
180                  "🗜️  CompressedImage received\n"
181                  "  • frame_id:        %s\n"
182                  "  • stamp (sec):     %.6f\n"
183                  "  • format:          %s\n"
184                  "  • data size:       %zu\n"
185                  "  • recv FPS (1s):   %.1f",
186                  msg->header.frame_id.c_str(),
187                  rclcpp::Time(msg->header.stamp).seconds(),
188                  msg->format.c_str(), msg->data.size(), get_fps());
189    }
190  }
191
192  // CameraInfo callback (camera intrinsic parameters)
193  void cb_camerainfo(const sensor_msgs::msg::CameraInfo::SharedPtr msg) {
194    // CameraInfo is typically published once; print it once
195    std::ostringstream oss;
196    oss << "📷 " << topic_type_ << " received\n"
197        << "  • frame_id:        " << msg->header.frame_id << "\n"
198        << "  • stamp (sec):     " << rclcpp::Time(msg->header.stamp).seconds()
199        << "\n"
200        << "  • width x height:  " << msg->width << " x " << msg->height << "\n"
201        << "  • distortion_model:" << msg->distortion_model << "\n"
202        << "  • D: [";
203    for (size_t i = 0; i < msg->d.size(); ++i) {
204      oss << msg->d[i];
205      if (i + 1 < msg->d.size())
206        oss << ", ";
207    }
208    oss << "]\n  • K: [";
209    for (int i = 0; i < 9; ++i) {
210      oss << msg->k[i];
211      if (i + 1 < 9)
212        oss << ", ";
213    }
214    oss << "]\n  • R: [";
215    for (int i = 0; i < 9; ++i) {
216      oss << msg->r[i];
217      if (i + 1 < 9)
218        oss << ", ";
219    }
220    oss << "]\n  • P: [";
221    for (int i = 0; i < 12; ++i) {
222      oss << msg->p[i];
223      if (i + 1 < 12)
224        oss << ", ";
225    }
226    oss << "]\n"
227        << "  • binning_x: " << msg->binning_x << "\n"
228        << "  • binning_y: " << msg->binning_y << "\n"
229        << "  • roi: { x_offset: " << msg->roi.x_offset
230        << ", y_offset: " << msg->roi.y_offset
231        << ", height: " << msg->roi.height << ", width: " << msg->roi.width
232        << ", do_rectify: " << (msg->roi.do_rectify ? "true" : "false") << " }";
233    RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
234  }
235
236  // Track arrival timestamps to compute FPS
237  void update_arrivals() {
238    const rclcpp::Time now = this->get_clock()->now();
239    arrivals_.push_back(now);
240    while (!arrivals_.empty() && (now - arrivals_.front()).seconds() > 1.0) {
241      arrivals_.pop_front();
242    }
243  }
244  double get_fps() const { return static_cast<double>(arrivals_.size()); }
245
246  // Control printing frequency
247  bool should_print() {
248    const rclcpp::Time now = this->get_clock()->now();
249    if ((now - last_print_).seconds() >= 1.0) {
250      last_print_ = now;
251      return true;
252    }
253    return false;
254  }
255
256  // Dump received images to a video file (RGB images only)
257  void dump_image_to_video(const sensor_msgs::msg::Image::SharedPtr &msg) {
258    cv::Mat image;
259    try {
260      // Obtain the Mat without copying by not specifying encoding
261      cv_bridge::CvImageConstPtr cvp = cv_bridge::toCvShare(msg);
262      image = cvp->image;
263      // Convert to BGR for uniform saving
264      if (msg->encoding == "rgb8") {
265        cv::cvtColor(image, image, cv::COLOR_RGB2BGR);
266      } else {
267        RCLCPP_WARN(get_logger(), "image encoding not expected: %s",
268                    msg->encoding.c_str());
269        return;
270      }
271    } catch (const std::exception &e) {
272      RCLCPP_WARN(get_logger(), "cv_bridge exception: %s", e.what());
273      return;
274    }
275
276    // Initialize VideoWriter
277    if (!video_writer_.isOpened()) {
278      int fourcc = cv::VideoWriter::fourcc('m', 'p', '4', 'v');
279      double fps = dump_video_fps_;
280      bool ok = video_writer_.open(dump_video_path_, fourcc, fps,
281                                   cv::Size(image.cols, image.rows), true);
282      if (!ok) {
283        RCLCPP_ERROR(get_logger(), "Failed to open video file: %s",
284                     dump_video_path_.c_str());
285        dump_video_path_.clear(); // stop trying
286        return;
287      }
288      RCLCPP_INFO(get_logger(), "VideoWriter started: %s, size=%dx%d, fps=%.1f",
289                  dump_video_path_.c_str(), image.cols, image.rows, fps);
290    }
291    video_writer_.write(image);
292  }
293
294  // Member variables
295  std::string topic_type_;
296  std::string topic_name_;
297  std::string dump_video_path_;
298  double dump_video_fps_ = 30.0;
299
300  // Subscriptions
301  rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr sub_image_;
302  rclcpp::Subscription<sensor_msgs::msg::CompressedImage>::SharedPtr
303      sub_compressed_;
304  rclcpp::Subscription<sensor_msgs::msg::CameraInfo>::SharedPtr sub_camerainfo_;
305
306  // FPS statistics
307  rclcpp::Time last_print_{0, 0, RCL_ROS_TIME};
308  std::deque<rclcpp::Time> arrivals_;
309
310  // Video writer
311  cv::VideoWriter video_writer_;
312};
313
314int main(int argc, char **argv) {
315  rclcpp::init(argc, argv);
316  auto node = std::make_shared<CameraTopicEcho>();
317  rclcpp::spin(node);
318  rclcpp::shutdown();
319  return 0;
320}

Usage Instructions:

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

Stereo Camera Data Subscription

This example uses echo_camera_stereo, subscribing to the /aima/hal/sensor/stereo_head_front_*/ topics to receive stereo camera data, supporting RGB images, compressed images, and camera intrinsic parameters from both the left and right cameras.

Features:

  • Supports independent subscription for left and right cameras

  • Real-time FPS statistics and data display

  • Supports RGB video recording

  • Configurable camera selection (left/right)

Supported Data Types:

  • left_rgb_image: Left camera RGB image (sensor_msgs/Image)

  • left_rgb_image_compressed: Left camera compressed RGB image (sensor_msgs/CompressedImage)

  • left_camera_info: Left camera intrinsic parameters (sensor_msgs/CameraInfo)

  • right_rgb_image: Right camera RGB image (sensor_msgs/Image)

  • right_rgb_image_compressed: Right camera compressed RGB image (sensor_msgs/CompressedImage)

  • right_camera_info: Right camera intrinsic parameters (sensor_msgs/CameraInfo)

  1#include <algorithm>
  2#include <deque>
  3#include <iomanip>
  4#include <memory>
  5#include <rclcpp/rclcpp.hpp>
  6#include <sensor_msgs/msg/camera_info.hpp>
  7#include <sensor_msgs/msg/compressed_image.hpp>
  8#include <sensor_msgs/msg/image.hpp>
  9#include <sstream>
 10#include <string>
 11#include <vector>
 12
 13// OpenCV headers for image/video writing
 14#include <cv_bridge/cv_bridge.h>
 15#include <opencv2/core.hpp>
 16#include <opencv2/imgproc.hpp>
 17#include <opencv2/videoio.hpp>
 18
 19/**
 20 * @brief Example of subscribing to multiple topics for the stereo head camera
 21 *
 22 * You can select which topic type to subscribe to via the startup argument
 23 * --ros-args -p topic_type:=<type>:
 24 *   - left_rgb_image: left camera RGB image (sensor_msgs/Image)
 25 *   - left_rgb_image_compressed: left camera RGB compressed image
 26 * (sensor_msgs/CompressedImage)
 27 *   - left_camera_info: left camera intrinsic parameters
 28 * (sensor_msgs/CameraInfo)
 29 *   - right_rgb_image: right camera RGB image (sensor_msgs/Image)
 30 *   - right_rgb_image_compressed: right camera RGB compressed image
 31 * (sensor_msgs/CompressedImage)
 32 *   - right_camera_info: right camera intrinsic parameters
 33 * (sensor_msgs/CameraInfo)
 34 *
 35 * Examples:
 36 *   ros2 run examples echo_camera_stereo --ros-args -p
 37 * topic_type:=left_rgb_image ros2 run examples echo_camera_stereo --ros-args -p
 38 * topic_type:=right_rgb_image ros2 run examples echo_camera_stereo --ros-args
 39 * -p topic_type:=left_camera_info
 40 *
 41 * topic_type defaults to "left_rgb_image"
 42 *
 43 * See individual callbacks for more detailed comments
 44 */
 45class StereoCameraTopicEcho : public rclcpp::Node {
 46public:
 47  StereoCameraTopicEcho() : Node("stereo_camera_topic_echo") {
 48    // Select which topic type to subscribe to
 49    topic_type_ =
 50        declare_parameter<std::string>("topic_type", "left_rgb_image");
 51    dump_video_path_ = declare_parameter<std::string>("dump_video_path", "");
 52    dump_video_fps_ = declare_parameter<double>("dump_video_fps", 10.0);
 53
 54    // Subscribed topics and their message layouts
 55    // 1. /aima/hal/sensor/stereo_head_front_left/rgb_image
 56    //    - topic_type: left_rgb_image
 57    //    - message type: sensor_msgs::msg::Image
 58    //    - frame_id: stereo_head_front
 59    //    - child_frame_id: /
 60    //    - contents: left camera raw image
 61    // 2. /aima/hal/sensor/stereo_head_front_left/rgb_image/compressed
 62    //    - topic_type: left_rgb_image_compressed
 63    //    - message type: sensor_msgs::msg::CompressedImage
 64    //    - frame_id: stereo_head_front
 65    //    - contents: left camera compressed image
 66    // 3. /aima/hal/sensor/stereo_head_front_left/camera_info
 67    //    - topic_type: left_camera_info
 68    //    - message type: sensor_msgs::msg::CameraInfo
 69    //    - frame_id: stereo_head_front
 70    //    - contents: left camera intrinsic parameters
 71    // 4. /aima/hal/sensor/stereo_head_front_right/rgb_image
 72    //    - topic_type: right_rgb_image
 73    //    - message type: sensor_msgs::msg::Image
 74    //    - frame_id: stereo_head_front_right
 75    //    - child_frame_id: /
 76    //    - contents: right camera raw image
 77    // 5. /aima/hal/sensor/stereo_head_front_right/rgb_image/compressed
 78    //    - topic_type: right_rgb_image_compressed
 79    //    - message type: sensor_msgs::msg::CompressedImage
 80    //    - frame_id: stereo_head_front_right
 81    //    - contents: right camera compressed image
 82    // 6. /aima/hal/sensor/stereo_head_front_right/camera_info
 83    //    - topic_type: right_camera_info
 84    //    - message type: sensor_msgs::msg::CameraInfo
 85    //    - frame_id: stereo_head_front_right
 86    //    - contents: right camera intrinsic parameters
 87
 88    // Set QoS parameters - use SensorData QoS
 89    auto qos = rclcpp::SensorDataQoS();
 90
 91    // Enable left camera RGB image subscription
 92    if (topic_type_ == "left_rgb_image") {
 93      topic_name_ = "/aima/hal/sensor/stereo_head_front_left/rgb_image";
 94      sub_image_ = create_subscription<sensor_msgs::msg::Image>(
 95          topic_name_, qos,
 96          std::bind(&StereoCameraTopicEcho::cb_image, this,
 97                    std::placeholders::_1));
 98      RCLCPP_INFO(get_logger(), "✅ Subscribing Left RGB Image: %s",
 99                  topic_name_.c_str());
100      if (!dump_video_path_.empty()) {
101        RCLCPP_INFO(get_logger(), "📝 Will dump received images to video: %s",
102                    dump_video_path_.c_str());
103      }
104
105      // Enable left camera RGB compressed image subscription
106    } else if (topic_type_ == "left_rgb_image_compressed") {
107      topic_name_ =
108          "/aima/hal/sensor/stereo_head_front_left/rgb_image/compressed";
109      sub_compressed_ = create_subscription<sensor_msgs::msg::CompressedImage>(
110          topic_name_, qos,
111          std::bind(&StereoCameraTopicEcho::cb_compressed, this,
112                    std::placeholders::_1));
113      RCLCPP_INFO(get_logger(), "✅ Subscribing Left CompressedImage: %s",
114                  topic_name_.c_str());
115
116      // Enable left camera info subscription
117    } else if (topic_type_ == "left_camera_info") {
118
119      topic_name_ = "/aima/hal/sensor/stereo_head_front_left/camera_info";
120      // CameraInfo subscriptions must use reliable + transient_local
121      // QoS in order to receive latched/history messages (even if only one
122      // message was published). Here we use keep_last(1) + reliable
123      // + transient_local.
124      sub_camerainfo_ = create_subscription<sensor_msgs::msg::CameraInfo>(
125          topic_name_,
126          rclcpp::QoS(rclcpp::KeepLast(1)).reliable().transient_local(),
127          std::bind(&StereoCameraTopicEcho::cb_camerainfo, this,
128                    std::placeholders::_1));
129      RCLCPP_INFO(get_logger(),
130                  "✅ Subscribing Left CameraInfo (with transient_local): %s",
131                  topic_name_.c_str());
132
133      // Enable right camera RGB image subscription
134    } else if (topic_type_ == "right_rgb_image") {
135      topic_name_ = "/aima/hal/sensor/stereo_head_front_right/rgb_image";
136      sub_image_ = create_subscription<sensor_msgs::msg::Image>(
137          topic_name_, qos,
138          std::bind(&StereoCameraTopicEcho::cb_image, this,
139                    std::placeholders::_1));
140      RCLCPP_INFO(get_logger(), "✅ Subscribing Right RGB Image: %s",
141                  topic_name_.c_str());
142      if (!dump_video_path_.empty()) {
143        RCLCPP_INFO(get_logger(), "📝 Will dump received images to video: %s",
144                    dump_video_path_.c_str());
145      }
146
147      // Enable right camera RGB compressed image subscription
148    } else if (topic_type_ == "right_rgb_image_compressed") {
149      topic_name_ =
150          "/aima/hal/sensor/stereo_head_front_right/rgb_image/compressed";
151      sub_compressed_ = create_subscription<sensor_msgs::msg::CompressedImage>(
152          topic_name_, qos,
153          std::bind(&StereoCameraTopicEcho::cb_compressed, this,
154                    std::placeholders::_1));
155      RCLCPP_INFO(get_logger(), "✅ Subscribing Right CompressedImage: %s",
156                  topic_name_.c_str());
157
158      // Enable right camera info subscription
159    } else if (topic_type_ == "right_camera_info") {
160      topic_name_ = "/aima/hal/sensor/stereo_head_front_right/camera_info";
161      // CameraInfo subscriptions must use reliable + transient_local
162      // QoS in order to receive latched/history messages (even if only one
163      // message was published). Here we use keep_last(1) + reliable
164      // + transient_local.
165      sub_camerainfo_ = create_subscription<sensor_msgs::msg::CameraInfo>(
166          topic_name_,
167          rclcpp::QoS(rclcpp::KeepLast(1)).reliable().transient_local(),
168          std::bind(&StereoCameraTopicEcho::cb_camerainfo, this,
169                    std::placeholders::_1));
170      RCLCPP_INFO(get_logger(),
171                  "✅ Subscribing Right CameraInfo (with transient_local): %s",
172                  topic_name_.c_str());
173
174      // Unknown topic_type error
175    } else {
176      RCLCPP_ERROR(get_logger(), "Unknown topic_type: %s", topic_type_.c_str());
177      throw std::runtime_error("Unknown topic_type");
178    }
179  }
180
181  ~StereoCameraTopicEcho() override {
182    if (video_writer_.isOpened()) {
183      video_writer_.release();
184      RCLCPP_INFO(get_logger(), "Video file closed.");
185    }
186  }
187
188private:
189  // Image callback (left/right RGB image)
190  void cb_image(const sensor_msgs::msg::Image::SharedPtr msg) {
191    update_arrivals();
192
193    if (should_print()) {
194      RCLCPP_INFO(get_logger(),
195                  "📸 %s received\n"
196                  "  • frame_id:        %s\n"
197                  "  • stamp (sec):     %.6f\n"
198                  "  • encoding:        %s\n"
199                  "  • size (WxH):      %u x %u\n"
200                  "  • step (bytes/row):%u\n"
201                  "  • is_bigendian:    %u\n"
202                  "  • recv FPS (1s):   %.1f",
203                  topic_type_.c_str(), msg->header.frame_id.c_str(),
204                  rclcpp::Time(msg->header.stamp).seconds(),
205                  msg->encoding.c_str(), msg->width, msg->height, msg->step,
206                  msg->is_bigendian, get_fps());
207    }
208
209    // Video dump is supported only for RGB images
210    if ((topic_type_ == "left_rgb_image" || topic_type_ == "right_rgb_image") &&
211        !dump_video_path_.empty()) {
212      dump_image_to_video(msg);
213    }
214  }
215
216  // CompressedImage callback (left/right RGB compressed image)
217  void cb_compressed(const sensor_msgs::msg::CompressedImage::SharedPtr msg) {
218    update_arrivals();
219
220    if (should_print()) {
221      RCLCPP_INFO(get_logger(),
222                  "🗜️  %s received\n"
223                  "  • frame_id:        %s\n"
224                  "  • stamp (sec):     %.6f\n"
225                  "  • format:          %s\n"
226                  "  • data size:       %zu\n"
227                  "  • recv FPS (1s):   %.1f",
228                  topic_type_.c_str(), msg->header.frame_id.c_str(),
229                  rclcpp::Time(msg->header.stamp).seconds(),
230                  msg->format.c_str(), msg->data.size(), get_fps());
231    }
232  }
233
234  // CameraInfo callback (left/right camera intrinsic parameters)
235  void cb_camerainfo(const sensor_msgs::msg::CameraInfo::SharedPtr msg) {
236    // CameraInfo is typically published once; print it once
237    std::ostringstream oss;
238    oss << "📷 " << topic_type_ << " received\n"
239        << "  • frame_id:        " << msg->header.frame_id << "\n"
240        << "  • stamp (sec):     " << rclcpp::Time(msg->header.stamp).seconds()
241        << "\n"
242        << "  • width x height:  " << msg->width << " x " << msg->height << "\n"
243        << "  • distortion_model:" << msg->distortion_model << "\n"
244        << "  • D: [";
245    for (size_t i = 0; i < msg->d.size(); ++i) {
246      oss << msg->d[i];
247      if (i + 1 < msg->d.size())
248        oss << ", ";
249    }
250    oss << "]\n  • K: [";
251    for (int i = 0; i < 9; ++i) {
252      oss << msg->k[i];
253      if (i + 1 < 9)
254        oss << ", ";
255    }
256    oss << "]\n  • R: [";
257    for (int i = 0; i < 9; ++i) {
258      oss << msg->r[i];
259      if (i + 1 < 9)
260        oss << ", ";
261    }
262    oss << "]\n  • P: [";
263    for (int i = 0; i < 12; ++i) {
264      oss << msg->p[i];
265      if (i + 1 < 12)
266        oss << ", ";
267    }
268    oss << "]\n"
269        << "  • binning_x: " << msg->binning_x << "\n"
270        << "  • binning_y: " << msg->binning_y << "\n"
271        << "  • roi: { x_offset: " << msg->roi.x_offset
272        << ", y_offset: " << msg->roi.y_offset
273        << ", height: " << msg->roi.height << ", width: " << msg->roi.width
274        << ", do_rectify: " << (msg->roi.do_rectify ? "true" : "false") << " }";
275    RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
276  }
277
278  // Track arrival timestamps to compute FPS
279  void update_arrivals() {
280    const rclcpp::Time now = this->get_clock()->now();
281    arrivals_.push_back(now);
282    while (!arrivals_.empty() && (now - arrivals_.front()).seconds() > 1.0) {
283      arrivals_.pop_front();
284    }
285  }
286  double get_fps() const { return static_cast<double>(arrivals_.size()); }
287
288  // Control printing frequency
289  bool should_print() {
290    const rclcpp::Time now = this->get_clock()->now();
291    if ((now - last_print_).seconds() >= 1.0) {
292      last_print_ = now;
293      return true;
294    }
295    return false;
296  }
297
298  // Dump received images to a video file (RGB images only)
299  void dump_image_to_video(const sensor_msgs::msg::Image::SharedPtr &msg) {
300    cv::Mat image;
301    try {
302      // Obtain the Mat without copying by not specifying encoding
303      cv_bridge::CvImageConstPtr cvp = cv_bridge::toCvShare(msg);
304      image = cvp->image;
305      // Convert to BGR for uniform saving
306      if (msg->encoding == "rgb8") {
307        cv::cvtColor(image, image, cv::COLOR_RGB2BGR);
308      } else {
309        RCLCPP_WARN(get_logger(), "image encoding not expected: %s",
310                    msg->encoding.c_str());
311        return;
312      }
313    } catch (const std::exception &e) {
314      RCLCPP_WARN(get_logger(), "cv_bridge exception: %s", e.what());
315      return;
316    }
317
318    // Initialize VideoWriter
319    if (!video_writer_.isOpened()) {
320      int fourcc = cv::VideoWriter::fourcc('m', 'p', '4', 'v');
321      double fps = dump_video_fps_;
322      bool ok = video_writer_.open(dump_video_path_, fourcc, fps,
323                                   cv::Size(image.cols, image.rows), true);
324      if (!ok) {
325        RCLCPP_ERROR(get_logger(), "Failed to open video file: %s",
326                     dump_video_path_.c_str());
327        dump_video_path_.clear(); // stop trying
328        return;
329      }
330      RCLCPP_INFO(get_logger(), "VideoWriter started: %s, size=%dx%d, fps=%.1f",
331                  dump_video_path_.c_str(), image.cols, image.rows, fps);
332    }
333    video_writer_.write(image);
334  }
335
336  // Member variables
337  std::string topic_type_;
338  std::string topic_name_;
339  std::string dump_video_path_;
340  double dump_video_fps_ = 10.0;
341
342  // Subscriptions
343  rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr sub_image_;
344  rclcpp::Subscription<sensor_msgs::msg::CompressedImage>::SharedPtr
345      sub_compressed_;
346  rclcpp::Subscription<sensor_msgs::msg::CameraInfo>::SharedPtr sub_camerainfo_;
347
348  // FPS statistics
349  rclcpp::Time last_print_{0, 0, RCL_ROS_TIME};
350  std::deque<rclcpp::Time> arrivals_;
351
352  // Video writer
353  cv::VideoWriter video_writer_;
354};
355
356int main(int argc, char **argv) {
357  rclcpp::init(argc, argv);
358  auto node = std::make_shared<StereoCameraTopicEcho>();
359  rclcpp::spin(node);
360  rclcpp::shutdown();
361  return 0;
362}

Usage Instructions:

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

Rear Head Monocular Camera Data Subscription

This example uses echo_camera_head_rear, subscribing to the /aima/hal/sensor/rgb_head_rear/ topic to receive data from the robot’s rear head monocular camera, supporting RGB images (with/without mask), compressed images, and camera intrinsic parameters.

Features:

  • Supports rear head camera data subscription

  • Real-time FPS statistics and data display

  • Supports RGB video recording with/without obstructed area masked

  • Configurable topic type selection

  • Detects installation orientation and applies rotation compensation

Supported Data Types:

  • rgb_image: RGB image (sensor_msgs/Image)

  • rgb_image_compressed: Compressed RGB image (sensor_msgs/CompressedImage)

  • camera_info: Camera intrinsic parameters (sensor_msgs/CameraInfo)

  1#include <algorithm>
  2#include <deque>
  3#include <filesystem>
  4#include <fstream>
  5#include <memory>
  6#include <rclcpp/rclcpp.hpp>
  7#include <regex>
  8#include <sensor_msgs/msg/camera_info.hpp>
  9#include <sensor_msgs/msg/compressed_image.hpp>
 10#include <sensor_msgs/msg/image.hpp>
 11#include <sstream>
 12#include <string>
 13#include <vector>
 14
 15#include <cv_bridge/cv_bridge.h>
 16#include <opencv2/core.hpp>
 17#include <opencv2/imgcodecs.hpp>
 18#include <opencv2/imgproc.hpp>
 19#include <opencv2/videoio.hpp>
 20
 21/**
 22 * @brief Example of subscribing to multiple topics for the rear head monocular
 23 * camera
 24 *
 25 * You can select which topic type to subscribe to via the startup argument
 26 * --ros-args -p topic_type:=<type>:
 27 *   - rgb_image: RGB image (sensor_msgs/Image)
 28 *   - rgb_image_compressed: RGB compressed image (sensor_msgs/CompressedImage)
 29 *   - camera_info: Camera intrinsic parameters (sensor_msgs/CameraInfo)
 30 *
 31 * Examples:
 32 *   ros2 run examples echo_camera_head_rear --ros-args -p topic_type:=rgb_image
 33 *   ros2 run examples echo_camera_head_rear --ros-args -p
 34 * topic_type:=rgb_image_compressed ros2 run examples echo_camera_head_rear
 35 * --ros-args -p topic_type:=camera_info
 36 *
 37 * topic_type defaults to "rgb_image"
 38 *
 39 * Note: Some rear cameras are rotated by 90 degrees. This example
 40 * automatically detects them and applies rotation compensation
 41 * when needed.
 42 */
 43class HeadRearCameraTopicEcho : public rclcpp::Node {
 44public:
 45  HeadRearCameraTopicEcho() : Node("head_rear_camera_topic_echo") {
 46    topic_type_ = declare_parameter<std::string>("topic_type", "rgb_image");
 47    dump_video_path_ = declare_parameter<std::string>("dump_video_path", "");
 48    dump_video_fps_ = declare_parameter<double>("dump_video_fps", 10.0);
 49    with_mask_ = declare_parameter<bool>("with_mask", false);
 50    auto_rotate_ = declare_parameter<bool>("auto_rotate", true);
 51
 52    auto qos = rclcpp::SensorDataQoS();
 53
 54    if (auto_rotate_) {
 55      detect_rotate_from_file();
 56    }
 57
 58    if (with_mask_ && !dump_video_path_.empty()) {
 59      std::string mask_file = need_rotation_ ? "rgb_head_rear_mask_t35.png"
 60                                             : "rgb_head_rear_mask.png";
 61      auto mask_path =
 62          std::filesystem::read_symlink("/proc/self/exe").parent_path() /
 63          "data" / mask_file;
 64      mask_image_ = cv::imread(mask_path, cv::IMREAD_GRAYSCALE);
 65      if (mask_image_.empty()) {
 66        RCLCPP_ERROR(get_logger(), "Failed to load mask file from %s",
 67                     mask_path.c_str());
 68        throw std::runtime_error("Failed to load mask file");
 69      }
 70      RCLCPP_INFO(get_logger(), "🎭 Loaded mask: %s", mask_file.c_str());
 71    }
 72
 73    if (topic_type_ == "rgb_image") {
 74      topic_name_ = "/aima/hal/sensor/rgb_head_rear/rgb_image";
 75      sub_image_ = create_subscription<sensor_msgs::msg::Image>(
 76          topic_name_, qos,
 77          std::bind(&HeadRearCameraTopicEcho::cb_image, this,
 78                    std::placeholders::_1));
 79      RCLCPP_INFO(get_logger(), "✅ Subscribing RGB Image: %s",
 80                  topic_name_.c_str());
 81      if (!dump_video_path_.empty()) {
 82        RCLCPP_INFO(get_logger(),
 83                    "📝 Will dump received images %s mask %s to video: %s",
 84                    (with_mask_ ? "with" : "without"),
 85                    (need_rotation_ ? "with rotation compensation"
 86                                    : "without rotation"),
 87                    dump_video_path_.c_str());
 88      }
 89    }
 90
 91    else if (topic_type_ == "rgb_image_compressed") {
 92      topic_name_ = "/aima/hal/sensor/rgb_head_rear/rgb_image/compressed";
 93      sub_compressed_ = create_subscription<sensor_msgs::msg::CompressedImage>(
 94          topic_name_, qos,
 95          std::bind(&HeadRearCameraTopicEcho::cb_compressed, this,
 96                    std::placeholders::_1));
 97      RCLCPP_INFO(get_logger(), "✅ Subscribing CompressedImage: %s",
 98                  topic_name_.c_str());
 99
100    } else if (topic_type_ == "camera_info") {
101      topic_name_ = "/aima/hal/sensor/rgb_head_rear/camera_info";
102      sub_camerainfo_ = create_subscription<sensor_msgs::msg::CameraInfo>(
103          topic_name_,
104          rclcpp::QoS(rclcpp::KeepLast(1)).reliable().transient_local(),
105          std::bind(&HeadRearCameraTopicEcho::cb_camerainfo, this,
106                    std::placeholders::_1));
107      RCLCPP_INFO(get_logger(),
108                  "✅ Subscribing CameraInfo (with transient_local): %s",
109                  topic_name_.c_str());
110
111    } else {
112      RCLCPP_ERROR(get_logger(), "Unknown topic_type: %s", topic_type_.c_str());
113      throw std::runtime_error("Unknown topic_type");
114    }
115  }
116
117  ~HeadRearCameraTopicEcho() override {
118    if (video_writer_.isOpened()) {
119      video_writer_.release();
120      RCLCPP_INFO(get_logger(), "Video file closed.");
121    }
122  }
123
124private:
125  void detect_rotate_from_file() {
126    std::string hw_file = "/agibot/data/info/hardware_version";
127    std::ifstream file(hw_file);
128
129    if (file.is_open()) {
130      std::getline(file, hw_version_);
131      hw_version_.erase(hw_version_.find_last_not_of(" \n\r\t") + 1);
132      RCLCPP_INFO(get_logger(), "📋 Hardware version from file: '%s'",
133                  hw_version_.c_str());
134      check_rotation(hw_version_);
135    } else {
136      RCLCPP_WARN(get_logger(), "⚠️  Hardware version file not found: %s",
137                  hw_file.c_str());
138    }
139  }
140
141  void check_rotation(const std::string &version) {
142    std::regex rear_rotation_pattern("t3[d.]5", std::regex::icase);
143    if (std::regex_search(version, rear_rotation_pattern)) {
144      need_rotation_ = true;
145      RCLCPP_INFO(get_logger(), "🔄 Rotation detected: Rear camera rotation "
146                                "compensation enabled (90° counter-clockwise)");
147    } else {
148      RCLCPP_INFO(
149          get_logger(),
150          "✅ No rotation detected (%s): No rotation compensation needed",
151          version.c_str());
152    }
153  }
154
155  cv::Mat rotate_image_90_ccw(const cv::Mat &image) {
156    cv::Mat rotated;
157    cv::rotate(image, rotated, cv::ROTATE_90_COUNTERCLOCKWISE);
158    return rotated;
159  }
160
161  void cb_image(const sensor_msgs::msg::Image::SharedPtr msg) {
162    update_arrivals();
163
164    if (should_print()) {
165      std::string rotation_note = need_rotation_ ? " (rotated 90° CCW)" : "";
166      RCLCPP_INFO(get_logger(),
167                  "📸 %s received%s\n"
168                  "  • frame_id:        %s\n"
169                  "  • stamp (sec):     %.6f\n"
170                  "  • encoding:        %s\n"
171                  "  • size (WxH):      %u x %u\n"
172                  "  • step (bytes/row):%u\n"
173                  "  • is_bigendian:    %u\n"
174                  "  • recv FPS (1s):   %.1f",
175                  topic_type_.c_str(), rotation_note.c_str(),
176                  msg->header.frame_id.c_str(),
177                  rclcpp::Time(msg->header.stamp).seconds(),
178                  msg->encoding.c_str(), msg->width, msg->height, msg->step,
179                  msg->is_bigendian, get_fps());
180    }
181
182    if (topic_type_ == "rgb_image" && !dump_video_path_.empty()) {
183      dump_image_to_video(msg);
184    }
185  }
186
187  void cb_compressed(const sensor_msgs::msg::CompressedImage::SharedPtr msg) {
188    update_arrivals();
189
190    if (should_print()) {
191      RCLCPP_INFO(get_logger(),
192                  "🗜️  %s received\n"
193                  "  • frame_id:        %s\n"
194                  "  • stamp (sec):     %.6f\n"
195                  "  • format:          %s\n"
196                  "  • data size:       %zu\n"
197                  "  • recv FPS (1s):   %.1f",
198                  topic_type_.c_str(), msg->header.frame_id.c_str(),
199                  rclcpp::Time(msg->header.stamp).seconds(),
200                  msg->format.c_str(), msg->data.size(), get_fps());
201    }
202  }
203
204  void cb_camerainfo(const sensor_msgs::msg::CameraInfo::SharedPtr msg) {
205    std::ostringstream oss;
206    oss << "📷 " << topic_type_ << " received\n"
207        << "  • frame_id:        " << msg->header.frame_id << "\n"
208        << "  • stamp (sec):     " << rclcpp::Time(msg->header.stamp).seconds()
209        << "\n"
210        << "  • width x height:  " << msg->width << " x " << msg->height << "\n"
211        << "  • distortion_model:" << msg->distortion_model << "\n"
212        << "  • D: [";
213    for (size_t i = 0; i < msg->d.size(); ++i) {
214      oss << msg->d[i];
215      if (i + 1 < msg->d.size())
216        oss << ", ";
217    }
218    oss << "]\n  • K: [";
219    for (int i = 0; i < 9; ++i) {
220      oss << msg->k[i];
221      if (i + 1 < 9)
222        oss << ", ";
223    }
224    oss << "]\n  • R: [";
225    for (int i = 0; i < 9; ++i) {
226      oss << msg->r[i];
227      if (i + 1 < 9)
228        oss << ", ";
229    }
230    oss << "]\n  • P: [";
231    for (int i = 0; i < 12; ++i) {
232      oss << msg->p[i];
233      if (i + 1 < 12)
234        oss << ", ";
235    }
236    oss << "]\n"
237        << "  • binning_x: " << msg->binning_x << "\n"
238        << "  • binning_y: " << msg->binning_y << "\n"
239        << "  • roi: { x_offset: " << msg->roi.x_offset
240        << ", y_offset: " << msg->roi.y_offset
241        << ", height: " << msg->roi.height << ", width: " << msg->roi.width
242        << ", do_rectify: " << (msg->roi.do_rectify ? "true" : "false") << " }";
243    RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
244  }
245
246  void update_arrivals() {
247    const rclcpp::Time now = this->get_clock()->now();
248    arrivals_.push_back(now);
249    while (!arrivals_.empty() && (now - arrivals_.front()).seconds() > 1.0) {
250      arrivals_.pop_front();
251    }
252  }
253  double get_fps() const { return static_cast<double>(arrivals_.size()); }
254
255  bool should_print() {
256    const rclcpp::Time now = this->get_clock()->now();
257    if ((now - last_print_).seconds() >= 1.0) {
258      last_print_ = now;
259      return true;
260    }
261    return false;
262  }
263
264  void dump_image_to_video(const sensor_msgs::msg::Image::SharedPtr &msg) {
265    cv::Mat image;
266    try {
267      cv_bridge::CvImageConstPtr cvp = cv_bridge::toCvShare(msg);
268      image = cvp->image;
269      if (msg->encoding == "rgb8") {
270        cv::cvtColor(image, image, cv::COLOR_RGB2BGR);
271      } else {
272        RCLCPP_WARN(get_logger(), "image encoding not expected: %s",
273                    msg->encoding.c_str());
274        return;
275      }
276
277      if (need_rotation_) {
278        image = rotate_image_90_ccw(image);
279      }
280
281      if (with_mask_) {
282        image.setTo(cv::Scalar(0, 0, 0), mask_image_ == 0);
283      }
284    } catch (const std::exception &e) {
285      RCLCPP_WARN(get_logger(), "cv_bridge exception: %s", e.what());
286      return;
287    }
288
289    if (!video_writer_.isOpened()) {
290      int fourcc = cv::VideoWriter::fourcc('m', 'p', '4', 'v');
291      double fps = dump_video_fps_;
292      bool ok = video_writer_.open(dump_video_path_, fourcc, fps,
293                                   cv::Size(image.cols, image.rows), true);
294      if (!ok) {
295        RCLCPP_ERROR(get_logger(), "Failed to open video file: %s",
296                     dump_video_path_.c_str());
297        dump_video_path_.clear();
298        return;
299      }
300      RCLCPP_INFO(get_logger(), "VideoWriter started: %s, size=%dx%d, fps=%.1f",
301                  dump_video_path_.c_str(), image.cols, image.rows, fps);
302    }
303    video_writer_.write(image);
304  }
305
306  std::string topic_type_;
307  std::string topic_name_;
308  std::string dump_video_path_;
309  double dump_video_fps_ = 10.0;
310  bool with_mask_;
311  bool auto_rotate_;
312  bool need_rotation_ = false;
313  std::string hw_version_;
314  cv::Mat mask_image_;
315
316  rclcpp::Subscription<sensor_msgs::msg::Image>::SharedPtr sub_image_;
317  rclcpp::Subscription<sensor_msgs::msg::CompressedImage>::SharedPtr
318      sub_compressed_;
319  rclcpp::Subscription<sensor_msgs::msg::CameraInfo>::SharedPtr sub_camerainfo_;
320
321  rclcpp::Time last_print_{0, 0, RCL_ROS_TIME};
322  std::deque<rclcpp::Time> arrivals_;
323
324  cv::VideoWriter video_writer_;
325};
326
327int main(int argc, char **argv) {
328  rclcpp::init(argc, argv);
329  auto node = std::make_shared<HeadRearCameraTopicEcho>();
330  rclcpp::spin(node);
331  rclcpp::shutdown();
332  return 0;
333}

Usage Instructions:

  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.mp4
    
  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.mp4
    
  6. Disable auto rotation compensation (get original image):

    ros2 run examples echo_camera_head_rear --ros-args -p topic_type:=rgb_image -p auto_rotate:=false
    

Application Scenarios:

  • Face recognition and tracking

  • Object detection and recognition

  • Visual SLAM

  • Image processing and computer vision algorithm development

  • Robot visual navigation

6.2.15 Head Touch Sensor Data Subscription

This example uses echo_head_touch_sensor, which subscribes to the /aima/hal/sensor/touch_head topic to receive the robot’s touch sensor data on the head.

Features:

  • The event data would change from “IDLE” to “TOUCH” when robot’s head touched

 1//
 2// Created by agiuser on 2026/1/23.
 3//
 4
 5#include <aimdk_msgs/msg/touch_state.hpp>
 6#include <rclcpp/rclcpp.hpp>
 7#include <string>
 8
 9class TouchStateSubscriber : public rclcpp::Node {
10public:
11  TouchStateSubscriber() : Node("touch_state_subscriber") {
12    subscription_ = this->create_subscription<aimdk_msgs::msg::TouchState>(
13        "/aima/hal/sensor/touch_head", 10,
14        std::bind(&TouchStateSubscriber::touch_callback, this,
15                  std::placeholders::_1));
16
17    RCLCPP_INFO(this->get_logger(), "TouchState subscriber started, listening "
18                                    "to /aima/hal/sensor/touch_head");
19  }
20
21private:
22  void touch_callback(const aimdk_msgs::msg::TouchState::SharedPtr msg) {
23    // print message info
24    std::string event_str = get_event_type_string(msg->event_type);
25    RCLCPP_INFO(this->get_logger(), "Timestamp: %d.%09d, Event: %s (%d)",
26                msg->header.stamp.sec, msg->header.stamp.nanosec,
27                event_str.c_str(), msg->event_type);
28  }
29
30  std::string get_event_type_string(uint8_t event_type) {
31    switch (event_type) {
32    case aimdk_msgs::msg::TouchState::UNKNOWN:
33      return "UNKNOWN";
34    case aimdk_msgs::msg::TouchState::IDLE:
35      return "IDLE";
36    case aimdk_msgs::msg::TouchState::TOUCH:
37      return "TOUCH";
38    default:
39      return "INVALID";
40    }
41  }
42  rclcpp::Subscription<aimdk_msgs::msg::TouchState>::SharedPtr subscription_;
43};
44
45int main(int argc, char *argv[]) {
46  rclcpp::init(argc, argv);
47  auto node = std::make_shared<TouchStateSubscriber>();
48  rclcpp::spin(node);
49  rclcpp::shutdown();
50  return 0;
51}

Usage Instructions:

ros2 run examples echo_head_touch_sensor

Output Example:

...
[INFO] [1779174857.614539691] [touch_state_subscriber]: Timestamp: 1779174857.613006485, Event: IDLE (1)
[INFO] [1779174857.623661452] [touch_state_subscriber]: Timestamp: 1779174857.623052293, Event: TOUCH (2)

6.2.16 LiDAR Data Subscription

This example uses echo_lidar_data, subscribing to the /aima/hal/sensor/lidar_chest_front/ topic to receive LiDAR data, supporting both point cloud data and IMU data types.

Features:

  • Supports LiDAR point cloud data subscription

  • Supports LiDAR IMU data subscription

  • Real-time FPS statistics and data display

  • Configurable topic type selection

  • Detailed output of data field information

Supported Data Types:

  • PointCloud2: LiDAR point cloud data (sensor_msgs/PointCloud2)

  • Imu: LiDAR IMU data (sensor_msgs/Imu)

Technical Implementation:

  • Uses SensorDataQoS configuration (BEST_EFFORT + VOLATILE)

  • Supports parsing and displaying point cloud field information

  • Supports IMU quaternion, angular velocity, and linear acceleration data

  • Provides detailed debugging log output

Application Scenarios:

  • LiDAR data acquisition and analysis

  • Point cloud data processing and visualization

  • Robot navigation and localization

  • SLAM algorithm development

  • Environmental perception and mapping

  1#include <deque>
  2#include <iomanip>
  3#include <memory>
  4#include <rclcpp/rclcpp.hpp>
  5#include <sensor_msgs/msg/imu.hpp>
  6#include <sensor_msgs/msg/point_cloud2.hpp>
  7#include <sstream>
  8#include <string>
  9#include <vector>
 10
 11/**
 12 * @brief Example for subscribing to chest LIDAR data
 13 *
 14 * Supports subscribing to the following topics:
 15 *   1. /aima/hal/sensor/lidar_chest_front/lidar_pointcloud
 16 *      - Data type: sensor_msgs::msg::PointCloud2
 17 *      - frame_id: lidar_chest_front
 18 *      - child_frame_id: /
 19 *      - Content: LIDAR point cloud data
 20 *   2. /aima/hal/sensor/lidar_chest_front/imu
 21 *      - Data type: sensor_msgs::msg::Imu
 22 *      - frame_id: lidar_imu_chest_front
 23 *      - Content: LIDAR IMU data
 24 *
 25 * You can select the topic type to subscribe to using the launch parameter
 26 * --ros-args -p topic_type:=<type>:
 27 *   - pointcloud: Subscribe to LIDAR point cloud
 28 *   - imu: Subscribe to LIDAR IMU
 29 * The default topic_type is pointcloud
 30 */
 31class LidarChestEcho : public rclcpp::Node {
 32public:
 33  LidarChestEcho() : Node("lidar_chest_echo") {
 34    topic_type_ = declare_parameter<std::string>("topic_type", "pointcloud");
 35
 36    // SensorDataQoS: BEST_EFFORT + VOLATILE
 37    auto qos = rclcpp::SensorDataQoS();
 38
 39    if (topic_type_ == "pointcloud") {
 40      topic_name_ = "/aima/hal/sensor/lidar_chest_front/lidar_pointcloud";
 41      sub_pointcloud_ = create_subscription<sensor_msgs::msg::PointCloud2>(
 42          topic_name_, qos,
 43          std::bind(&LidarChestEcho::cb_pointcloud, this,
 44                    std::placeholders::_1));
 45      RCLCPP_INFO(get_logger(), "✅ Subscribing LIDAR PointCloud2: %s",
 46                  topic_name_.c_str());
 47    } else if (topic_type_ == "imu") {
 48      topic_name_ = "/aima/hal/sensor/lidar_chest_front/imu";
 49      sub_imu_ = create_subscription<sensor_msgs::msg::Imu>(
 50          topic_name_, qos,
 51          std::bind(&LidarChestEcho::cb_imu, this, std::placeholders::_1));
 52      RCLCPP_INFO(get_logger(), "✅ Subscribing LIDAR IMU: %s",
 53                  topic_name_.c_str());
 54    } else {
 55      RCLCPP_ERROR(get_logger(), "Unknown topic_type: %s", topic_type_.c_str());
 56      throw std::runtime_error("Unknown topic_type");
 57    }
 58  }
 59
 60private:
 61  // PointCloud2 callback
 62  void cb_pointcloud(const sensor_msgs::msg::PointCloud2::SharedPtr msg) {
 63    update_arrivals();
 64
 65    if (should_print()) {
 66      std::ostringstream oss;
 67      oss << std::fixed << std::boolalpha << std::setfill('0');
 68      oss << "🟢 LIDAR PointCloud2 received\n"
 69          << "  • frame_id:        " << msg->header.frame_id << "\n"
 70          << "  • stamp (sec):     " << msg->header.stamp.sec << "."
 71          << std::setw(9) << msg->header.stamp.nanosec << "\n"
 72          << "  • width x height:  " << msg->width << " x " << msg->height
 73          << "\n"
 74          << "  • point_step:      " << msg->point_step << "\n"
 75          << "  • row_step:        " << msg->row_step << "\n"
 76          << "  • fields:          ";
 77      for (const auto &f : msg->fields)
 78        oss << f.name << "(" << (int)f.datatype << ") ";
 79      oss << "\n  • is_bigendian:    " << msg->is_bigendian
 80          << "\n  • is_dense:        " << msg->is_dense
 81          << "\n  • data size:       " << msg->data.size()
 82          << std::setprecision(1) // fps precision .1f
 83          << "\n  • recv FPS (1s):   " << get_fps();
 84      RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
 85    }
 86  }
 87
 88  // IMU callback
 89  void cb_imu(const sensor_msgs::msg::Imu::SharedPtr msg) {
 90    update_arrivals();
 91
 92    if (should_print()) {
 93      std::ostringstream oss;
 94      oss << std::fixed << std::setfill('0');
 95      oss << "🟢 LIDAR IMU received\n"
 96          << "  • frame_id:        " << msg->header.frame_id << "\n"
 97          << "  • stamp (sec):     " << msg->header.stamp.sec << "."
 98          << std::setw(9) << msg->header.stamp.nanosec << "\n"
 99          << std::setprecision(6) // data precision .6f
100          << "  • orientation:     [" << msg->orientation.x << ", "
101          << msg->orientation.y << ", " << msg->orientation.z << ", "
102          << msg->orientation.w << "]\n"
103          << "  • angular_velocity:[" << msg->angular_velocity.x << ", "
104          << msg->angular_velocity.y << ", " << msg->angular_velocity.z << "]\n"
105          << "  • linear_accel:    [" << msg->linear_acceleration.x << ", "
106          << msg->linear_acceleration.y << ", " << msg->linear_acceleration.z
107          << "]\n"
108          << std::setprecision(1) // fps precision .1f
109          << "  • recv FPS (1s):   " << get_fps();
110      RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
111    }
112  }
113
114  // Update FPS statistics
115  void update_arrivals() {
116    const rclcpp::Time now = this->get_clock()->now();
117    arrivals_.push_back(now);
118    while (!arrivals_.empty() && (now - arrivals_.front()).seconds() > 1.0) {
119      arrivals_.pop_front();
120    }
121  }
122  double get_fps() const { return static_cast<double>(arrivals_.size()); }
123
124  // Control print frequency
125  bool should_print() {
126    const rclcpp::Time now = this->get_clock()->now();
127    if ((now - last_print_).seconds() >= 1.0) {
128      last_print_ = now;
129      return true;
130    }
131    return false;
132  }
133
134  // Member variables
135  std::string topic_type_;
136  std::string topic_name_;
137
138  rclcpp::Subscription<sensor_msgs::msg::PointCloud2>::SharedPtr
139      sub_pointcloud_;
140  rclcpp::Subscription<sensor_msgs::msg::Imu>::SharedPtr sub_imu_;
141
142  rclcpp::Time last_print_{0, 0, RCL_ROS_TIME};
143  std::deque<rclcpp::Time> arrivals_;
144};
145
146int main(int argc, char **argv) {
147  rclcpp::init(argc, argv);
148  auto node = std::make_shared<LidarChestEcho>();
149  rclcpp::spin(node);
150  rclcpp::shutdown();
151  return 0;
152}

Usage Instructions:

# Subscribe to LiDAR point cloud data
ros2 run examples echo_lidar_data --ros-args -p topic_type:=pointcloud

# Subscribe to LiDAR IMU data
ros2 run examples echo_lidar_data --ros-args -p topic_type:=imu

Output Example:

...
[INFO] [1779175640.420801950] [lidar_chest_echo]: 🟢 LIDAR PointCloud2 received
  • frame_id:        lidar_chest_front
  • stamp (sec):     1779175640.315327000
  • width x height:  24779 x 1
  • point_step:      32
  • row_step:        792928
  • fields:          x(7) y(7) z(7) intensity(7) ring(4) timestamp(8)
  • is_bigendian:    false
  • is_dense:        true
  • data size:       792928
  • recv FPS (1s):   11.0

6.2.17 IMU Data Subscription

This example uses echo_imu_data to subscribe to IMU topics for the robot’s orientation, angular velocity, and linear acceleration data, and prints the covariance diagonals of each field. It subscribes to the chest IMU by default and can be switched to the torso IMU, LiDAR IMU, or depth-camera IMU via a parameter.

Features:

  • Subscribes to sensor_msgs/Imu data, outputting orientation quaternion, angular velocity, and linear acceleration

  • Prints the covariance diagonals of orientation/angular velocity/acceleration (-1 means unknown)

  • Measures the receive interval (recv_dt) to observe the actual arrival rate

  • Switch between IMU sources via the imu_topic parameter

Supported Topics:

  • /aima/hal/imu/chest/state: Chest IMU (default)

  • /aima/hal/imu/torso/state: Torso IMU

  • /aima/hal/sensor/lidar_chest_front/imu: LiDAR IMU

  • /aima/hal/sensor/rgbd_head_front/imu: Depth-camera IMU

Technical Implementation:

  • Uses SensorDataQoS (BEST_EFFORT + VOLATILE), consistent with the default QoS for sensor data

 1#include <chrono>
 2#include <iomanip>
 3#include <rclcpp/rclcpp.hpp>
 4#include <sensor_msgs/msg/imu.hpp>
 5#include <string>
 6
 7class ImuPrinter : public rclcpp::Node {
 8public:
 9  ImuPrinter() : rclcpp::Node("imu_printer") {
10    // Allow overriding the topic name via a parameter
11    imu_topic_ = this->declare_parameter<std::string>(
12        "imu_topic",
13        "/aima/hal/imu/chest/state"); // Change to other IMU topics, e.g.
14                                      // waist IMU: /aima/hal/imu/torso/state
15                                      // lidar IMU:
16                                      // /aima/hal/sensor/lidar_chest_front/imu
17
18    auto qos = rclcpp::SensorDataQoS(); // BestEffort / Volatile
19    sub_ = this->create_subscription<sensor_msgs::msg::Imu>(
20        imu_topic_, qos,
21        std::bind(&ImuPrinter::cb, this, std::placeholders::_1));
22
23    RCLCPP_INFO(this->get_logger(), "Subscribing IMU: %s", imu_topic_.c_str());
24  }
25
26private:
27  void cb(const sensor_msgs::msg::Imu::SharedPtr msg) {
28    // Compute receive interval (based on wall time, for observation only)
29    auto now = std::chrono::steady_clock::now();
30    double dt_ms =
31        last_recv_.time_since_epoch().count() == 0
32            ? 0.0
33            : std::chrono::duration<double, std::milli>(now - last_recv_)
34                  .count();
35    last_recv_ = now;
36
37    const auto &q = msg->orientation;
38    const auto &w = msg->angular_velocity;
39    const auto &a = msg->linear_acceleration;
40
41    // Print
42    std::ostringstream oss;
43    oss << std::fixed << std::setprecision(6);
44    oss << "stamp=" << rclcpp::Time(msg->header.stamp).seconds()
45        << "s  frame=" << msg->header.frame_id
46        << "  recv_dt=" << std::setprecision(3) << dt_ms << "ms\n"
47        << std::setprecision(6) << "  orientation (x,y,z,w): [" << q.x << ", "
48        << q.y << ", " << q.z << ", " << q.w << "]\n"
49        << "  angular_velocity (rad/s): [" << w.x << ", " << w.y << ", " << w.z
50        << "]\n"
51        << "  linear_accel (m/s^2): [" << a.x << ", " << a.y << ", " << a.z
52        << "]";
53
54    // Optional: print diagonal covariances (-1 means unknown)
55    auto oc0 = msg->orientation_covariance[0];
56    auto oc4 = msg->orientation_covariance[4];
57    auto oc8 = msg->orientation_covariance[8];
58    auto wc0 = msg->angular_velocity_covariance[0];
59    auto wc4 = msg->angular_velocity_covariance[4];
60    auto wc8 = msg->angular_velocity_covariance[8];
61    auto ac0 = msg->linear_acceleration_covariance[0];
62    auto ac4 = msg->linear_acceleration_covariance[4];
63    auto ac8 = msg->linear_acceleration_covariance[8];
64
65    oss << "\n  cov_diag(ori, ang, acc): [" << oc0 << "," << oc4 << "," << oc8
66        << " | " << wc0 << "," << wc4 << "," << wc8 << " | " << ac0 << ","
67        << ac4 << "," << ac8 << "]";
68
69    RCLCPP_INFO(this->get_logger(), "%s", oss.str().c_str());
70  }
71
72  std::string imu_topic_;
73  rclcpp::Subscription<sensor_msgs::msg::Imu>::SharedPtr sub_;
74  std::chrono::steady_clock::time_point last_recv_{};
75};
76
77int main(int argc, char **argv) {
78  rclcpp::init(argc, argv);
79  rclcpp::spin(std::make_shared<ImuPrinter>());
80  rclcpp::shutdown();
81  return 0;
82}

Usage Instructions:

# Subscribe to the chest IMU (default)
ros2 run examples echo_imu_data

# Subscribe to the torso IMU
ros2 run examples echo_imu_data --ros-args -p imu_topic:=/aima/hal/imu/torso/state

# Subscribe to the LiDAR IMU
ros2 run examples echo_imu_data --ros-args -p imu_topic:=/aima/hal/sensor/lidar_chest_front/imu

Output Example:

[INFO] [1781764206.320174965] [imu_printer]: Subscribing IMU: /aima/hal/imu/chest/state
[INFO] [1781764206.318604000] [imu_printer]: stamp=1781764206.318604s  frame=base_link  recv_dt=2.039ms
  orientation (x,y,z,w): [0.087036, -0.264735, -0.019103, -0.960195]
  angular_velocity (rad/s): [-0.006531, 0.002874, 0.002391]
  linear_accel (m/s^2): [-5.013936, -1.548364, 8.280822]
  cov_diag(ori, ang, acc): [0.000000,0.000000,0.000000 | 0.000000,0.000000,0.000000 | 0.000000,0.000000,0.000000]

6.2.18 Play Video

This example uses play_video. Before running the node, you must upload the video file to the robot’s Interaction Computing Unit (PC3) (you may create a directory there to store videos, e.g. /var/tmp/videos/). Then, modify the video_path in the node program to the path of the video you want to play.

Attention

⚠️ Warning! The Interaction Computing Unit (PC3) is independent from the Development Computing Unit (PC2) where secondary development programs run. Audio and video files must be stored on the Interaction Computing Unit (IP: 10.0.1.42).
Audio and video files (and all parent directories up to root) must be readable by all users (new subdirectory under /var/tmp/ is recommended)

Attention

Before using this feature, run the following command on the Motion Control Computing Unit (PC1) to stop the task_manager module and prevent it from competing with your program for display control:

aima em stop-app task_manager

Function Description: By calling the PlayVideo service, the robot can play a video file located at a specified path on its screen. Ensure the video file has been uploaded to the Interaction Computing Unit, otherwise playback will fail.

💡 Tip: How to prepare a video file

You can use the echo_camera_rgbd example to record the robot’s depth camera RGB stream as an .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 <algorithm>
  5#include <cctype>
  6#include <chrono>
  7#include <iostream>
  8#include <memory>
  9#include <signal.h>
 10#include <string>
 11
 12// Global variable used for signal handling
 13std::shared_ptr<rclcpp::Node> g_node = nullptr;
 14
 15// Signal handler function
 16void signal_handler(int signal) {
 17  if (g_node) {
 18    RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
 19                signal);
 20    g_node.reset();
 21  }
 22  rclcpp::shutdown();
 23  exit(signal);
 24}
 25
 26class PlayVideoClient : public rclcpp::Node {
 27public:
 28  PlayVideoClient() : Node("play_video_client") {
 29    client_ = this->create_client<aimdk_msgs::srv::PlayVideo>(
 30        "/aimdk_5Fmsgs/srv/PlayVideo");
 31    RCLCPP_INFO(this->get_logger(), "✅ PlayVideo client node started.");
 32
 33    // Wait for the service to become available
 34    while (!client_->wait_for_service(std::chrono::seconds(2))) {
 35      RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
 36    }
 37    RCLCPP_INFO(this->get_logger(),
 38                "🟢 Service available, ready to send request.");
 39  }
 40
 41  bool send_request(const std::string &video_path, uint8_t mode,
 42                    int32_t priority) {
 43    try {
 44      auto request = std::make_shared<aimdk_msgs::srv::PlayVideo::Request>();
 45
 46      request->video_path = video_path;
 47      request->mode = mode;
 48      request->priority = priority;
 49
 50      RCLCPP_INFO(this->get_logger(),
 51                  "📨 Sending request to play video: mode=%hhu video=%s", mode,
 52                  video_path.c_str());
 53
 54      const std::chrono::milliseconds timeout(250);
 55      for (int i = 0; i < 8; i++) {
 56        request->header.header.stamp = this->now();
 57        auto future = client_->async_send_request(request);
 58        auto retcode = rclcpp::spin_until_future_complete(shared_from_this(),
 59                                                          future, timeout);
 60        if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
 61          // retry as remote peer is NOT handled well by ROS
 62          RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
 63          continue;
 64        }
 65        // future.done
 66        auto response = future.get();
 67        if (response->success) {
 68          RCLCPP_INFO(this->get_logger(),
 69                      "✅ Request to play video recorded successfully: %s",
 70                      response->message.c_str());
 71          return true;
 72        } else {
 73          RCLCPP_ERROR(this->get_logger(),
 74                       "❌ Failed to record play-video request: %s",
 75                       response->message.c_str());
 76          return false;
 77        }
 78      }
 79      RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
 80      return false;
 81    } catch (const std::exception &e) {
 82      RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
 83      return false;
 84    }
 85  }
 86
 87private:
 88  rclcpp::Client<aimdk_msgs::srv::PlayVideo>::SharedPtr client_;
 89};
 90
 91int main(int argc, char **argv) {
 92  try {
 93    rclcpp::init(argc, argv);
 94
 95    // Set up signal handlers
 96    signal(SIGINT, signal_handler);
 97    signal(SIGTERM, signal_handler);
 98
 99    std::string default_video_path =
100        "/agibot/software/face_ui/bin/app/aarch64-generic/assets/mp4/"
101        "emoji_x2_sleepy.mp4";
102    std::string video_path;
103    std::cout << "Enter video path (default: " << default_video_path << "): ";
104    std::getline(std::cin, video_path);
105    // Trim leading/trailing whitespace (parity with Python's .strip())
106    auto not_space = [](int c) { return !std::isspace(c); };
107    video_path.erase(
108        video_path.begin(),
109        std::find_if(video_path.begin(), video_path.end(), not_space));
110    video_path.erase(
111        std::find_if(video_path.rbegin(), video_path.rend(), not_space).base(),
112        video_path.end());
113    if (video_path.empty()) {
114      video_path = default_video_path;
115    }
116    int32_t priority = 5;
117    int mode = 2; // Loop playback
118    std::cout << "Enter video play mode (1: once, 2: loop): ";
119    std::cin >> mode;
120    if (mode < 1 || mode > 2) {
121      RCLCPP_ERROR(rclcpp::get_logger("main"), "Invalid play mode: %d", mode);
122      rclcpp::shutdown();
123      return 1;
124    }
125
126    g_node = std::make_shared<PlayVideoClient>();
127    auto client = std::dynamic_pointer_cast<PlayVideoClient>(g_node);
128
129    if (client) {
130      client->send_request(video_path, mode, priority);
131    }
132
133    // Clean up resources
134    g_node.reset();
135    rclcpp::shutdown();
136
137    return 0;
138  } catch (const std::exception &e) {
139    RCLCPP_ERROR(rclcpp::get_logger("main"),
140                 "Program exited with exception: %s", e.what());
141    return 1;
142  }
143}

6.2.19 Audio File Playback

This example uses play_audio, enabling playback of specified audio files, supporting WAV (PCM-encoded) and RAW PCM formats. Other formats such as MP3 are not supported.

Features:

  • Supports WAV (PCM-encoded) and RAW PCM playback; other formats such as MP3 are not supported

  • Supports priority control with configurable playback priority

  • Supports custom file paths and playback parameters

  • Provides comprehensive error handling and status feedback

Technical Implementation:

  • Uses the PlayAudioFile service for audio file playback

  • Supports priority settings (priority: 1–10, priority_weight: 1–100)

  • Provides detailed playback status feedback

Application Scenarios:

  • Audio file playback and media control

  • Voice prompts and sound effect playback

  • Multimedia application development

  • Robot interaction audio feedback

Attention

⚠️ Warning! The Interaction Computing Unit (PC3) is independent from the Development Computing Unit (PC2) where secondary development programs run. Audio and video files must be stored on the Interaction Computing Unit (IP: 10.0.1.42).
Audio and video files (and all parent directories up to root) must be readable by all users (new subdirectory under /var/tmp/ is recommended)

  1#include <aimdk_msgs/msg/common_state.hpp>
  2#include <aimdk_msgs/srv/play_audio_file.hpp>
  3#include <algorithm>
  4#include <cctype>
  5#include <filesystem>
  6#include <iostream>
  7#include <rclcpp/rclcpp.hpp>
  8#include <string>
  9
 10using PlayAudioFile = aimdk_msgs::srv::PlayAudioFile;
 11
 12int main(int argc, char **argv) {
 13  rclcpp::init(argc, argv);
 14  auto node = rclcpp::Node::make_shared("play_audio_client_min");
 15
 16  // 1) Service name
 17  const std::string service_name = "/aimdk_5Fmsgs/srv/PlayAudioFile";
 18  auto client = node->create_client<PlayAudioFile>(service_name);
 19
 20  RCLCPP_INFO(node->get_logger(), "✅ PlayAudio client node created.");
 21
 22  // 2) Input file path (prompt user if not provided as argument)
 23  std::string default_file = "/agibot/data/var/hal_audio/file/bingo.wav";
 24  std::string file_name;
 25
 26  if (argc > 1) {
 27    file_name = argv[1];
 28  } else {
 29    std::cout << "Enter audio file path to play (default: " << default_file
 30              << "): ";
 31    if (!std::getline(std::cin, file_name) || !rclcpp::ok()) {
 32      std::cout << std::endl;
 33      rclcpp::shutdown();
 34      return 1;
 35    }
 36    // Trim leading/trailing whitespace (parity with Python's .strip())
 37    auto not_space = [](int c) { return !std::isspace(c); };
 38    file_name.erase(
 39        file_name.begin(),
 40        std::find_if(file_name.begin(), file_name.end(), not_space));
 41    file_name.erase(
 42        std::find_if(file_name.rbegin(), file_name.rend(), not_space).base(),
 43        file_name.end());
 44    if (file_name.empty()) {
 45      file_name = default_file;
 46    }
 47  }
 48
 49  // 3) Build the request
 50  // Check if Ctrl+C was pressed during user input
 51  if (!rclcpp::ok()) {
 52    rclcpp::shutdown();
 53    return 1;
 54  }
 55
 56  auto req = std::make_shared<PlayAudioFile::Request>();
 57  req->file.pkg_name = "demo_client";
 58  req->file.file_name = std::filesystem::path(file_name).filename().string();
 59  req->file.file_path =
 60      std::filesystem::path(file_name).parent_path().string() + "/";
 61  req->file.priority = 8;
 62  req->file.priority_weight = 0;
 63
 64  // 4) Wait for service
 65  while (!client->wait_for_service(std::chrono::seconds(2))) {
 66    if (!rclcpp::ok()) {
 67      RCLCPP_ERROR(node->get_logger(),
 68                   "Interrupted while waiting for the service. Exiting.");
 69      rclcpp::shutdown();
 70      return 1;
 71    }
 72    RCLCPP_INFO(node->get_logger(), "⏳ Service unavailable, waiting...");
 73  }
 74  RCLCPP_INFO(node->get_logger(),
 75              "🟢 Service available, ready to send request.");
 76
 77  RCLCPP_INFO(node->get_logger(), "📨 Sending request to play audio: %s",
 78              file_name.c_str());
 79
 80  rclcpp::FutureReturnCode rc = rclcpp::FutureReturnCode::TIMEOUT;
 81  std::shared_future<PlayAudioFile::Response::SharedPtr> future;
 82  for (int i = 0; i < 5; ++i) {
 83    req->request.header.stamp = node->now();
 84    future = client->async_send_request(req);
 85    rc = rclcpp::spin_until_future_complete(node, future,
 86                                            std::chrono::seconds(1));
 87    if (rc == rclcpp::FutureReturnCode::SUCCESS) {
 88      break;
 89    }
 90    if (rc == rclcpp::FutureReturnCode::INTERRUPTED) {
 91      // Check if Ctrl+C was pressed during spin_until_future_complete
 92      if (rclcpp::ok()) {
 93        RCLCPP_WARN(node->get_logger(), "Interrupted while waiting");
 94      }
 95      rclcpp::shutdown();
 96      return 1;
 97    }
 98    // retry as remote peer is NOT handled well by ROS
 99    RCLCPP_INFO(node->get_logger(), "trying ... [%d]", i);
100  }
101
102  if (rc != rclcpp::FutureReturnCode::SUCCESS) {
103    // Check if Ctrl+C was pressed, avoid using node after shutdown
104    if (rclcpp::ok()) {
105      RCLCPP_ERROR(node->get_logger(),
106                   "❌ Service call not completed or timed out.");
107    }
108    rclcpp::shutdown();
109    return 1;
110  }
111
112  // 5) Handle response (success is in reponse.status)
113  try {
114    const auto resp = future.get();
115    bool success = resp->reponse.status.value ==
116                   aimdk_msgs::msg::CommonState::SUCCESS; // SUCCESS (1)
117
118    if (success) {
119      RCLCPP_INFO(node->get_logger(),
120                  "✅ Request to play audio file recorded successfully.");
121    } else {
122      // FAILURE(2): focus-apply failed; INVALID(5): invalid params;
123      // other: see CommonState for details.
124      const char *reason = "unknown";
125      switch (resp->reponse.status.value) {
126      case aimdk_msgs::msg::CommonState::FAILURE: // FAILURE (2)
127        reason = "focus apply failed";
128        break;
129      case aimdk_msgs::msg::CommonState::INVALID: // INVALID (5)
130        reason = "invalid params";
131        break;
132      default:
133        break;
134      }
135      RCLCPP_ERROR(node->get_logger(),
136                   "❌ Failed to record play-audio request (status=%d, %s): %s",
137                   resp->reponse.status.value, reason,
138                   resp->reponse.message.c_str());
139    }
140  } catch (const std::exception &e) {
141    RCLCPP_ERROR(node->get_logger(), "Call exception: %s", e.what());
142  }
143
144  rclcpp::shutdown();
145  return 0;
146}

Usage Instructions:

# Play default audio file
ros2 run examples play_audio

# Play a specified audio file
# Replace /path/to/your/audio_file.wav with the actual file path on the interaction board
ros2 run examples play_audio /path/to/your/audio_file.wav

# Play TTS cached audio file
ros2 run examples play_audio /agibot/data/var/hal_audio/file/bingo.wav

Output Example:

[INFO] [1779184008.888874978] [play_audio_client_min]: ✅ PlayAudio client node created.
[INFO] [1779184008.895467698] [play_audio_client_min]: 🟢 Service available, ready to send request.
[INFO] [1779184008.897450608] [play_audio_client_min]: 📨 Sending request to play audio: /agibot/data/var/hal_audio/file/bingo.wav
[INFO] [1779184008.962054291] [play_audio_client_min]: ✅ Request to play audio file recorded successfully.

Notes:

  • Ensure the audio file path is correct and the file exists

  • Supported file formats: WAV (PCM-encoded), RAW PCM. Other formats such as MP3 are not supported

  • Priority settings affect playback queue order

6.2.20 TTS (Text-to-Speech)

This example uses play_tts, enabling the robot to speak the provided text. Users can input any text depending on the scenario.

Features:

  • Supports command-line arguments and interactive input

  • Includes comprehensive service availability checks and error handling

  • Supports priority control and interruption mechanism

  • Provides detailed playback status feedback

Core Code

  1#include <aimdk_msgs/msg/tts_priority_level.hpp>
  2#include <aimdk_msgs/srv/play_tts.hpp>
  3#include <chrono>
  4#include <iostream>
  5#include <rclcpp/rclcpp.hpp>
  6#include <string>
  7
  8using PlayTTS = aimdk_msgs::srv::PlayTts;
  9
 10int main(int argc, char **argv) {
 11  rclcpp::init(argc, argv);
 12  auto node = rclcpp::Node::make_shared("play_tts_client_min");
 13
 14  const std::string service_name = "/aimdk_5Fmsgs/srv/PlayTts";
 15  auto client = node->create_client<PlayTTS>(service_name);
 16
 17  RCLCPP_INFO(node->get_logger(), "✅ PlayTts client node created.");
 18
 19  // Wait for the service to become available
 20  while (!client->wait_for_service(std::chrono::seconds(2))) {
 21    if (!rclcpp::ok()) {
 22      rclcpp::shutdown();
 23      return 1;
 24    }
 25    RCLCPP_INFO(node->get_logger(), "⏳ Service unavailable, waiting...");
 26  }
 27  RCLCPP_INFO(node->get_logger(),
 28              "🟢 Service available, ready to send request.");
 29
 30  // Get text to speak
 31  std::string tts_text;
 32  if (argc > 1) {
 33    tts_text = argv[1];
 34  } else {
 35    std::cout << "Enter text to speak: ";
 36    if (!std::getline(std::cin, tts_text) || !rclcpp::ok()) {
 37      std::cout << std::endl;
 38      rclcpp::shutdown();
 39      return 1;
 40    }
 41    if (tts_text.empty()) {
 42      tts_text = "Hello, I am AgiBot X2.";
 43    }
 44  }
 45
 46  if (!rclcpp::ok()) {
 47    rclcpp::shutdown();
 48    return 1;
 49  }
 50
 51  auto req = std::make_shared<PlayTTS::Request>();
 52  req->tts_req.text = tts_text;
 53  req->tts_req.domain = "demo_client"; // Required: identifies the caller
 54  req->tts_req.trace_id =
 55      "demo"; // Optional: request identifier for the TTS request
 56  req->tts_req.is_interrupted =
 57      true; // Required: whether to interrupt same-priority playback
 58  req->tts_req.priority_weight = 0;
 59  req->tts_req.priority_level.value =
 60      aimdk_msgs::msg::TtsPriorityLevel::INTERACTION_L6; // L6 (6)
 61
 62  RCLCPP_INFO(node->get_logger(), "📨 Sending request to play tts: text=%s",
 63              tts_text.c_str());
 64
 65  std::shared_future<PlayTTS::Response::SharedPtr> future;
 66  rclcpp::FutureReturnCode rc = rclcpp::FutureReturnCode::TIMEOUT;
 67  for (int i = 0; i < 8; ++i) {
 68    req->header.header.stamp = node->now();
 69    future = client->async_send_request(req);
 70    rc = rclcpp::spin_until_future_complete(node, future,
 71                                            std::chrono::milliseconds(250));
 72    if (rc == rclcpp::FutureReturnCode::SUCCESS) {
 73      break;
 74    }
 75    if (!rclcpp::ok()) {
 76      rclcpp::shutdown();
 77      return 1;
 78    }
 79    // retry as remote peer is NOT handled well by ROS
 80    RCLCPP_INFO(node->get_logger(), "trying ... [%d]", i);
 81  }
 82
 83  if (rc != rclcpp::FutureReturnCode::SUCCESS) {
 84    if (rclcpp::ok()) {
 85      RCLCPP_ERROR(node->get_logger(),
 86                   "❌ Service call not completed or timed out.");
 87    }
 88    rclcpp::shutdown();
 89    return 1;
 90  }
 91
 92  const auto resp = future.get();
 93  if (resp->tts_resp.is_success) {
 94    RCLCPP_INFO(node->get_logger(), "✅ TTS sent successfully.");
 95  } else {
 96    RCLCPP_ERROR(node->get_logger(),
 97                 "❌ Failed to send TTS: error_code=%u, error_message=%s",
 98                 resp->tts_resp.error_code,
 99                 resp->tts_resp.error_message.c_str());
100  }
101
102  rclcpp::shutdown();
103  return 0;
104}

Usage Instructions

# Use command-line arguments to speak text (recommended)
ros2 run examples play_tts "Hello, I am the AgiBot X2 robot"

# Or run without arguments; the program will prompt for input
ros2 run examples play_tts

Output Example

[INFO] [1779181543.136645281] [play_tts_client_min]: ✅ PlayTts client node created.
[INFO] [1779181543.137467698] [play_tts_client_min]: 🟢 Service available, ready to send request.
[INFO] [1779181543.138450608] [play_tts_client_min]: 📨 Sending request to play tts: text=Hello
[INFO] [1779181543.146077262] [play_tts_client_min]: ✅ TTS sent successfully.

Notes

  • Ensure the TTS service is running properly

  • Supports both Chinese and English text-to-speech

  • Priority settings affect playback queue order

  • Interruption feature can stop the currently playing speech

Interface Reference

  • Service: /aimdk_5Fmsgs/srv/PlayTts

  • Message: aimdk_msgs/srv/PlayTts

6.2.21 Microphone Audio Reception

This example uses mic_receiver, subscribing to the /agent/process_audio_output topic to receive the robot’s noise-reduced audio stream. It supports both internal and external microphone audio streams, and automatically saves complete speech segments as PCM files based on VAD (Voice Activity Detection) states.

Features:

  • Automatically detects speech start, processing, and end based on VAD state

  • Automatically saves complete speech segments as PCM files (mono, 16 kHz sample rate, S16LE format)

  • Stores recordings categorized by timestamp and audio stream

  • Supports audio duration calculation and statistical output

Application Scenarios:

  • Speech recognition and speech processing

  • Audio data acquisition and analysis

  • Real-time speech monitoring

  • Audio quality evaluation

  • Multi-microphone array data processing

  1#include <aimdk_msgs/msg/audio_vad_state_type.hpp>
  2#include <aimdk_msgs/msg/processed_audio_output.hpp>
  3#include <chrono>
  4#include <ctime>
  5#include <filesystem>
  6#include <fstream>
  7#include <iomanip>
  8#include <rclcpp/rclcpp.hpp>
  9#include <sstream>
 10#include <string>
 11#include <unordered_map>
 12#include <vector>
 13
 14namespace fs = std::filesystem;
 15
 16class AudioSubscriber : public rclcpp::Node {
 17public:
 18  AudioSubscriber() : rclcpp::Node("audio_subscriber") {
 19    // Audio buffers, stored separately by stream_id
 20    // stream_id -> buffer
 21    audio_buffers_ = {};
 22    recording_state_ = {};
 23
 24    audio_output_dir_ = "audio_recordings";
 25    fs::create_directories(audio_output_dir_);
 26
 27    // Note: deep queue to avoid missing data in a burst at start of VAD.
 28    auto qos = rclcpp::QoS(
 29        rclcpp::QoSInitialization::from_rmw(rmw_qos_profile_sensor_data));
 30    qos.keep_last(500).best_effort();
 31
 32    subscription_ =
 33        this->create_subscription<aimdk_msgs::msg::ProcessedAudioOutput>(
 34            "/agent/process_audio_output", qos,
 35            std::bind(&AudioSubscriber::audio_callback, this,
 36                      std::placeholders::_1));
 37
 38    RCLCPP_INFO(this->get_logger(),
 39                "Start subscribing to noise-suppressed audio data...");
 40  }
 41
 42private:
 43  void
 44  audio_callback(const aimdk_msgs::msg::ProcessedAudioOutput::SharedPtr msg) {
 45    try {
 46      uint32_t stream_id = msg->stream_id;
 47      uint8_t vad_state = msg->audio_vad_state.value;
 48      const std::vector<uint8_t> &audio_data = msg->audio_data;
 49
 50      using V = aimdk_msgs::msg::AudioVadStateType;
 51      static const std::unordered_map<uint8_t, std::string> vad_state_names = {
 52          {V::AUDIO_VAD_STATE_BEGIN, "Speech start"},
 53          {V::AUDIO_VAD_STATE_PROCESSING, "Speech in progress"},
 54          {V::AUDIO_VAD_STATE_END, "Speech end"}};
 55      static const std::unordered_map<uint32_t, std::string> stream_names = {
 56          {1, "Built-in microphone"}, {2, "External microphone"}};
 57
 58      RCLCPP_INFO(this->get_logger(),
 59                  "Received audio data: stream_id=%u, vad_state=%u(%s), "
 60                  "audio_size=%zu bytes",
 61                  stream_id, vad_state,
 62                  vad_state_names.count(vad_state)
 63                      ? vad_state_names.at(vad_state).c_str()
 64                      : "Unknown state",
 65                  audio_data.size());
 66
 67      handle_vad_state(stream_id, vad_state, audio_data);
 68    } catch (const std::exception &e) {
 69      RCLCPP_ERROR(this->get_logger(), "Error processing audio message: %s",
 70                   e.what());
 71    }
 72  }
 73
 74  void handle_vad_state(uint32_t stream_id, uint8_t vad_state,
 75                        const std::vector<uint8_t> &audio_data) {
 76    // Initialize the buffer for this stream_id (if it does not exist)
 77    if (audio_buffers_.count(stream_id) == 0) {
 78      audio_buffers_[stream_id] = std::vector<uint8_t>();
 79      recording_state_[stream_id] = false;
 80    }
 81
 82    using V = aimdk_msgs::msg::AudioVadStateType;
 83    static const std::unordered_map<uint8_t, std::string> vad_state_names = {
 84        {V::AUDIO_VAD_STATE_BEGIN, "Speech start"},
 85        {V::AUDIO_VAD_STATE_PROCESSING, "Speech in progress"},
 86        {V::AUDIO_VAD_STATE_END, "Speech end"}};
 87    static const std::unordered_map<uint32_t, std::string> stream_names = {
 88        {1, "Built-in microphone"}, {2, "External microphone"}};
 89
 90    RCLCPP_INFO(this->get_logger(), "[%s] VAD state: %s audio: %zu bytes",
 91                stream_names.count(stream_id)
 92                    ? stream_names.at(stream_id).c_str()
 93                    : ("Unknown Stream " + std::to_string(stream_id)).c_str(),
 94                vad_state_names.count(vad_state)
 95                    ? vad_state_names.at(vad_state).c_str()
 96                    : ("Unknown State" + std::to_string(vad_state)).c_str(),
 97                audio_data.size());
 98
 99    if (vad_state == V::AUDIO_VAD_STATE_BEGIN) { // BEGIN (1)
100      RCLCPP_INFO(this->get_logger(), "🎤 Speech start detected");
101      if (recording_state_[stream_id] == false) {
102        audio_buffers_[stream_id].clear();
103        recording_state_[stream_id] = true;
104      }
105      if (!audio_data.empty()) {
106        audio_buffers_[stream_id].insert(audio_buffers_[stream_id].end(),
107                                         audio_data.begin(), audio_data.end());
108      }
109
110    } else if (vad_state == V::AUDIO_VAD_STATE_PROCESSING) { // PROCESSING (2)
111      RCLCPP_INFO(this->get_logger(), "🔄 Speech in progress...");
112      if (recording_state_[stream_id] && !audio_data.empty()) {
113        audio_buffers_[stream_id].insert(audio_buffers_[stream_id].end(),
114                                         audio_data.begin(), audio_data.end());
115      }
116
117    } else if (vad_state == V::AUDIO_VAD_STATE_END) { // END (3)
118      RCLCPP_INFO(this->get_logger(), "✅ Speech end");
119      if (recording_state_[stream_id] && !audio_data.empty()) {
120        audio_buffers_[stream_id].insert(audio_buffers_[stream_id].end(),
121                                         audio_data.begin(), audio_data.end());
122      }
123      if (recording_state_[stream_id] && !audio_buffers_[stream_id].empty()) {
124        save_audio_segment(audio_buffers_[stream_id], stream_id);
125      }
126      recording_state_[stream_id] = false;
127
128      // AUDIO_VAD_STATE_UNKNOWN
129    } else {
130      if (recording_state_[stream_id]) {
131        RCLCPP_INFO(this->get_logger(), "⏹️ Reset recording state");
132        recording_state_[stream_id] = false;
133      }
134    }
135
136    // Output the current buffer status.
137    size_t buffer_size = audio_buffers_[stream_id].size();
138    bool recording = recording_state_[stream_id];
139    RCLCPP_DEBUG(this->get_logger(),
140                 "[Stream %u] Buffer size: %zu bytes, Recording state: %s",
141                 stream_id, buffer_size, recording ? "true" : "false");
142  }
143
144  void save_audio_segment(const std::vector<uint8_t> &audio_data,
145                          uint32_t stream_id) {
146    if (audio_data.empty())
147      return;
148
149    // Get the current timestamp.
150    auto now = std::chrono::system_clock::now();
151    std::time_t t = std::chrono::system_clock::to_time_t(now);
152    auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
153                  now.time_since_epoch()) %
154              1000;
155
156    std::ostringstream oss;
157    oss << std::put_time(std::localtime(&t), "%Y%m%d_%H%M%S") << "_"
158        << std::setw(3) << std::setfill('0') << ms.count();
159
160    // Create a subdirectory by stream_id.
161    fs::path stream_dir =
162        fs::path(audio_output_dir_) / ("stream_" + std::to_string(stream_id));
163    fs::create_directories(stream_dir);
164
165    static const std::unordered_map<uint32_t, std::string> stream_names = {
166        {1, "internal_mic"}, {2, "external_mic"}};
167    std::string stream_name = stream_names.count(stream_id)
168                                  ? stream_names.at(stream_id)
169                                  : ("stream_" + std::to_string(stream_id));
170    std::string filename = stream_name + "_" + oss.str() + ".pcm";
171    fs::path filepath = stream_dir / filename;
172
173    try {
174      std::ofstream ofs(filepath, std::ios::binary);
175      ofs.write(reinterpret_cast<const char *>(audio_data.data()),
176                audio_data.size());
177      ofs.close();
178      RCLCPP_INFO(this->get_logger(),
179                  "Audio segment saved: %s (size: %zu bytes)", filepath.c_str(),
180                  audio_data.size());
181
182      // Record audio file duration (assuming 16kHz, 16-bit, mono)
183      int sample_rate = 16000;
184      int bits_per_sample = 16;
185      int channels = 1;
186      int bytes_per_sample = bits_per_sample / 8;
187      size_t total_samples = audio_data.size() / (bytes_per_sample * channels);
188      double duration_seconds =
189          static_cast<double>(total_samples) / sample_rate;
190
191      RCLCPP_INFO(this->get_logger(), "Audio duration: %.2f s (%zu samples)",
192                  duration_seconds, total_samples);
193    } catch (const std::exception &e) {
194      RCLCPP_ERROR(this->get_logger(), "Failed to save audio file: %s",
195                   e.what());
196    }
197  }
198
199  // Member variables
200  std::unordered_map<uint32_t, std::vector<uint8_t>> audio_buffers_;
201  std::unordered_map<uint32_t, bool> recording_state_;
202  std::string audio_output_dir_;
203  rclcpp::Subscription<aimdk_msgs::msg::ProcessedAudioOutput>::SharedPtr
204      subscription_;
205};
206
207int main(int argc, char **argv) {
208  rclcpp::init(argc, argv);
209  auto node = std::make_shared<AudioSubscriber>();
210  RCLCPP_INFO(
211      node->get_logger(),
212      "Listening for noise-suppressed audio data, press Ctrl+C to exit...");
213  rclcpp::spin(node);
214  rclcpp::shutdown();
215  return 0;
216}

Usage Instructions:

  1. Run the program:

    # Build
    colcon build --packages-select examples
    
    # Subscribe to GNSS data
    ros2 run examples echo_gnss_data
    

    Press Ctrl+C to safely exit; the program automatically handles the start and end of audio streams.

  2. Play saved PCM file:

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

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

      • stream_1/: Built-in microphone audio

      • stream_2/: External microphone audio

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

      • internal_mic_20250909_133649_738.pcm (built-in microphone)

      • external_mic_20250909_133650_123.pcm (external microphone)

  3. Play saved PCM file:

    # Play built-in microphone recording
    aplay -r 16000 -f S16_LE -c 1 audio_recordings/stream_1/internal_mic_20250909_133649_738.pcm
    

    Parameter explanation:

    • -r 16000 # Sampling rate 16 kHz

    • -f S16_LE # 16-bit little-endian format

    • -c 1 # Mono. You may also use other audio players (e.g., Audacity) to import and play raw PCM files.

  4. Convert to WAV format (optional):

    # Use ffmpeg to convert to WAV format
    ffmpeg -f s16le -ar 16000 -ac 1 -i external_mic_20250909_133649_738.pcm output.wav
    

Output Example:

Normal startup and operation:

[INFO] [1779195658.978174026] [audio_subscriber]: Start subscribing to noise-suppressed audio data...
[INFO] [1779195658.978795319] [audio_subscriber]: Listening for noise-suppressed audio data, press Ctrl+C to exit...

Speech start detected:

[INFO] [1779195676.331873180] [audio_subscriber]: Received audio data: stream_id=1, vad_state=1(Speech start), audio_size=2560 bytes
[INFO] [1779195676.332029112] [audio_subscriber]: [Built-in microphone] VAD state: Speech start audio: 2560 bytes
[INFO] [1779195676.332061655] [audio_subscriber]: 🎤 Speech start detected

Speech processing:

[INFO] [1779195676.332590408] [audio_subscriber]: Received audio data: stream_id=1, vad_state=2(Speech in progress), audio_size=18560 bytes
[INFO] [1779195676.332690053] [audio_subscriber]: [Built-in microphone] VAD state: Speech in progress audio: 18560 bytes
[INFO] [1779195676.332720229] [audio_subscriber]: 🔄 Speech in progress...
...

Speech end and save:

[INFO] [1779195676.881115234] [audio_subscriber]: Received audio data: stream_id=1, vad_state=3(Speech end), audio_size=2560 bytes
[INFO] [1779195676.881210656] [audio_subscriber]: [Built-in microphone] VAD state: Speech end audio: 2560 bytes
[INFO] [1779195676.881234399] [audio_subscriber]: ✅ Speech end
[INFO] [1779195676.882756437] [audio_subscriber]: Audio segment saved: audio_recordings/stream_1/internal_mic_20260519_210116_881.pcm (size: 42880 bytes)
[INFO] [1779195676.882860210] [audio_subscriber]: Audio duration: 1.34 s (21440 samples)

6.2.22 Raw Microphone Audio Stream Receiver

This example uses mic_raw_receiver, which subscribes to the /aima/hal/audio/capture topic to receive raw audio data from the robot and automatically saves it to PCM files split by channel.

Features:

  • Stores recordings categorized by timestamp and audio stream

  • Automatically saves audio channel data as PCM files (mono, 16 kHz sample rate, S16LE format)

  • Supports mic-source switch detection

  1/**
  2 * Microphone raw data receiving example
  3 *
  4 * This example subscribes to the `/aima/hal/audio/capture` topic to receive
  5 * the robot's raw audio data. It supports both the built-in microphone and the
  6 * external microphone audio streams, and automatically saves complete audio
  7 * data as PCM files split by channel.
  8 *
  9 * Features:
 10 * - Automatically saves raw audio as PCM files
 11 * - Stores files categorized by timestamp and mic source
 12 */
 13
 14#include <aimdk_msgs/msg/audio_capture.hpp>
 15#include <chrono>
 16#include <ctime>
 17#include <filesystem>
 18#include <fstream>
 19#include <iomanip>
 20#include <rclcpp/rclcpp.hpp>
 21#include <sstream>
 22#include <string>
 23#include <unordered_map>
 24#include <vector>
 25
 26namespace fs = std::filesystem;
 27
 28class RawAudioSubscriber : public rclcpp::Node {
 29public:
 30  RawAudioSubscriber() : rclcpp::Node("raw_audio_subscriber") {
 31    mic_channels_ = 0;
 32    ref_channels_ = 0;
 33    inited_ = false;
 34
 35    // Create audio output directory named by current timestamp
 36    auto now = std::chrono::system_clock::now();
 37    auto time_t_now = std::chrono::system_clock::to_time_t(now);
 38    auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
 39                  now.time_since_epoch()) %
 40              1000;
 41    std::ostringstream oss;
 42    oss << std::put_time(std::localtime(&time_t_now), "%Y%m%d_%H%M%S_")
 43        << std::setw(3) << std::setfill('0') << ms.count();
 44    audio_output_dir_ = "audio_recordings/" + oss.str();
 45    fs::create_directories(audio_output_dir_);
 46
 47    // Note: deep queue to avoid missing data
 48    auto qos = rclcpp::QoS(rclcpp::KeepLast(500)).best_effort();
 49
 50    subscription_ = this->create_subscription<aimdk_msgs::msg::AudioCapture>(
 51        "/aima/hal/audio/capture", qos,
 52        std::bind(&RawAudioSubscriber::audio_callback, this,
 53                  std::placeholders::_1));
 54
 55    RCLCPP_INFO(this->get_logger(), "Start subscribing to raw audio data...");
 56  }
 57
 58  void run() {
 59    timer_ = this->create_wall_timer(
 60        std::chrono::seconds(1),
 61        std::bind(&RawAudioSubscriber::save_audio_segments, this));
 62    rclcpp::spin(shared_from_this());
 63  }
 64
 65private:
 66  void audio_callback(const aimdk_msgs::msg::AudioCapture::SharedPtr msg) {
 67    if (!inited_) {
 68      mic_channels_ = msg->mic_channels;
 69      ref_channels_ = msg->ref_channels;
 70      inited_ = true;
 71    } else if (mic_channels_ != msg->mic_channels ||
 72               ref_channels_ != msg->ref_channels) {
 73      // quit as MIC switched
 74      RCLCPP_ERROR(
 75          this->get_logger(),
 76          "MIC channels info changed (mic:%u ref:%u) -> (mic:%u ref:%u)",
 77          mic_channels_, ref_channels_, msg->mic_channels, msg->ref_channels);
 78      rclcpp::shutdown();
 79      return;
 80    }
 81
 82    handle_audio_data(msg->data.data);
 83  }
 84
 85  void handle_audio_data(const std::vector<uint8_t> &audio_data) {
 86    // Split S16LE data into channels
 87    int channels =
 88        static_cast<int>(ref_channels_) + static_cast<int>(mic_channels_);
 89    if (channels == 0)
 90      return;
 91
 92    const int bytes_per_channel = 2;
 93    const int unit_size = channels * bytes_per_channel;
 94    size_t num_frames = audio_data.size() / unit_size;
 95
 96    for (size_t k = 0; k < num_frames; ++k) {
 97      for (int i = 0; i < channels; ++i) {
 98        size_t base = k * unit_size + i * bytes_per_channel;
 99        audio_buffers_[i].push_back(audio_data[base]);
100        audio_buffers_[i].push_back(audio_data[base + 1]);
101      }
102    }
103  }
104
105  void save_audio_segments() {
106    RCLCPP_INFO(this->get_logger(), "Flushing cached audio data...");
107    int total =
108        static_cast<int>(mic_channels_) + static_cast<int>(ref_channels_);
109    for (int i = 0; i < total; ++i) {
110      bool is_ref = (i >= static_cast<int>(mic_channels_));
111      save_audio_segment(i, is_ref);
112    }
113  }
114
115  void save_audio_segment(int channel, bool is_ref) {
116    auto &buf = audio_buffers_[channel];
117    if (buf.empty())
118      return;
119
120    std::string channel_type = is_ref ? "ref" : "mic";
121    std::string filename =
122        "channel_" + std::to_string(channel) + "_" + channel_type + ".pcm";
123    fs::path filepath = fs::path(audio_output_dir_) / filename;
124
125    try {
126      // Append mode (equivalent to Python 'ab')
127      std::ofstream ofs(filepath, std::ios::binary | std::ios::app);
128      ofs.write(reinterpret_cast<const char *>(buf.data()), buf.size());
129      ofs.close();
130
131      RCLCPP_INFO(this->get_logger(),
132                  "Audio segment saved: %s appended: %zu bytes",
133                  filepath.c_str(), buf.size());
134      buf.clear();
135    } catch (const std::exception &e) {
136      RCLCPP_ERROR(this->get_logger(), "Failed to save audio file: %s",
137                   e.what());
138    }
139  }
140
141  bool inited_;
142  uint8_t mic_channels_;
143  uint8_t ref_channels_;
144  std::string audio_output_dir_;
145  std::unordered_map<int, std::vector<uint8_t>> audio_buffers_;
146  rclcpp::Subscription<aimdk_msgs::msg::AudioCapture>::SharedPtr subscription_;
147  rclcpp::TimerBase::SharedPtr timer_;
148};
149
150int main(int argc, char **argv) {
151  rclcpp::init(argc, argv);
152  auto node = std::make_shared<RawAudioSubscriber>();
153  RCLCPP_INFO(node->get_logger(),
154              "Listening to raw audio data, press Ctrl+C to exit...");
155  node->run();
156  rclcpp::shutdown();
157  return 0;
158}

Usage Instructions:

  1. Run the program:

    # Build
    colcon build --packages-select examples
    
    # Subscribe to GNSS data
    ros2 run examples echo_gnss_data
    
  2. Play saved PCM file:

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

    • Audio files are stored in a directory named by the program’s startup timestamp:

      • 20250909_133650_123

    • File naming format: channel_{index}_{type}.pcm, e.g.

      • channel_0_mic.pcm (microphone)

      • channel_4_ref.pcm (reference / echo-cancellation)

  3. Play saved PCM file:

    • Play on a computer:

      aplay -r 16000 -f S16_LE -c 1 audio_recordings/20250909_133650_123/channel_0_mic.pcm
      
    • Play on the robot:

      # Use the raw audio stream playback example
      ros2 run examples play_audio_stream --ros-args -p raw_audio_path:=$PWD/audio_recordings/20250909_133650_123/channel_0_mic.pcm
      # Or use the media file playback example; note that the file must be transferred to the interaction compute unit first, replace the path below
      ros2 run examples play_audio please/use/path/on/PC3.pcm
      

6.2.23 Get Current Microphone

  1#include "aimdk_msgs/msg/common_request.hpp"
  2#include "aimdk_msgs/msg/common_state.hpp"
  3#include "aimdk_msgs/srv/get_mic_source_request.hpp"
  4#include "rclcpp/rclcpp.hpp"
  5#include <chrono>
  6#include <memory>
  7#include <signal.h>
  8
  9std::shared_ptr<rclcpp::Node> g_node = nullptr;
 10
 11void signal_handler(int signal) {
 12  if (g_node) {
 13    RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
 14                signal);
 15    g_node.reset();
 16  }
 17  rclcpp::shutdown();
 18  exit(signal);
 19}
 20
 21class GetMicSourceRequestClient : public rclcpp::Node {
 22public:
 23  GetMicSourceRequestClient() : Node("get_mic_source_request_client") {
 24    client_ = this->create_client<aimdk_msgs::srv::GetMicSourceRequest>(
 25        "/aimdk_5Fmsgs/srv/GetMicSourceRequest");
 26
 27    RCLCPP_INFO(this->get_logger(),
 28                "✅ GetMicSourceRequest client node created.");
 29
 30    // Wait for the service to become available
 31    while (!client_->wait_for_service(std::chrono::seconds(2))) {
 32      RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
 33    }
 34    RCLCPP_INFO(this->get_logger(),
 35                "🟢 Service available, ready to send request.");
 36  }
 37
 38  void send_request() {
 39    try {
 40      auto request =
 41          std::make_shared<aimdk_msgs::srv::GetMicSourceRequest::Request>();
 42      request->header = aimdk_msgs::msg::CommonRequest();
 43
 44      RCLCPP_INFO(this->get_logger(), "📨 Sending request to get MIC source");
 45
 46      auto timeout = std::chrono::milliseconds(250);
 47      for (int i = 0; i < 8; i++) {
 48        request->header.header.stamp = this->now();
 49        auto future = client_->async_send_request(request);
 50        auto retcode = rclcpp::spin_until_future_complete(
 51            this->shared_from_this(), future, timeout);
 52        if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
 53          // retry as remote peer is NOT handled well by ROS
 54          RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
 55          continue;
 56        }
 57        // future.done
 58        auto response = future.get();
 59        if (response->header.status.value ==
 60            aimdk_msgs::msg::CommonState::SUCCESS) { // SUCCESS (1)
 61          RCLCPP_INFO(this->get_logger(), "✅ MIC source get successfully.");
 62          RCLCPP_INFO(this->get_logger(), "MIC id: %d", response->mic_source);
 63        } else {
 64          RCLCPP_ERROR(this->get_logger(), "❌ Failed to get MIC source: %s",
 65                       response->header.message.c_str());
 66        }
 67        return;
 68      }
 69      RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
 70    } catch (const std::exception &e) {
 71      RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
 72    }
 73  }
 74
 75private:
 76  rclcpp::Client<aimdk_msgs::srv::GetMicSourceRequest>::SharedPtr client_;
 77};
 78
 79int main(int argc, char *argv[]) {
 80  try {
 81    rclcpp::init(argc, argv);
 82    signal(SIGINT, signal_handler);
 83    signal(SIGTERM, signal_handler);
 84
 85    g_node = std::make_shared<GetMicSourceRequestClient>();
 86    auto client = std::dynamic_pointer_cast<GetMicSourceRequestClient>(g_node);
 87
 88    if (client) {
 89      client->send_request();
 90    }
 91
 92    g_node.reset();
 93    rclcpp::shutdown();
 94    return 0;
 95  } catch (const std::exception &e) {
 96    RCLCPP_ERROR(rclcpp::get_logger("main"),
 97                 "Program exited with exception: %s", e.what());
 98    return 1;
 99  }
100}

6.2.24 Switch Microphone

  1#include "aimdk_msgs/msg/common_request.hpp"
  2#include "aimdk_msgs/msg/common_state.hpp"
  3#include "aimdk_msgs/srv/set_mic_source_request.hpp"
  4#include "rclcpp/rclcpp.hpp"
  5#include <chrono>
  6#include <map>
  7#include <memory>
  8#include <signal.h>
  9#include <string>
 10
 11std::shared_ptr<rclcpp::Node> g_node = nullptr;
 12
 13void signal_handler(int signal) {
 14  if (g_node) {
 15    RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
 16                signal);
 17    g_node.reset();
 18  }
 19  rclcpp::shutdown();
 20  exit(signal);
 21}
 22
 23class SetMicSourceRequestClient : public rclcpp::Node {
 24public:
 25  SetMicSourceRequestClient() : Node("set_mic_source_request_client") {
 26    client_ = this->create_client<aimdk_msgs::srv::SetMicSourceRequest>(
 27        "/aimdk_5Fmsgs/srv/SetMicSourceRequest");
 28
 29    RCLCPP_INFO(this->get_logger(),
 30                "✅ SetMicSourceRequest client node created.");
 31
 32    // Wait for the service to become available
 33    while (!client_->wait_for_service(std::chrono::seconds(2))) {
 34      RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
 35    }
 36    RCLCPP_INFO(this->get_logger(),
 37                "🟢 Service available, ready to send request.");
 38  }
 39
 40  void send_request(int mic_source) {
 41    try {
 42      auto request =
 43          std::make_shared<aimdk_msgs::srv::SetMicSourceRequest::Request>();
 44      request->header = aimdk_msgs::msg::CommonRequest();
 45      request->mic_source = mic_source;
 46
 47      RCLCPP_INFO(this->get_logger(), "📨 Sending request to set mic source: %d",
 48                  mic_source);
 49
 50      auto timeout = std::chrono::milliseconds(250);
 51      for (int i = 0; i < 8; i++) {
 52        request->header.header.stamp = this->now();
 53        auto future = client_->async_send_request(request);
 54        auto retcode = rclcpp::spin_until_future_complete(
 55            this->shared_from_this(), future, timeout);
 56        if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
 57          // retry as remote peer is NOT handled well by ROS
 58          RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
 59          continue;
 60        }
 61        // future.done
 62        auto response = future.get();
 63        if (response->header.status.value ==
 64            aimdk_msgs::msg::CommonState::SUCCESS) { // SUCCESS (1)
 65          RCLCPP_INFO(this->get_logger(), "✅ MIC source set successfully.");
 66        } else {
 67          RCLCPP_ERROR(this->get_logger(), "❌ Failed to set MIC source: %s",
 68                       response->header.message.c_str());
 69        }
 70        return;
 71      }
 72      RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
 73    } catch (const std::exception &e) {
 74      RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
 75    }
 76  }
 77
 78private:
 79  rclcpp::Client<aimdk_msgs::srv::SetMicSourceRequest>::SharedPtr client_;
 80};
 81
 82int main(int argc, char *argv[]) {
 83  // abbr -> mic_id mapping
 84  // 0: internal MIC, 1: external MIC
 85  const std::map<std::string, int> choices = {{"int", 0}, {"ext", 1}};
 86
 87  try {
 88    rclcpp::init(argc, argv);
 89    signal(SIGINT, signal_handler);
 90    signal(SIGTERM, signal_handler);
 91
 92    std::string mic_abbr;
 93    if (argc > 1) {
 94      mic_abbr = argv[1];
 95    } else {
 96      printf("%-4s - %-6s : %s\n", "abbr", "mic_id", "description");
 97      printf("%-4s - %-6d : %s\n", "int", 0, "internal MIC");
 98      printf("%-4s - %-6d : %s\n", "ext", 1, "external MIC");
 99      printf("Enter abbr of MIC source: ");
100      std::cin >> mic_abbr;
101    }
102
103    auto it = choices.find(mic_abbr);
104    if (it == choices.end()) {
105      RCLCPP_ERROR(rclcpp::get_logger("main"), "Invalid abbr of MIC source: %s",
106                   mic_abbr.c_str());
107      return 1;
108    }
109    int mic_id = it->second;
110
111    g_node = std::make_shared<SetMicSourceRequestClient>();
112    auto client = std::dynamic_pointer_cast<SetMicSourceRequestClient>(g_node);
113
114    if (client) {
115      client->send_request(mic_id);
116    }
117
118    g_node.reset();
119    rclcpp::shutdown();
120    return 0;
121  } catch (const std::exception &e) {
122    RCLCPP_ERROR(rclcpp::get_logger("main"),
123                 "Program exited with exception: %s", e.what());
124    return 1;
125  }
126}

Usage Instructions

# Build
colcon build --packages-select examples

# Interactive run
ros2 run examples set_mic_source

# Run with argument
ros2 run examples set_mic_source ext  # ext - external mic, int - internal mic

6.2.25 Raw Audio Stream Playback

This example uses play_audio_stream, which first requests audio focus then publishes a PCM file at real-time rate to the /aima/hal/audio/playback topic; it exits automatically when audio focus is taken away.

  1/**
  2 * Raw audio stream playback example
  3 *
  4 * This example gets audio focus first and then publishes raw audio data at a
  5 * fixed rate to the `/aima/hal/audio/playback` topic. It stops playing when
  6 * audio focus is lost.
  7 *
  8 * Usage:
  9 *   ros2 run examples play_audio_stream --ros-args -p
 10 * raw_audio_path:=/path/to/file.pcm
 11 */
 12
 13#include <aimdk_msgs/msg/audio_data.hpp>
 14#include <aimdk_msgs/msg/audio_info.hpp>
 15#include <aimdk_msgs/msg/audio_playback.hpp>
 16#include <aimdk_msgs/msg/common_state.hpp>
 17#include <aimdk_msgs/msg/focus_requester.hpp>
 18#include <aimdk_msgs/msg/focus_response.hpp>
 19#include <aimdk_msgs/srv/abandon_audio_focus.hpp>
 20#include <aimdk_msgs/srv/request_audio_focus.hpp>
 21#include <chrono>
 22#include <csignal>
 23#include <fstream>
 24#include <rclcpp/rclcpp.hpp>
 25#include <string>
 26#include <thread>
 27#include <vector>
 28
 29// ---------------------------------------------------------------------------
 30// FakeMicDevice: simulates a real-time microphone from a raw PCM file
 31// ---------------------------------------------------------------------------
 32class FakeMicDevice {
 33public:
 34  explicit FakeMicDevice(const std::string &raw_audio_file)
 35      : channels_(1), sample_rate_(16000), bytes_per_sample_(2), index_(0),
 36        start_time_(std::chrono::steady_clock::time_point{}) {
 37    std::ifstream f(raw_audio_file, std::ios::binary);
 38    if (!f.is_open()) {
 39      throw std::runtime_error("Cannot open audio file: " + raw_audio_file);
 40    }
 41    audio_data_ = std::vector<uint8_t>(std::istreambuf_iterator<char>(f),
 42                                       std::istreambuf_iterator<char>());
 43  }
 44
 45  int get_channel_count() const { return channels_; }
 46  int get_sample_rate() const { return sample_rate_; }
 47
 48  std::vector<uint8_t> get_cached_data() {
 49    auto now = std::chrono::steady_clock::now();
 50    if (start_time_ == std::chrono::steady_clock::time_point{}) {
 51      // fake init: assume audio data started 1s ago
 52      start_time_ = now - std::chrono::seconds(1);
 53    }
 54
 55    double elapsed = std::chrono::duration<double>(now - start_time_).count();
 56    size_t tail = static_cast<size_t>(elapsed * sample_rate_) * channels_ *
 57                  bytes_per_sample_;
 58
 59    std::vector<uint8_t> data;
 60    if (tail < audio_data_.size()) {
 61      data = std::vector<uint8_t>(audio_data_.begin() + index_,
 62                                  audio_data_.begin() + tail);
 63      index_ = tail;
 64    } else {
 65      // loopback
 66      data =
 67          std::vector<uint8_t>(audio_data_.begin() + index_, audio_data_.end());
 68      index_ = 0;
 69      start_time_ = now;
 70    }
 71    return data;
 72  }
 73
 74private:
 75  int channels_;
 76  int sample_rate_;
 77  int bytes_per_sample_;
 78  size_t index_;
 79  std::chrono::steady_clock::time_point start_time_;
 80  std::vector<uint8_t> audio_data_;
 81};
 82
 83// ---------------------------------------------------------------------------
 84// AudioStreamPlayer node
 85// ---------------------------------------------------------------------------
 86using RequestAudioFocus = aimdk_msgs::srv::RequestAudioFocus;
 87using AbandonAudioFocus = aimdk_msgs::srv::AbandonAudioFocus;
 88
 89class AudioStreamPlayer : public rclcpp::Node {
 90public:
 91  AudioStreamPlayer() : rclcpp::Node("audio_stream_player") {
 92    this->declare_parameter<std::string>("raw_audio_path", "");
 93    std::string raw_audio_path =
 94        this->get_parameter("raw_audio_path").as_string();
 95
 96    mic_device_ = std::make_unique<FakeMicDevice>(raw_audio_path);
 97
 98    pkg_name_ = "audio_stream_player" + std::to_string(getpid());
 99    focus_ = false;
100    focus_force_ = true;
101
102    RCLCPP_INFO(this->get_logger(), "local pkg name: %s", pkg_name_.c_str());
103
104    // Create focus request/release clients
105    request_client_ = this->create_client<RequestAudioFocus>(
106        "/aimdk_5Fmsgs/srv/RequestAudioFocus");
107    release_client_ = this->create_client<AbandonAudioFocus>(
108        "/aimdk_5Fmsgs/srv/AbandonAudioFocus");
109
110    // Wait for service to become available
111    while (!request_client_->wait_for_service(std::chrono::seconds(2))) {
112      RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
113    }
114    RCLCPP_INFO(this->get_logger(),
115                "🟢 Service available, ready to send request.");
116
117    // Subscriber: focus events
118    auto focus_qos = rclcpp::QoS(rclcpp::KeepLast(10)).reliable();
119    sub_ = this->create_subscription<aimdk_msgs::msg::FocusResponse>(
120        "/aima/hal/audio/focus_response", focus_qos,
121        std::bind(&AudioStreamPlayer::focus_event_callback, this,
122                  std::placeholders::_1));
123
124    // Publisher: raw audio playback
125    pub_ = this->create_publisher<aimdk_msgs::msg::AudioPlayback>(
126        "/aima/hal/audio/playback", 10);
127  }
128
129  void send_request(bool focus) {
130    if (focus) {
131      send_focus_request();
132    } else {
133      send_focus_release();
134    }
135  }
136
137  bool is_holding_focus() const { return focus_ && focus_force_; }
138
139  void spin_until_focus() {
140    while (rclcpp::ok()) {
141      focus_force_ = true;
142      send_request(true);
143      if (is_holding_focus())
144        return;
145
146      RCLCPP_INFO(this->get_logger(), "need retry to get focus");
147      auto t0 = std::chrono::steady_clock::now();
148      while (rclcpp::ok()) {
149        auto t1 = std::chrono::steady_clock::now();
150        if (std::chrono::duration<double>(t1 - t0).count() >= 1.0)
151          break;
152        rclcpp::spin_some(shared_from_this());
153        std::this_thread::sleep_for(std::chrono::milliseconds(100));
154      }
155    }
156  }
157
158  void run() {
159    RCLCPP_INFO(this->get_logger(), "🟢 publishing audio data ...");
160    while (rclcpp::ok() && is_holding_focus()) {
161      run_once();
162      rclcpp::spin_some(shared_from_this());
163      std::this_thread::sleep_for(std::chrono::milliseconds(50));
164    }
165    if (!is_holding_focus()) {
166      RCLCPP_INFO(this->get_logger(), "focus out, exiting...");
167    }
168  }
169
170private:
171  void send_focus_request() {
172    auto req = std::make_shared<RequestAudioFocus::Request>();
173    aimdk_msgs::msg::FocusRequester requester;
174    requester.pkg_name = pkg_name_;
175    requester.priority = kAudioPriority;
176    req->focus_requester = requester;
177
178    RCLCPP_INFO(this->get_logger(), "📨 Sending RequestAudioFocus request");
179    std::shared_future<RequestAudioFocus::Response::SharedPtr> future;
180    for (int i = 0; i < 8; ++i) {
181      future = request_client_->async_send_request(req).future.share();
182      auto status = rclcpp::spin_until_future_complete(
183          shared_from_this(), future, std::chrono::milliseconds(250));
184      if (status == rclcpp::FutureReturnCode::SUCCESS)
185        break;
186      // retry as remote peer is NOT handled well by ROS
187      RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
188    }
189
190    auto response = future.get();
191    if (!response) {
192      RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
193      return;
194    }
195    if (response->reponse.status.value ==
196        aimdk_msgs::msg::CommonState::SUCCESS) { // SUCCESS (1)
197      focus_ = response->focus_response.focus_gain;
198      RCLCPP_INFO(this->get_logger(), "✅ RequestAudioFocus done: focus %s",
199                  focus_ ? "true" : "false");
200    } else {
201      RCLCPP_ERROR(this->get_logger(),
202                   "❌ Failed in response of RequestAudioFocus: %s",
203                   response->reponse.message.c_str());
204    }
205  }
206
207  void send_focus_release() {
208    auto req = std::make_shared<AbandonAudioFocus::Request>();
209    aimdk_msgs::msg::FocusRequester requester;
210    requester.pkg_name = pkg_name_;
211    requester.priority = kAudioPriority; // must match RequestAudioFocus
212    req->focus_requester = requester;
213
214    RCLCPP_INFO(this->get_logger(), "📨 Sending AbandonAudioFocus request");
215    std::shared_future<AbandonAudioFocus::Response::SharedPtr> future;
216    for (int i = 0; i < 8; ++i) {
217      future = release_client_->async_send_request(req).future.share();
218      auto status = rclcpp::spin_until_future_complete(
219          shared_from_this(), future, std::chrono::milliseconds(250));
220      if (status == rclcpp::FutureReturnCode::SUCCESS)
221        break;
222      // retry as remote peer is NOT handled well by ROS
223      RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
224    }
225
226    auto response = future.get();
227    if (!response) {
228      RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
229      return;
230    }
231    if (response->reponse.status.value ==
232        aimdk_msgs::msg::CommonState::SUCCESS) { // SUCCESS (1)
233      // always focus False after abandon
234      focus_ = response->focus_response.focus_gain;
235      RCLCPP_INFO(this->get_logger(), "✅ AbandonAudioFocus done: focus %s",
236                  focus_ ? "true" : "false");
237    } else {
238      RCLCPP_ERROR(this->get_logger(),
239                   "❌ Failed in response of AbandonAudioFocus: %s",
240                   response->reponse.message.c_str());
241    }
242  }
243
244  void
245  focus_event_callback(const aimdk_msgs::msg::FocusResponse::SharedPtr msg) {
246    if (msg->pkg_name == pkg_name_) {
247      RCLCPP_INFO(this->get_logger(),
248                  "receive focus out event: focus state: %s",
249                  msg->focus_gain ? "true" : "false");
250      focus_force_ = msg->focus_gain;
251    }
252  }
253
254  void run_once() {
255    aimdk_msgs::msg::AudioPlayback msg;
256    msg.pkg_name = pkg_name_;
257    msg.token_id = pkg_name_;
258
259    aimdk_msgs::msg::AudioInfo info;
260    info.channels = static_cast<uint8_t>(mic_device_->get_channel_count());
261    info.sample_rate = static_cast<uint32_t>(mic_device_->get_sample_rate());
262
263    aimdk_msgs::msg::AudioData audio_data;
264    audio_data.data = mic_device_->get_cached_data();
265
266    msg.info = info;
267    msg.data = audio_data;
268    pub_->publish(msg);
269  }
270
271  std::string pkg_name_;
272  bool focus_;
273  bool focus_force_;
274  static constexpr uint32_t kAudioPriority = 8;
275  std::unique_ptr<FakeMicDevice> mic_device_;
276  rclcpp::Client<RequestAudioFocus>::SharedPtr request_client_;
277  rclcpp::Client<AbandonAudioFocus>::SharedPtr release_client_;
278  rclcpp::Subscription<aimdk_msgs::msg::FocusResponse>::SharedPtr sub_;
279  rclcpp::Publisher<aimdk_msgs::msg::AudioPlayback>::SharedPtr pub_;
280};
281
282// ---------------------------------------------------------------------------
283// Signal handling
284// ---------------------------------------------------------------------------
285static std::shared_ptr<AudioStreamPlayer> g_node;
286
287void signal_handler(int sig) {
288  if (g_node) {
289    if (g_node->is_holding_focus()) {
290      g_node->send_request(false);
291    }
292    RCLCPP_INFO(g_node->get_logger(),
293                "Received signal %d, abandon audio focus and shutting down",
294                sig);
295  }
296  rclcpp::shutdown();
297}
298
299int main(int argc, char **argv) {
300  rclcpp::init(argc, argv);
301
302  try {
303    auto node = std::make_shared<AudioStreamPlayer>();
304    g_node = node;
305    std::signal(SIGINT, signal_handler);
306    std::signal(SIGTERM, signal_handler);
307
308    node->spin_until_focus();
309    node->run();
310
311  } catch (const std::exception &e) {
312    RCLCPP_ERROR(rclcpp::get_logger("main"),
313                 "Program exited with exception: %s", e.what());
314  }
315
316  if (rclcpp::ok()) {
317    rclcpp::shutdown();
318  }
319  return 0;
320}

Usage Instructions:

# Replace /path/to/your/raw_audio_file.pcm with the actual PCM file path (mono, 16 kHz, S16LE)
ros2 run examples play_audio_stream --ros-args -p raw_audio_path:=/path/to/your/raw_audio_file.pcm

6.2.26 Emoji Control

This example uses play_emoji, which allows the robot to display a specified emoji. Users can choose an emoji from the available list; see the Emoji List for details.

  1#include "aimdk_msgs/srv/play_emoji.hpp"
  2#include "aimdk_msgs/msg/common_request.hpp"
  3#include "rclcpp/rclcpp.hpp"
  4#include <chrono>
  5#include <iostream>
  6#include <memory>
  7#include <signal.h>
  8#include <string>
  9
 10// Global variable used for signal handling
 11std::shared_ptr<rclcpp::Node> g_node = nullptr;
 12
 13// Signal handler function
 14void signal_handler(int signal) {
 15  if (g_node) {
 16    RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
 17                signal);
 18    g_node.reset();
 19  }
 20  rclcpp::shutdown();
 21  exit(signal);
 22}
 23
 24class PlayEmojiClient : public rclcpp::Node {
 25public:
 26  PlayEmojiClient() : Node("play_emoji_client") {
 27    client_ = this->create_client<aimdk_msgs::srv::PlayEmoji>(
 28        "/aimdk_5Fmsgs/srv/PlayEmoji");
 29    RCLCPP_INFO(this->get_logger(), "✅ PlayEmoji client node started.");
 30
 31    // Wait for the service to become available
 32    while (!client_->wait_for_service(std::chrono::seconds(2))) {
 33      RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
 34    }
 35    RCLCPP_INFO(this->get_logger(),
 36                "🟢 Service available, ready to send request.");
 37  }
 38
 39  bool send_request(uint8_t emoji, uint8_t mode, int32_t priority) {
 40    try {
 41      auto request = std::make_shared<aimdk_msgs::srv::PlayEmoji::Request>();
 42
 43      request->emotion_id = emoji;
 44      request->mode = mode;
 45      request->priority = priority;
 46
 47      RCLCPP_INFO(
 48          this->get_logger(),
 49          "📨 Sending request to play emoji: id=%hhu, mode=%hhu, priority=%d",
 50          emoji, mode, priority);
 51
 52      const std::chrono::milliseconds timeout(250);
 53      for (int i = 0; i < 8; i++) {
 54        request->header.header.stamp = this->now();
 55        auto future = client_->async_send_request(request);
 56        auto retcode = rclcpp::spin_until_future_complete(shared_from_this(),
 57                                                          future, timeout);
 58        if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
 59          // retry as remote peer is NOT handled well by ROS
 60          RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
 61          continue;
 62        }
 63        // future.done
 64        auto response = future.get();
 65        if (response->success) {
 66          RCLCPP_INFO(this->get_logger(),
 67                      "✅ Request to play emoji recorded successfully: %s",
 68                      response->message.c_str());
 69          return true;
 70        } else {
 71          RCLCPP_ERROR(this->get_logger(),
 72                       "❌ Failed to record play-emoji request: %s",
 73                       response->message.c_str());
 74          return false;
 75        }
 76      }
 77      RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
 78      return false;
 79    } catch (const std::exception &e) {
 80      RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
 81      return false;
 82    }
 83  }
 84
 85private:
 86  rclcpp::Client<aimdk_msgs::srv::PlayEmoji>::SharedPtr client_;
 87};
 88
 89int main(int argc, char **argv) {
 90  try {
 91    rclcpp::init(argc, argv);
 92
 93    // Set up signal handlers
 94    signal(SIGINT, signal_handler);
 95    signal(SIGTERM, signal_handler);
 96
 97    int32_t priority = 10;
 98
 99    int emotion = 1; // Expression type, 1 means Blink
100    std::cout
101        << "Enter expression ID: 1-Blink, 60-Bored, 70-Abnormal, 80-Sleeping, "
102           "90-Happy, 190-Very Angry, 200-Adoration"
103        << std::endl;
104    std::cin >> emotion;
105
106    int mode = 1; // Playback mode, 1 means play once, 2 means loop
107    std::cout << "Enter play mode (1: once, 2: loop): ";
108    std::cin >> mode;
109    if (mode < 1 || mode > 2) {
110      RCLCPP_ERROR(rclcpp::get_logger("main"), "Invalid play mode: %d", mode);
111      rclcpp::shutdown();
112      return 1;
113    }
114
115    g_node = std::make_shared<PlayEmojiClient>();
116    auto client = std::dynamic_pointer_cast<PlayEmojiClient>(g_node);
117
118    if (client) {
119      client->send_request(emotion, mode, priority);
120    }
121
122    // Clean up resources
123    g_node.reset();
124    rclcpp::shutdown();
125
126    return 0;
127  } catch (const std::exception &e) {
128    RCLCPP_ERROR(rclcpp::get_logger("main"),
129                 "Program exited with exception: %s", e.what());
130    return 1;
131  }
132}

6.2.27 Map Building

In this example, slam is used to send messages to the mapping service via this node to achieve mapping functionality.

 1#include <iostream>
 2#include <rclcpp/rclcpp.hpp>
 3#include <std_msgs/msg/string.hpp>
 4#include <string>
 5
 6// Class definition for publishing commands to control SLAM operations
 7class SlamCommandPublisher : public rclcpp::Node {
 8public:
 9  // Constructor for the SlamCommandPublisher class
10  SlamCommandPublisher()
11      : Node("slam_command_publisher") // Initialize the node with the name
12                                       // "slam_command_publisher"
13  {
14    // Match SLAM subscriber QoS: TRANSIENT_LOCAL + RELIABLE
15    rclcpp::QoS qos(10);
16    qos.transient_local().reliable();
17    publisher_ = this->create_publisher<std_msgs::msg::String>(
18        "/integrated_command", qos);
19  }
20
21  // Method to publish the "start_mapping" command
22  void publish_start_mapping() {
23    auto message = std_msgs::msg::String();
24    message.data = "start_mapping";
25    RCLCPP_INFO(this->get_logger(), "Publishing: [%s]", message.data.c_str());
26    publisher_->publish(message);
27  }
28
29  // Method to publish the "stop_mapping" command with a specified map name
30  void publish_stop_mapping(const std::string &map_name) {
31    auto message = std_msgs::msg::String();
32    message.data = "stop_mapping:" + map_name;
33    RCLCPP_INFO(this->get_logger(), "Publishing: [%s]", message.data.c_str());
34    publisher_->publish(message);
35  }
36
37private:
38  // Publisher object to publish messages to the "/integrated_command" topic
39  rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
40};
41
42int main(int argc, char *argv[]) {
43  // Initialize the ROS 2 communication system
44  rclcpp::init(argc, argv);
45  // Create an instance of the SlamCommandPublisher node
46  auto node = std::make_shared<SlamCommandPublisher>();
47
48  int input;
49  std::string map_name;
50
51  // Infinite loop to continuously accept user input
52  while (rclcpp::ok()) {
53    std::cout << "Enter 1 to start mapping, 2 to stop mapping: ";
54    std::cin >> input;
55
56    if (input == 1) {
57      // Publish the "start_mapping" command
58      node->publish_start_mapping();
59    } else if (input == 2) {
60      // Prompt the user to enter the map name
61      std::cout << "Enter map name: ";
62      std::cin >> map_name;
63      // Publish the "stop_mapping" command with the specified map name
64      node->publish_stop_mapping(map_name);
65      break;
66    } else {
67      // Handle invalid input
68      std::cout << "Invalid input. Please enter 1 or 2." << std::endl;
69    }
70  }
71
72  // Shutdown the ROS 2 communication system
73  rclcpp::shutdown();
74  return 0;
75}

Usage Instructions:

ros2 run examples slam
Enter 1 to start mapping, 2 to stop mapping: 1
[INFO] [1772528788.353934160] [slam_command_publisher]: Publishing: [start_mapping]
Enter 1 to start mapping, 2 to stop mapping: 2
Enter map name: testmap
[INFO] [1772528791.900924187] [slam_command_publisher]: Publishing: [stop_mapping:testmap]

After starting the mapping process, push the machine or control the robot to move in order to complete the mapping.

6.2.28 Relocalization Using a Map

This example uses the map built in the Map Building step for localization.

  1#include <chrono>
  2#include <functional>
  3#include <geometry_msgs/msg/pose.hpp>
  4#include <iostream>
  5#include <nav_msgs/msg/odometry.hpp>
  6#include <rclcpp/rclcpp.hpp>
  7#include <std_msgs/msg/string.hpp>
  8#include <string>
  9
 10using namespace std::chrono_literals;
 11
 12class RelocalizationNode : public rclcpp::Node {
 13public:
 14  RelocalizationNode(uint64_t map_id, double init_x, double init_y)
 15      : Node("relocalization_node"), map_id_(map_id), init_x_(init_x),
 16        init_y_(init_y) {
 17    // /integrated_command subscriber requires TRANSIENT_LOCAL durability
 18    auto command_qos = rclcpp::QoS(rclcpp::KeepLast(10));
 19    command_qos.transient_local();
 20
 21    integrated_command_pub_ = this->create_publisher<std_msgs::msg::String>(
 22        "/integrated_command", command_qos);
 23    relocalization_pose_pub_ = this->create_publisher<geometry_msgs::msg::Pose>(
 24        "/relocalization_pose", 10);
 25
 26    // Create subscriber with BEST_EFFORT QoS
 27    auto lidar_loc_qos = rclcpp::QoS(rclcpp::KeepLast(10));
 28    lidar_loc_qos.best_effort();
 29
 30    odometry_sub_ = this->create_subscription<nav_msgs::msg::Odometry>(
 31        "/slam/lidar_odom", lidar_loc_qos,
 32        std::bind(&RelocalizationNode::odometryCallback, this,
 33                  std::placeholders::_1));
 34
 35    // Wait for subscribers before publishing
 36    publish_timer_ = this->create_wall_timer(200ms, [this]() {
 37      // Wait until both publishers have at least one subscriber
 38      if (integrated_command_pub_->get_subscription_count() == 0 ||
 39          relocalization_pose_pub_->get_subscription_count() == 0) {
 40        RCLCPP_INFO_THROTTLE(
 41            this->get_logger(), *this->get_clock(), 2000,
 42            "Waiting for subscribers... (command: %zu, pose: %zu)",
 43            integrated_command_pub_->get_subscription_count(),
 44            relocalization_pose_pub_->get_subscription_count());
 45        return;
 46      }
 47      publish_timer_->cancel();
 48
 49      // Publish integrated_command
 50      auto integrated_command_msg = std_msgs::msg::String();
 51      integrated_command_msg.data =
 52          "start_relocalization:" + std::to_string(map_id_);
 53      integrated_command_pub_->publish(integrated_command_msg);
 54      RCLCPP_INFO(this->get_logger(), "Published integrated_command");
 55
 56      // Schedule relocalization_pose publication after delay
 57      this->relocalization_pose_timer_ = this->create_wall_timer(1s, [this]() {
 58        this->relocalization_pose_timer_->cancel();
 59
 60        auto relocalization_pose_msg = geometry_msgs::msg::Pose();
 61        relocalization_pose_msg.position.x = init_x_;
 62        relocalization_pose_msg.position.y = init_y_;
 63        relocalization_pose_msg.position.z = 0.0;
 64        relocalization_pose_msg.orientation.x = 0.0;
 65        relocalization_pose_msg.orientation.y = 0.0;
 66        relocalization_pose_msg.orientation.z = 0.0;
 67        relocalization_pose_msg.orientation.w = 1.0;
 68
 69        relocalization_pose_pub_->publish(relocalization_pose_msg);
 70        RCLCPP_INFO(this->get_logger(), "Published relocalization_pose");
 71
 72        // Start timeout timer (30 seconds)
 73        this->timeout_timer_ = this->create_wall_timer(30s, [this]() {
 74          if (!success_received_) {
 75            RCLCPP_ERROR(this->get_logger(),
 76                         "Timeout reached - Relocalization failed!");
 77            rclcpp::shutdown();
 78          }
 79        });
 80
 81        RCLCPP_INFO(this->get_logger(),
 82                    "Waiting for robot pose data (timeout: 30s)...");
 83      });
 84    });
 85  }
 86
 87private:
 88  // get robot pose here
 89  void odometryCallback(const nav_msgs::msg::Odometry::SharedPtr msg) {
 90    if (!success_received_) {
 91      success_received_ = true;
 92      if (timeout_timer_) {
 93        timeout_timer_->cancel();
 94      }
 95      RCLCPP_INFO(this->get_logger(),
 96                  "Received odometry data - Relocalization successful!");
 97      rclcpp::shutdown();
 98    }
 99  }
100
101  rclcpp::Publisher<std_msgs::msg::String>::SharedPtr integrated_command_pub_;
102  rclcpp::Publisher<geometry_msgs::msg::Pose>::SharedPtr
103      relocalization_pose_pub_;
104  rclcpp::Subscription<nav_msgs::msg::Odometry>::SharedPtr odometry_sub_;
105
106  // Timers
107  rclcpp::TimerBase::SharedPtr publish_timer_;
108  rclcpp::TimerBase::SharedPtr relocalization_pose_timer_;
109  rclcpp::TimerBase::SharedPtr timeout_timer_;
110
111  uint64_t map_id_;
112  double init_x_;
113  double init_y_;
114  bool success_received_ = false;
115};
116
117int main(int argc, char *argv[]) {
118  rclcpp::init(argc, argv);
119
120  // Inputs needed for relocalization. To find them, see:
121  //   map_id        -> sqlite3 /agibot/data/var/MapManagerModule/map.db
122  //                    "SELECT * FROM map;"
123  //   init_x/init_y -> origin (u, v) in
124  //                    /agibot/data/var/MapManagerModule/<map_id>/grid_map_info.txt
125  //                    (line 2; pixel coordinates)
126  uint64_t map_id = 0;
127  double init_x = 0.0;
128  double init_y = 0.0;
129  try {
130    std::string line;
131    std::cout << "Enter map_id: ";
132    std::getline(std::cin, line);
133    map_id = std::stoull(line);
134    std::cout << "Enter initial position.x (pixel u): ";
135    std::getline(std::cin, line);
136    init_x = std::stod(line);
137    std::cout << "Enter initial position.y (pixel v): ";
138    std::getline(std::cin, line);
139    init_y = std::stod(line);
140  } catch (const std::exception &e) {
141    std::cerr << "Invalid input, aborted." << std::endl;
142    rclcpp::shutdown();
143    return 1;
144  }
145
146  auto node = std::make_shared<RelocalizationNode>(map_id, init_x, init_y);
147  rclcpp::spin(node);
148  rclcpp::shutdown();
149  return 0;
150}

Usage Instructions:

ros2 run examples relocate
Enter map_id: 1774430080403
Enter initial position.x (pixel u): 273
Enter initial position.y (pixel v): 200
[INFO] [1772528939.129777359] [relocalization_node]: Published integrated_command
[INFO] [1772528940.130067255] [relocalization_node]: Published relocalization_pose
[INFO] [1772528940.130129806] [relocalization_node]: Waiting for robot pose data (timeout: 30s)...
[INFO] [1772528945.130242770] [relocalization_node]: Received odometry data - Relocalization successful!

Move the robot to the starting point of the map construction, then run the relocation program.

How to obtain these parameters: for map_id and the starting-point pixel coordinates (position.x / position.y), see Obtaining Map Identifiers and Coordinates.

6.2.29 Retrieve a Stored Map

In this example, get_map is used to retrieve stored map data by name, including the map path, resolution, dimensions, and map ID.

 1//
 2// Created by agiuser on 2026/3/2.
 3//
 4
 5#include "aimdk_msgs/srv/get_stored_map_by_name.hpp"
 6#include "rclcpp/rclcpp.hpp"
 7#include <chrono>
 8#include <cinttypes>
 9#include <iostream>
10#include <memory>
11#include <string>
12
13class MapClient : public rclcpp::Node {
14public:
15  MapClient() : Node("get_map_client") {
16    client_ = this->create_client<aimdk_msgs::srv::GetStoredMapByName>(
17        "/aimdk_5Fmsgs/srv/GetStoredMapByName");
18  }
19
20  void send_request(const std::string &map_name) {
21    while (!client_->wait_for_service(std::chrono::seconds(1))) {
22      if (!rclcpp::ok()) {
23        RCLCPP_ERROR(this->get_logger(),
24                     "Interrupted while waiting for service.");
25        return;
26      }
27      RCLCPP_INFO(this->get_logger(),
28                  "Service not available, waiting again...");
29    }
30
31    auto request =
32        std::make_shared<aimdk_msgs::srv::GetStoredMapByName::Request>();
33
34    request->header.stamp = this->now();
35    request->header.frame_id = "";
36
37    request->map_name = map_name;
38
39    auto result_future = client_->async_send_request(request);
40
41    // Use a fresh executor to spin, avoiding any pre-existing state.
42    rclcpp::executors::SingleThreadedExecutor exec;
43    exec.add_node(shared_from_this());
44    if (exec.spin_until_future_complete(result_future,
45                                        std::chrono::seconds(60)) !=
46        rclcpp::FutureReturnCode::SUCCESS) {
47      RCLCPP_ERROR(this->get_logger(), "Service call failed or timed out.");
48      return;
49    }
50
51    auto result = result_future.get();
52
53    if (result->code == 0) {
54      RCLCPP_INFO(this->get_logger(), "Service call succeeded");
55      RCLCPP_INFO(this->get_logger(), "Map path: %s", result->map_path.c_str());
56      RCLCPP_INFO(this->get_logger(), "Map resolution: %f",
57                  result->map_info.resolution);
58      RCLCPP_INFO(this->get_logger(), "Map width: %u", result->map_info.width);
59      RCLCPP_INFO(this->get_logger(), "Map height: %u",
60                  result->map_info.height);
61      RCLCPP_INFO(this->get_logger(), "Map id: %" PRIu64, result->map_id);
62    } else {
63      RCLCPP_ERROR(this->get_logger(),
64                   "Service call failed with code: %" PRIu64, result->code);
65    }
66  }
67
68private:
69  rclcpp::Client<aimdk_msgs::srv::GetStoredMapByName>::SharedPtr client_;
70};
71
72int main(int argc, char **argv) {
73  rclcpp::init(argc, argv);
74
75  auto client = std::make_shared<MapClient>();
76
77  // Map name (NOT map_id) is required. To list stored maps, run:
78  //   sqlite3 /agibot/data/var/MapManagerModule/map.db "SELECT * FROM map;"
79  std::cout << "Enter map name: ";
80  std::string map_name;
81  std::getline(std::cin, map_name);
82  if (map_name.empty()) {
83    RCLCPP_ERROR(client->get_logger(), "Map name is empty, aborted.");
84    rclcpp::shutdown();
85    return 1;
86  }
87  client->send_request(map_name);
88
89  rclcpp::shutdown();
90  return 0;
91}

Usage Instructions:

ros2 run examples get_map
Enter map name: test_map
[INFO] [get_map_client]: Service call succeeded
[INFO] [get_map_client]: Map path: /path/to/test_map.png
[INFO] [get_map_client]: Map resolution: 0.050000
[INFO] [get_map_client]: Map width: 200
[INFO] [get_map_client]: Map height: 200
[INFO] [get_map_client]: Map id: 1774430080403

How to obtain these parameters: the input is the map name (map_name), not map_id; to obtain map_name, see Obtaining Map Identifiers and Coordinates.

6.2.30 Navigation

This example provides an interactive terminal menu for starting, pausing, resuming, and stopping navigation tasks.

  1#include <chrono>
  2#include <cinttypes>
  3#include <cstring>
  4#include <iostream>
  5#include <string>
  6#include <thread>
  7
  8#include <aimdk_msgs/msg/message_header.hpp>
  9#include <aimdk_msgs/msg/pnc_task_request.hpp>
 10#include <geometry_msgs/msg/pose_stamped.hpp>
 11#include <rclcpp/rclcpp.hpp>
 12
 13using namespace std::chrono_literals;
 14
 15// task_request enum
 16constexpr uint8_t TASK_REQUEST_START = 1;
 17constexpr uint8_t TASK_REQUEST_STOP = 2;
 18constexpr uint8_t TASK_REQUEST_PAUSE = 3;
 19constexpr uint8_t TASK_REQUEST_RESUME = 4;
 20
 21class NavigationNode : public rclcpp::Node {
 22public:
 23  NavigationNode() : Node("navigation_example") {
 24    publisher_ = this->create_publisher<aimdk_msgs::msg::PncTaskRequest>(
 25        "/aima/te/pnc_task_request", 10);
 26
 27    auto now_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
 28                      std::chrono::system_clock::now().time_since_epoch())
 29                      .count();
 30    task_id_ = static_cast<uint64_t>(now_ms);
 31    map_id_ = 0;
 32
 33    RCLCPP_INFO(this->get_logger(),
 34                "Navigation example node started, task_id=%" PRIu64, task_id_);
 35  }
 36
 37  void send_start(uint64_t map_id, double x, double y, double z, double ox,
 38                  double oy, double oz, double ow, double radius,
 39                  uint8_t pnc_mode, double max_speed) {
 40    map_id_ = map_id;
 41    auto msg = aimdk_msgs::msg::PncTaskRequest();
 42    msg.header = make_header();
 43    msg.task_type = 2;
 44    msg.task_request = TASK_REQUEST_START;
 45    msg.pnc_mode = pnc_mode;
 46    msg.max_forward_speed = max_speed;
 47    msg.task_id = task_id_;
 48    msg.map_id = map_id;
 49    msg.target_pose_radius = radius;
 50    std::memset(msg.reserve_info.data(), 0, msg.reserve_info.size());
 51
 52    auto &target = msg.target_pose;
 53    target.header.stamp = this->get_clock()->now();
 54    target.header.frame_id = "map";
 55    target.pose.position.x = x;
 56    target.pose.position.y = y;
 57    target.pose.position.z = z;
 58    target.pose.orientation.x = ox;
 59    target.pose.orientation.y = oy;
 60    target.pose.orientation.z = oz;
 61    target.pose.orientation.w = ow;
 62
 63    RCLCPP_INFO(this->get_logger(),
 64                "[Start] task_id=%" PRIu64 ", map_id=%" PRIu64 ", "
 65                "target=(%.2f, %.2f, %.2f), radius=%.2f",
 66                task_id_, map_id, x, y, z, radius);
 67    start_publishing(msg);
 68  }
 69
 70  void send_pause() {
 71    auto msg = make_simple_request(TASK_REQUEST_PAUSE, 0);
 72    RCLCPP_INFO(this->get_logger(), "[Pause] task_id=%" PRIu64, task_id_);
 73    start_publishing(msg);
 74  }
 75
 76  void send_resume() {
 77    auto msg = make_simple_request(TASK_REQUEST_RESUME, map_id_);
 78    RCLCPP_INFO(this->get_logger(),
 79                "[Resume] task_id=%" PRIu64 ", map_id=%" PRIu64, task_id_,
 80                map_id_);
 81    start_publishing(msg);
 82  }
 83
 84  void send_stop() {
 85    auto msg = make_simple_request(TASK_REQUEST_STOP, map_id_);
 86    RCLCPP_INFO(this->get_logger(),
 87                "[Stop] task_id=%" PRIu64 ", map_id=%" PRIu64, task_id_,
 88                map_id_);
 89    start_publishing(msg);
 90  }
 91
 92  void stop_publishing() {
 93    if (timer_) {
 94      timer_->cancel();
 95      timer_.reset();
 96      RCLCPP_INFO(this->get_logger(), "Stopped continuous publishing");
 97    }
 98  }
 99
100  uint64_t task_id() const { return task_id_; }
101
102private:
103  void start_publishing(const aimdk_msgs::msg::PncTaskRequest &msg) {
104    current_msg_ = msg;
105    if (timer_) {
106      timer_->cancel();
107    }
108    publisher_->publish(current_msg_);
109    timer_ = this->create_wall_timer(1s, [this]() {
110      current_msg_.header.stamp = this->get_clock()->now();
111      current_msg_.header.meas_stamp = this->get_clock()->now();
112      current_msg_.target_pose.header.stamp = this->get_clock()->now();
113      publisher_->publish(current_msg_);
114    });
115    RCLCPP_INFO(this->get_logger(), "Continuous publishing at 1Hz");
116  }
117
118  aimdk_msgs::msg::MessageHeader make_header() {
119    auto header = aimdk_msgs::msg::MessageHeader();
120    auto now = this->get_clock()->now();
121    header.stamp = now;
122    header.meas_stamp = now;
123    header.frame_id = "map";
124    header.sequence = 0;
125    return header;
126  }
127
128  aimdk_msgs::msg::PncTaskRequest make_simple_request(uint8_t task_request,
129                                                      uint64_t map_id) {
130    auto msg = aimdk_msgs::msg::PncTaskRequest();
131    msg.header = make_header();
132    msg.task_type = 2;
133    msg.task_request = task_request;
134    msg.pnc_mode = 0;
135    msg.max_forward_speed = 0.5;
136    msg.task_id = task_id_;
137    msg.map_id = map_id;
138    msg.target_pose_radius = 0.0;
139    std::memset(msg.reserve_info.data(), 0, msg.reserve_info.size());
140    msg.target_pose.pose.orientation.w = 1.0;
141    return msg;
142  }
143
144  rclcpp::Publisher<aimdk_msgs::msg::PncTaskRequest>::SharedPtr publisher_;
145  rclcpp::TimerBase::SharedPtr timer_;
146  aimdk_msgs::msg::PncTaskRequest current_msg_;
147  uint64_t task_id_;
148  uint64_t map_id_;
149};
150
151template <typename T> T input_value(const std::string &prompt, T default_val) {
152  std::cout << prompt << " [" << default_val << "]: ";
153  std::string line;
154  std::getline(std::cin, line);
155  if (line.empty()) {
156    return default_val;
157  }
158  if constexpr (std::is_same_v<T, uint64_t>) {
159    return std::stoull(line);
160  } else if constexpr (std::is_same_v<T, uint8_t>) {
161    return static_cast<uint8_t>(std::stoi(line));
162  } else if constexpr (std::is_integral_v<T>) {
163    return static_cast<T>(std::stoll(line));
164  } else {
165    return static_cast<T>(std::stod(line));
166  }
167}
168
169void print_menu(uint64_t task_id) {
170  std::cout << "\n========================================\n"
171            << "  Navigation Control  (task_id=" << task_id << ")\n"
172            << "========================================\n"
173            << "  1. Start navigation\n"
174            << "  2. Pause navigation\n"
175            << "  3. Resume navigation\n"
176            << "  4. Stop navigation\n"
177            << "  q. Quit\n"
178            << "========================================\n";
179}
180
181int main(int argc, char *argv[]) {
182  rclcpp::init(argc, argv);
183  auto node = std::make_shared<NavigationNode>();
184
185  std::thread spin_thread([node]() { rclcpp::spin(node); });
186
187  RCLCPP_INFO(node->get_logger(),
188              "Waiting for node communication ready (5s)...");
189  std::this_thread::sleep_for(5s);
190
191  std::string choice;
192  while (rclcpp::ok()) {
193    print_menu(node->task_id());
194    std::cout << "Select an option: ";
195    std::getline(std::cin, choice);
196
197    if (choice == "1") {
198      std::cout << "\n--- Start Navigation Parameters ---\n";
199      auto map_id = input_value<uint64_t>("Map ID (map_id)", 1773113429735ULL);
200      auto x = input_value<double>("Target x", 1.0);
201      auto y = input_value<double>("Target y", 2.0);
202      auto z = input_value<double>("Target z", 0.0);
203      auto ox = input_value<double>("Orientation x", 0.0);
204      auto oy = input_value<double>("Orientation y", 0.0);
205      auto oz = input_value<double>("Orientation z", 0.0);
206      auto ow = input_value<double>("Orientation w", 1.0);
207      auto radius = input_value<double>("Target pose radius", 0.5);
208      auto pnc_mode = input_value<uint8_t>("pnc_mode", 0);
209      auto max_speed = input_value<double>("Max forward speed", 0.5);
210      node->send_start(map_id, x, y, z, ox, oy, oz, ow, radius, pnc_mode,
211                       max_speed);
212    } else if (choice == "2") {
213      node->send_pause();
214    } else if (choice == "3") {
215      node->send_resume();
216    } else if (choice == "4") {
217      node->send_stop();
218    } else if (choice == "q" || choice == "Q") {
219      node->stop_publishing();
220      std::cout << "Exiting navigation control.\n";
221      break;
222    } else {
223      std::cout << "Invalid input, please try again.\n";
224    }
225  }
226
227  rclcpp::shutdown();
228  spin_thread.join();
229  return 0;
230}

Usage Instructions:

ros2 run examples navigation

Output Example:

[INFO] [navigation_example]: Navigation example node started, task_id=1709123456789
[INFO] [navigation_example]: Waiting for node communication ready (5s)...

========================================
  Navigation Control  (task_id=1709123456789)
========================================
  1. Start navigation
  2. Pause navigation
  3. Resume navigation
  4. Stop navigation
  q. Quit
========================================
Select an option: 1

--- Start Navigation Parameters ---
Map ID (map_id) [1773113429735]:
Target x [1]:
Target y [2]:
...
[INFO] [navigation_example]: [Start] task_id=1709123456789, map_id=1773113429735, target=(1.00, 2.00, 0.00), radius=0.50

When starting navigation, you need to enter parameters such as map ID, target pose, and arrival precision. Each has a default value; press Enter directly to use the default.

How to obtain these parameters: to obtain map_id, see Obtaining Map Identifiers and Coordinates; target_x / target_y are the target point in metric coordinates (map frame), different from the pixel coordinates used for relocalization — specify them according to the actual task.

6.2.31 Play LinkCraft Action

In this example, play_linkcraft is used to list the LinkCraft actions available on the robot and select one for playback.

  1//
  2// Created by agiuser on 2026/3/3.
  3//
  4
  5#include <aimdk_msgs/srv/execute_action_resource.hpp>
  6#include <aimdk_msgs/srv/get_robot_resources.hpp>
  7#include <cinttypes>
  8#include <iostream>
  9#include <rclcpp/rclcpp.hpp>
 10#include <string>
 11#include <vector>
 12
 13using namespace std::chrono_literals;
 14
 15class RobotResourceClient : public rclcpp::Node {
 16public:
 17  RobotResourceClient() : Node("robot_resource_client") {
 18    get_resource_client_ =
 19        this->create_client<aimdk_msgs::srv::GetRobotResources>(
 20            "/aimdk_5Fmsgs/srv/GetRobotResources");
 21    play_resource_client_ =
 22        this->create_client<aimdk_msgs::srv::ExecuteActionResource>(
 23            "/aimdk_5Fmsgs/srv/ExecuteActionResource");
 24  }
 25
 26  bool get_robot_resources() {
 27    while (!get_resource_client_->wait_for_service(1s)) {
 28      if (!rclcpp::ok()) {
 29        return false;
 30      }
 31      RCLCPP_INFO(this->get_logger(),
 32                  "Service not available, waiting again...");
 33    }
 34
 35    if (!rclcpp::ok()) {
 36      return false;
 37    }
 38
 39    auto request =
 40        std::make_shared<aimdk_msgs::srv::GetRobotResources::Request>();
 41
 42    auto future_result = get_resource_client_->async_send_request(request);
 43
 44    if (rclcpp::spin_until_future_complete(this->get_node_base_interface(),
 45                                           future_result) !=
 46        rclcpp::FutureReturnCode::SUCCESS) {
 47      if (rclcpp::ok()) {
 48        RCLCPP_ERROR(this->get_logger(), "Failed to call GetRobotResources");
 49      }
 50      return false;
 51    }
 52
 53    auto response = future_result.get();
 54    resources_ = response->robot_resources;
 55    RCLCPP_INFO(this->get_logger(), "Found %zu resources", resources_.size());
 56    return true;
 57  }
 58
 59  void print_resource_menu() {
 60    if (resources_.empty()) {
 61      std::cout << "(no resources)\n";
 62      return;
 63    }
 64
 65    std::cout << "\n=== Available Robot Resources ===\n";
 66    for (size_t i = 0; i < resources_.size(); ++i) {
 67      const auto &ver = resources_[i].current_version;
 68      std::cout << "[" << i << "] "
 69                << (ver.name.empty() ? "<no name>" : ver.name) << "\n";
 70      std::cout << "     key:     " << resources_[i].resource_key << "\n";
 71      std::cout << "     version: " << ver.version << "\n";
 72    }
 73    std::cout << "=================================\n\n";
 74  }
 75
 76  int pick_resource() {
 77    if (resources_.empty()) {
 78      return -1;
 79    }
 80
 81    std::string line;
 82    while (true) {
 83      std::cout << "Select resource index [0-" << resources_.size() - 1
 84                << "] (q to quit): ";
 85      if (!std::getline(std::cin, line)) {
 86        return -1;
 87      }
 88      if (line == "q" || line == "quit" || line == "exit") {
 89        return -1;
 90      }
 91      try {
 92        int idx = std::stoi(line);
 93        if (idx >= 0 && idx < static_cast<int>(resources_.size())) {
 94          return idx;
 95        }
 96        std::cout << "Index out of range (0-" << resources_.size() - 1
 97                  << ").\n";
 98      } catch (...) {
 99        std::cout << "Please enter a number.\n";
100      }
101    }
102  }
103
104  void play_resource(int idx) {
105    while (!play_resource_client_->wait_for_service(1s)) {
106      if (!rclcpp::ok()) {
107        RCLCPP_ERROR(this->get_logger(),
108                     "Interrupted while waiting for service.");
109        return;
110      }
111      RCLCPP_INFO(this->get_logger(), "Service not available, waiting...");
112    }
113
114    if (!rclcpp::ok()) {
115      return;
116    }
117
118    const auto &resource = resources_[idx];
119    auto request =
120        std::make_shared<aimdk_msgs::srv::ExecuteActionResource::Request>();
121
122    request->resource_key = resource.resource_key;
123    request->resource_version = resource.current_version.version;
124
125    if (request->resource_key.find("onnx") != std::string::npos) {
126      request->meta = R"({"resource_type": "BODY_MONTION"})";
127    } else {
128      request->meta = R"({"resource_type": "ARM_MONTION"})";
129    }
130
131    RCLCPP_INFO(this->get_logger(), "Sending request:");
132    RCLCPP_INFO(this->get_logger(), "  resource_key: %s",
133                request->resource_key.c_str());
134    RCLCPP_INFO(this->get_logger(), "  resource_version: %s",
135                request->resource_version.c_str());
136    RCLCPP_INFO(this->get_logger(), "  meta: %s", request->meta.c_str());
137
138    auto future = play_resource_client_->async_send_request(request);
139
140    if (rclcpp::spin_until_future_complete(this->get_node_base_interface(),
141                                           future) ==
142        rclcpp::FutureReturnCode::SUCCESS) {
143      handle_response(future.get());
144    } else {
145      if (rclcpp::ok()) {
146        RCLCPP_ERROR(this->get_logger(),
147                     "Failed to call ExecuteActionResource");
148      }
149    }
150  }
151
152private:
153  rclcpp::Client<aimdk_msgs::srv::GetRobotResources>::SharedPtr
154      get_resource_client_;
155  rclcpp::Client<aimdk_msgs::srv::ExecuteActionResource>::SharedPtr
156      play_resource_client_;
157  std::vector<aimdk_msgs::msg::RobotResource> resources_;
158
159  void handle_response(
160      const aimdk_msgs::srv::ExecuteActionResource::Response::SharedPtr
161          response) {
162    RCLCPP_INFO(this->get_logger(), "Service response received:");
163    RCLCPP_INFO(this->get_logger(), "Response Header:");
164    RCLCPP_INFO(this->get_logger(), "  stamp: %d.%09u",
165                response->header.header.stamp.sec,
166                response->header.header.stamp.nanosec);
167    RCLCPP_INFO(this->get_logger(), "  code: %" PRId64,
168                response->header.header.code);
169    RCLCPP_INFO(this->get_logger(), "Status:");
170    RCLCPP_INFO(this->get_logger(), "  value: %d",
171                response->header.status.value);
172    RCLCPP_INFO(this->get_logger(), "  message: %s",
173                response->header.message.c_str());
174  }
175};
176
177int main(int argc, char **argv) {
178  rclcpp::init(argc, argv);
179  auto client_node = std::make_shared<RobotResourceClient>();
180
181  if (!client_node->get_robot_resources()) {
182    rclcpp::shutdown();
183    return 1;
184  }
185
186  client_node->print_resource_menu();
187  int chosen = client_node->pick_resource();
188  if (chosen < 0) {
189    RCLCPP_INFO(client_node->get_logger(), "No resource selected, exiting.");
190    rclcpp::shutdown();
191    return 0;
192  }
193
194  client_node->play_resource(chosen);
195  rclcpp::shutdown();
196  return 0;
197}

Usage Instructions:

ros2 run examples play_linkcraft
[INFO] [1772529153.517267476] [robot_resource_client]: Found 1 resources

=== Available Robot Resources ===
[0] resource_key: linkcraft_resource_onnx_01KBM2BHNFM93Z0DK6ES9F6DJA
     key:     linkcraft_resource_onnx_01KBM2BHNFM93Z0DK6ES9F6DJA
     version: 0.0.1
=================================

Select resource index [0-0]: 0
[INFO] [1772529153.517488347] [robot_resource_client]: Sending request:
[INFO] [1772529153.517497834] [robot_resource_client]:   resource_key: linkcraft_resource_onnx_01KBM2BHNFM93Z0DK6ES9F6DJA
[INFO] [1772529153.517505889] [robot_resource_client]:   resource_version: 0.0.1
[INFO] [1772529153.517473238] [robot_resource_client]:   meta: {"resource_type": "BODY_MONTION"}
[INFO] [1772529153.553673433] [robot_resource_client]: Service response received:
[INFO] [1772529153.553697021] [robot_resource_client]: Response Header:
[INFO] [1772529153.553700410] [robot_resource_client]:   stamp: 0.000000000
[INFO] [1772529153.553704130] [robot_resource_client]:   code: 0
[INFO] [1772529153.553707001] [robot_resource_client]: Status:
[INFO] [1772529153.553709853] [robot_resource_client]:   value: 1
[INFO] [1772529153.553712662] [robot_resource_client]:   message: 1/1 task(s) failed. Details: localhost: failed (status code: 400) - Failed with status code: 400

6.2.32 Play LinkCraft Action (Multi-Robot)

In this example, multi_play_linkcraft is used to synchronize time across multiple robots and perform group-controlled LinkCraft action playback.

  1//
  2// Created by agiuser on 2026/3/3.
  3//
  4
  5#include <aimdk_msgs/msg/slave_device.hpp>
  6#include <aimdk_msgs/srv/execute_action_resource.hpp>
  7#include <aimdk_msgs/srv/get_robot_resources.hpp>
  8#include <aimdk_msgs/srv/group_control_time_sync.hpp>
  9#include <cinttypes>
 10#include <iostream>
 11#include <rclcpp/rclcpp.hpp>
 12#include <string>
 13#include <vector>
 14
 15using namespace std::chrono_literals;
 16
 17class MultiRobotResourceClient : public rclcpp::Node {
 18public:
 19  MultiRobotResourceClient() : Node("robot_resource_client") {
 20    get_resource_client_ =
 21        this->create_client<aimdk_msgs::srv::GetRobotResources>(
 22            "/aimdk_5Fmsgs/srv/GetRobotResources");
 23    play_resource_client_ =
 24        this->create_client<aimdk_msgs::srv::ExecuteActionResource>(
 25            "/aimdk_5Fmsgs/srv/ExecuteActionResource");
 26    time_sync_client_ =
 27        this->create_client<aimdk_msgs::srv::GroupControlTimeSync>(
 28            "/aimdk_5Fmsgs/srv/GroupControlTimeSync");
 29  }
 30
 31  bool get_robot_resources() {
 32    while (!get_resource_client_->wait_for_service(1s)) {
 33      if (!rclcpp::ok()) {
 34        return false;
 35      }
 36      RCLCPP_INFO(this->get_logger(),
 37                  "Service not available, waiting again...");
 38    }
 39
 40    if (!rclcpp::ok()) {
 41      return false;
 42    }
 43
 44    auto request =
 45        std::make_shared<aimdk_msgs::srv::GetRobotResources::Request>();
 46
 47    auto future_result = get_resource_client_->async_send_request(request);
 48
 49    if (rclcpp::spin_until_future_complete(this->get_node_base_interface(),
 50                                           future_result) !=
 51        rclcpp::FutureReturnCode::SUCCESS) {
 52      if (rclcpp::ok()) {
 53        RCLCPP_ERROR(this->get_logger(), "Failed to call GetRobotResources");
 54      }
 55      return false;
 56    }
 57
 58    auto response = future_result.get();
 59    resources_ = response->robot_resources;
 60    RCLCPP_INFO(this->get_logger(), "Found %zu resources", resources_.size());
 61    return true;
 62  }
 63
 64  void print_resource_menu() {
 65    if (resources_.empty()) {
 66      std::cout << "(no resources)\n";
 67      return;
 68    }
 69
 70    std::cout << "\n=== Available Robot Resources ===\n";
 71    for (size_t i = 0; i < resources_.size(); ++i) {
 72      const auto &ver = resources_[i].current_version;
 73      std::cout << "[" << i << "] "
 74                << (ver.name.empty() ? "<no name>" : ver.name) << "\n";
 75      std::cout << "     key:     " << resources_[i].resource_key << "\n";
 76      std::cout << "     version: " << ver.version << "\n";
 77    }
 78    std::cout << "=================================\n\n";
 79  }
 80
 81  int pick_resource() {
 82    if (resources_.empty()) {
 83      return -1;
 84    }
 85
 86    std::string line;
 87    while (true) {
 88      std::cout << "Select resource index [0-" << resources_.size() - 1
 89                << "] (q to quit): ";
 90      if (!std::getline(std::cin, line)) {
 91        return -1;
 92      }
 93      if (line == "q" || line == "quit" || line == "exit") {
 94        return -1;
 95      }
 96      try {
 97        int idx = std::stoi(line);
 98        if (idx >= 0 && idx < static_cast<int>(resources_.size())) {
 99          return idx;
100        }
101        std::cout << "Index out of range (0-" << resources_.size() - 1
102                  << ").\n";
103      } catch (...) {
104        std::cout << "Please enter a number.\n";
105      }
106    }
107  }
108
109  void play_resource(int idx) {
110    while (!play_resource_client_->wait_for_service(1s)) {
111      if (!rclcpp::ok()) {
112        RCLCPP_ERROR(this->get_logger(),
113                     "Interrupted while waiting for service.");
114        return;
115      }
116      RCLCPP_INFO(this->get_logger(), "Service not available, waiting...");
117    }
118
119    if (!rclcpp::ok()) {
120      return;
121    }
122
123    const auto &resource = resources_[idx];
124    auto request =
125        std::make_shared<aimdk_msgs::srv::ExecuteActionResource::Request>();
126
127    request->resource_key = resource.resource_key;
128    request->resource_version = resource.current_version.version;
129
130    for (const auto &ip : slave_ips_) {
131      aimdk_msgs::msg::SlaveDevice slave;
132      slave.ip = ip;
133      request->slaves.push_back(slave);
134    }
135
136    if (request->resource_key.find("onnx") != std::string::npos) {
137      request->meta =
138          R"({"control_type": "GROUP_CONTROL","resource_type": "BODY_MONTION"})";
139    } else {
140      request->meta =
141          R"({"control_type": "GROUP_CONTROL","resource_type": "ARM_MONTION"})";
142    }
143
144    RCLCPP_INFO(this->get_logger(), "Sending request:");
145    RCLCPP_INFO(this->get_logger(), "  resource_key: %s",
146                request->resource_key.c_str());
147    RCLCPP_INFO(this->get_logger(), "  resource_version: %s",
148                request->resource_version.c_str());
149    RCLCPP_INFO(this->get_logger(), "  meta: %s", request->meta.c_str());
150    for (size_t i = 0; i < request->slaves.size(); ++i) {
151      RCLCPP_INFO(this->get_logger(), "  slaves[%zu] ip: %s", i,
152                  request->slaves[i].ip.c_str());
153    }
154
155    auto future = play_resource_client_->async_send_request(request);
156
157    if (rclcpp::spin_until_future_complete(this->get_node_base_interface(),
158                                           future) ==
159        rclcpp::FutureReturnCode::SUCCESS) {
160      handle_response(future.get());
161    } else {
162      if (rclcpp::ok()) {
163        RCLCPP_ERROR(this->get_logger(),
164                     "Failed to call ExecuteActionResource");
165      }
166    }
167  }
168
169  bool sync_time(const std::vector<std::string> &ips) {
170    slave_ips_ = ips;
171
172    while (!time_sync_client_->wait_for_service(1s)) {
173      if (!rclcpp::ok()) {
174        return false;
175      }
176      RCLCPP_INFO(this->get_logger(),
177                  "TimeSync service not available, waiting again...");
178    }
179
180    if (!rclcpp::ok()) {
181      return false;
182    }
183
184    auto request =
185        std::make_shared<aimdk_msgs::srv::GroupControlTimeSync::Request>();
186
187    for (const auto &ip : ips) {
188      aimdk_msgs::msg::SlaveDevice slave;
189      slave.ip = ip;
190      request->slaves.push_back(slave);
191    }
192
193    RCLCPP_INFO(this->get_logger(), "Sending GroupControlTimeSync request:");
194    for (const auto &slave : request->slaves) {
195      RCLCPP_INFO(this->get_logger(), "  slave ip: %s", slave.ip.c_str());
196    }
197
198    auto future = time_sync_client_->async_send_request(request);
199
200    if (rclcpp::spin_until_future_complete(this->get_node_base_interface(),
201                                           future) !=
202        rclcpp::FutureReturnCode::SUCCESS) {
203      if (rclcpp::ok()) {
204        RCLCPP_ERROR(this->get_logger(), "Failed to call GroupControlTimeSync");
205      }
206      return false;
207    }
208
209    auto response = future.get();
210    RCLCPP_INFO(this->get_logger(), "GroupControlTimeSync response:");
211    RCLCPP_INFO(this->get_logger(), "  error_code: %d", response->error_code);
212    RCLCPP_INFO(this->get_logger(), "  error_msg: %s",
213                response->error_msg.c_str());
214    return response->error_code == 0;
215  }
216
217private:
218  rclcpp::Client<aimdk_msgs::srv::GetRobotResources>::SharedPtr
219      get_resource_client_;
220  rclcpp::Client<aimdk_msgs::srv::ExecuteActionResource>::SharedPtr
221      play_resource_client_;
222  rclcpp::Client<aimdk_msgs::srv::GroupControlTimeSync>::SharedPtr
223      time_sync_client_;
224  std::vector<aimdk_msgs::msg::RobotResource> resources_;
225  std::vector<std::string> slave_ips_;
226
227  void handle_response(
228      const aimdk_msgs::srv::ExecuteActionResource::Response::SharedPtr
229          response) {
230    RCLCPP_INFO(this->get_logger(), "Service response received:");
231    RCLCPP_INFO(this->get_logger(), "Response Header:");
232    RCLCPP_INFO(this->get_logger(), "  stamp: %d.%09u",
233                response->header.header.stamp.sec,
234                response->header.header.stamp.nanosec);
235    RCLCPP_INFO(this->get_logger(), "  code: %" PRId64,
236                response->header.header.code);
237    RCLCPP_INFO(this->get_logger(), "Status:");
238    RCLCPP_INFO(this->get_logger(), "  value: %d",
239                response->header.status.value);
240    RCLCPP_INFO(this->get_logger(), "  message: %s",
241                response->header.message.c_str());
242  }
243};
244
245int main(int argc, char **argv) {
246  rclcpp::init(argc, argv);
247  auto client_node = std::make_shared<MultiRobotResourceClient>();
248
249  if (!client_node->get_robot_resources()) {
250    rclcpp::shutdown();
251    return 1;
252  }
253
254  // [USER_CONFIG] Replace the IPs below with your slave robot IPs.
255  std::vector<std::string> slave_ips = {"172.22.133.178", "172.22.135.45"};
256  if (!client_node->sync_time(slave_ips)) {
257    RCLCPP_WARN(client_node->get_logger(),
258                "Time sync failed or returned error.");
259  }
260
261  client_node->print_resource_menu();
262  int chosen = client_node->pick_resource();
263  if (chosen < 0) {
264    RCLCPP_INFO(client_node->get_logger(), "No resource selected, exiting.");
265    rclcpp::shutdown();
266    return 0;
267  }
268
269  client_node->play_resource(chosen);
270  rclcpp::shutdown();
271  return 0;
272}

Usage Instructions:

ros2 run examples multi_play_linkcraft
[INFO] [1772529153.517267476] [robot_resource_client]: Found 1 resources
[INFO] [1772529153.517473238] [robot_resource_client]: Sending GroupControlTimeSync request:
[INFO] [1772529153.517488347] [robot_resource_client]:   slave ip: 172.22.133.178
[INFO] [1772529153.517497834] [robot_resource_client]:   slave ip: 172.22.135.45
[INFO] [1772529153.517505889] [robot_resource_client]: GroupControlTimeSync response:
[INFO] [1772529153.517506000] [robot_resource_client]:   error_code: 0
[INFO] [1772529153.517506100] [robot_resource_client]:   error_msg: ok
[INFO] [1772529153.517473238] [robot_resource_client]: Sending request:
[INFO] [1772529153.517488347] [robot_resource_client]:   resource_key: linkcraft_resource_onnx_01KBM2BHNFM93Z0DK6ES9F6DJA
[INFO] [1772529153.517497834] [robot_resource_client]:   resource_version: 0.0.1
[INFO] [1772529153.517505889] [robot_resource_client]:   meta: {"control_type": "GROUP_CONTROL","resource_type": "BODY_MONTION"}
[INFO] [1772529153.517506200] [robot_resource_client]:   slaves[0] ip: 172.22.133.178
[INFO] [1772529153.517506300] [robot_resource_client]:   slaves[1] ip: 172.22.135.45
[INFO] [1772529153.553673433] [robot_resource_client]: Service response received:
[INFO] [1772529153.553697021] [robot_resource_client]: Response Header:
[INFO] [1772529153.553700410] [robot_resource_client]:   stamp: 0.000000000
[INFO] [1772529153.553704130] [robot_resource_client]:   code: 0
[INFO] [1772529153.553707001] [robot_resource_client]: Status:
[INFO] [1772529153.553709853] [robot_resource_client]:   value: 1
[INFO] [1772529153.553712662] [robot_resource_client]:   message: 1/1 task(s) failed. Details: localhost: failed (status code: 400) - Failed with status code: 400

6.2.33 LED Strip Control

Function Description: Demonstrates how to control the robot’s LED strip, supporting multiple display modes and customizable colors.

Attention

Before using this feature, run the following command on the Motion Control Computing Unit (PC1) to stop the task_manager module and prevent it from competing with your program for LED strip control:

aima em stop-app task_manager

Core Code:

  1#include <aimdk_msgs/msg/common_request.hpp>
  2#include <aimdk_msgs/srv/set_pmu_led.hpp>
  3#include <algorithm>
  4#include <cctype>
  5#include <chrono>
  6#include <iostream>
  7#include <memory>
  8#include <rclcpp/rclcpp.hpp>
  9#include <signal.h>
 10#include <string>
 11
 12std::shared_ptr<rclcpp::Node> g_node = nullptr;
 13
 14void signal_handler(int signal) {
 15  if (g_node) {
 16    RCLCPP_INFO(g_node->get_logger(), "Received signal %d, shutting down...",
 17                signal);
 18    g_node.reset();
 19  }
 20  rclcpp::shutdown();
 21  exit(signal);
 22}
 23
 24class PlayLightsClient : public rclcpp::Node {
 25public:
 26  PlayLightsClient() : Node("play_lights_client") {
 27    client_ = this->create_client<aimdk_msgs::srv::SetPmuLed>(
 28        "/aimdk_5Fmsgs/srv/SetPmuLed");
 29    RCLCPP_INFO(this->get_logger(), "✅ PlayLights client node started.");
 30
 31    // Wait for the service to become available
 32    while (!client_->wait_for_service(std::chrono::seconds(2))) {
 33      RCLCPP_INFO(this->get_logger(), "⏳ Service unavailable, waiting...");
 34    }
 35    RCLCPP_INFO(this->get_logger(),
 36                "🟢 Service available, ready to send request.");
 37  }
 38
 39  bool send_request(uint8_t led_mode, uint8_t r, uint8_t g, uint8_t b,
 40                    int32_t priority = 6, bool reset_priority = false) {
 41    try {
 42      auto request = std::make_shared<aimdk_msgs::srv::SetPmuLed::Request>();
 43
 44      request->led_strip_mode = led_mode;
 45      request->r = r;
 46      request->g = g;
 47      request->b = b;
 48      request->priority = priority;
 49      request->reset_priority = reset_priority;
 50      request->trace_id = "play_lights";
 51
 52      RCLCPP_INFO(this->get_logger(),
 53                  "📨 Sending request to control led strip: mode=%hhu, "
 54                  "RGB=(%hhu, %hhu, %hhu), priority=%d, reset_priority=%s",
 55                  led_mode, r, g, b, priority,
 56                  reset_priority ? "true" : "false");
 57
 58      // LED strip is slow to response (up to ~5s)
 59      const std::chrono::milliseconds timeout(5000);
 60      for (int i = 0; i < 4; i++) {
 61        request->request.header.stamp = this->now();
 62        auto future = client_->async_send_request(request);
 63        auto retcode = rclcpp::spin_until_future_complete(
 64            this->shared_from_this(), future, timeout);
 65
 66        if (retcode != rclcpp::FutureReturnCode::SUCCESS) {
 67          // retry as remote peer is NOT handled well by ROS
 68          RCLCPP_INFO(this->get_logger(), "trying ... [%d]", i);
 69          continue;
 70        }
 71        // future.done
 72        auto response = future.get();
 73        if (response->status_code == 0) {
 74          RCLCPP_INFO(this->get_logger(),
 75                      "✅ LED strip command sent successfully.");
 76          return true;
 77        } else {
 78          // 0x1024: priority insufficient (priority < current threshold);
 79          // use a higher priority or set reset_priority=true to reset the
 80          // threshold to 0.
 81          RCLCPP_ERROR(this->get_logger(),
 82                       "❌ LED strip command failed with status: 0x%X (%s)",
 83                       response->status_code,
 84                       response->status_code == 0x1024 ? "priority insufficient"
 85                                                       : "failure");
 86          return false;
 87        }
 88      }
 89      RCLCPP_ERROR(this->get_logger(), "❌ Service call failed or timed out.");
 90      return false;
 91    } catch (const std::exception &e) {
 92      RCLCPP_ERROR(this->get_logger(), "Exception occurred: %s", e.what());
 93      return false;
 94    }
 95  }
 96
 97private:
 98  rclcpp::Client<aimdk_msgs::srv::SetPmuLed>::SharedPtr client_;
 99};
100
101int main(int argc, char **argv) {
102  try {
103    rclcpp::init(argc, argv);
104    signal(SIGINT, signal_handler);
105    signal(SIGTERM, signal_handler);
106
107    g_node = std::make_shared<PlayLightsClient>();
108    auto client_node = std::dynamic_pointer_cast<PlayLightsClient>(g_node);
109
110    int led_mode = 0;          // LED Strip Mode
111    int r = 255, g = 0, b = 0; // RGB values
112    int priority = 6;          // Priority
113    bool reset_priority = false;
114
115    // Parse command-line arguments: mode R G B priority reset_priority
116    // e.g. ros2 run examples play_lights 1 255 0 0 6 false
117    if (argc > 6) {
118      led_mode = std::stoi(argv[1]);
119      if (led_mode < 0 || led_mode > 3) {
120        throw std::runtime_error("invalid mode");
121      }
122      r = std::stoi(argv[2]);
123      if (r < 0 || r > 255) {
124        throw std::runtime_error("invalid R value");
125      }
126      g = std::stoi(argv[3]);
127      if (g < 0 || g > 255) {
128        throw std::runtime_error("invalid G value");
129      }
130      b = std::stoi(argv[4]);
131      if (b < 0 || b > 255) {
132        throw std::runtime_error("invalid B value");
133      }
134      priority = std::stoi(argv[5]);
135      if (priority < 0) {
136        throw std::runtime_error("invalid priority value");
137      }
138      std::string reset_str = argv[6];
139      std::transform(reset_str.begin(), reset_str.end(), reset_str.begin(),
140                     ::tolower);
141      reset_priority = (reset_str == "true" || reset_str == "1" ||
142                        reset_str == "yes" || reset_str == "y");
143    } else {
144      std::cout << "=== LED strip control example ===" << std::endl;
145      std::cout << "Select LED strip mode:" << std::endl;
146      std::cout << "0 - Steady on" << std::endl;
147      std::cout << "1 - Breathing (4s cycle, sine brightness)" << std::endl;
148      std::cout << "2 - Blinking (1s cycle, 0.5s on, 0.5s off)" << std::endl;
149      std::cout << "3 - Flowing (2s cycle, light up from left to right)"
150                << std::endl;
151      std::cout << "Enter mode (0-3): ";
152      std::cin >> led_mode;
153      if (led_mode < 0 || led_mode > 3) {
154        throw std::runtime_error("invalid mode");
155      }
156
157      std::cout << "\nSet RGB color values (0-255):" << std::endl;
158      std::cout << "Red (R): ";
159      std::cin >> r;
160      if (r < 0 || r > 255) {
161        throw std::runtime_error("invalid R value");
162      }
163      std::cout << "Green (G): ";
164      std::cin >> g;
165      if (g < 0 || g > 255) {
166        throw std::runtime_error("invalid G value");
167      }
168      std::cout << "Blue (B): ";
169      std::cin >> b;
170      if (b < 0 || b > 255) {
171        throw std::runtime_error("invalid B value");
172      }
173
174      std::cout
175          << "\nSet priority (higher value = higher priority, default=6): ";
176      std::cin >> priority;
177      if (priority < 0) {
178        throw std::runtime_error("invalid priority value");
179      }
180
181      std::string reset_input;
182      std::cout << "Reset priority after command? (y/n, default=n): ";
183      std::cin >> reset_input;
184      std::transform(reset_input.begin(), reset_input.end(),
185                     reset_input.begin(), ::tolower);
186      reset_priority = (reset_input == "true" || reset_input == "1" ||
187                        reset_input == "yes" || reset_input == "y");
188    }
189
190    if (client_node) {
191      client_node->send_request(led_mode, r, g, b, priority, reset_priority);
192    }
193
194    g_node.reset();
195    rclcpp::shutdown();
196
197    return 0;
198  } catch (const std::exception &e) {
199    RCLCPP_ERROR(rclcpp::get_logger("main"),
200                 "Program terminated with exception: %s", e.what());
201    return 1;
202  }
203}

Usage Instructions:

# Build
colcon build --packages-select examples

# Run interactively
ros2 run examples play_lights

# Run with command-line parameters: <mode> <R> <G> <B> <priority> <reset_priority(true/false)>
ros2 run examples play_lights 1 255 0 0 6 false  # Mode 1, red, priority 6
ros2 run examples set_mic_source

# Run with argument
ros2 run examples set_mic_source ext  # ext - external mic, int - internal mic

Output Example:

=== LED strip control example ===
Select LED strip mode:
0 - Steady on
1 - Breathing (4s cycle, sine brightness)
2 - Blinking (1s cycle, 0.5s on, 0.5s off)
3 - Flowing (2s cycle, light up from left to right)
Enter mode (0-3): 1

Set RGB color values (0-255):
Red (R): 255
Green (G): 0
Blue (B): 0

Set priority (higher value = higher priority, default=6): 6
Reset priority after command? (y/n, default=n): n

[INFO] [play_lights_client]: 📨 Sending request to control led strip: mode=1, RGB=(255, 0, 0), priority=6, reset_priority=false
[INFO] [play_lights_client]: ✅ LED strip command sent successfully.

Technical Features:

  • Supports 4 LED display modes

  • Customizable RGB colors

  • Priority control (higher value = higher priority)

  • Priority reset support

  • Asynchronous service calls

  • Command-line parameter support

  • Input parameter validation

  • User-friendly interactive interface

6.2.34 GNSS Positioning Data Subscription

This example uses echo_gnss_data to subscribe to GNSS topics for positioning data, outputting latitude, longitude, altitude, and covariance information.

Features:

  • Subscribes to NavSatFix positioning data (latitude, longitude, altitude)

  • Real-time display of positioning status and covariance

Topics:

  • /aima/hal/sensor/gnss: Positioning data (sensor_msgs/NavSatFix), published automatically by the system

 1#include <chrono>
 2#include <iomanip>
 3#include <rclcpp/rclcpp.hpp>
 4#include <sensor_msgs/msg/nav_sat_fix.hpp>
 5#include <sstream>
 6#include <string>
 7
 8class GnssPrinter : public rclcpp::Node {
 9public:
10  GnssPrinter() : rclcpp::Node("gnss_printer") {
11    constexpr char kNavSatTopic[] = "/aima/hal/sensor/gnss";
12
13    auto qos = rclcpp::QoS(rclcpp::KeepLast(10))
14                   .reliability(rclcpp::ReliabilityPolicy::Reliable)
15                   .durability(rclcpp::DurabilityPolicy::TransientLocal);
16
17    navsat_sub_ = this->create_subscription<sensor_msgs::msg::NavSatFix>(
18        kNavSatTopic, qos,
19        std::bind(&GnssPrinter::navsat_cb, this, std::placeholders::_1));
20
21    RCLCPP_INFO(this->get_logger(), "Subscribing NavSatFix: %s", kNavSatTopic);
22  }
23
24private:
25  void navsat_cb(const sensor_msgs::msg::NavSatFix::SharedPtr msg) {
26    auto now = std::chrono::steady_clock::now();
27    double dt_ms =
28        last_navsat_recv_.time_since_epoch().count() == 0
29            ? 0.0
30            : std::chrono::duration<double, std::milli>(now - last_navsat_recv_)
31                  .count();
32    last_navsat_recv_ = now;
33
34    std::ostringstream oss;
35    oss << std::fixed << std::setprecision(6);
36    oss << "\n========== NavSatFix ==========\n";
37    oss << "stamp=" << rclcpp::Time(msg->header.stamp).seconds() << "s\n";
38    oss << "frame=" << msg->header.frame_id << "\n";
39    oss << "recv_dt=" << std::setprecision(3) << dt_ms << "ms\n";
40    oss << std::setprecision(8);
41    oss << "latitude:  " << msg->latitude << " deg\n";
42    oss << "longitude: " << msg->longitude << " deg\n";
43    oss << std::setprecision(3);
44    oss << "altitude:  " << msg->altitude << " m\n";
45
46    const char *status_str = "UNKNOWN";
47    switch (msg->status.status) {
48    case sensor_msgs::msg::NavSatStatus::STATUS_NO_FIX:
49      status_str = "NO_FIX";
50      break;
51    case sensor_msgs::msg::NavSatStatus::STATUS_FIX:
52      status_str = "FIX";
53      break;
54    }
55    oss << "status: " << status_str << "\n";
56    oss << "service: " << msg->status.service << "\n";
57
58    oss << std::setprecision(4);
59    oss << "position_covariance: [";
60    for (size_t i = 0; i < 9; ++i) {
61      if (i > 0)
62        oss << ", ";
63      oss << msg->position_covariance[i];
64    }
65    oss << "]\n";
66    oss << "position_covariance_type: "
67        << static_cast<int>(msg->position_covariance_type);
68
69    RCLCPP_INFO(this->get_logger(), "%s", oss.str().c_str());
70  }
71
72  rclcpp::Subscription<sensor_msgs::msg::NavSatFix>::SharedPtr navsat_sub_;
73  std::chrono::steady_clock::time_point last_navsat_recv_{};
74};
75
76int main(int argc, char **argv) {
77  rclcpp::init(argc, argv);
78  rclcpp::spin(std::make_shared<GnssPrinter>());
79  rclcpp::shutdown();
80  return 0;
81}

Usage Instructions:

# Build
colcon build --packages-select examples

# Subscribe to GNSS data
ros2 run examples echo_gnss_data

Output Example:

[INFO] [gnss_printer]: Subscribing NavSatFix: /aima/hal/sensor/gnss

========== NavSatFix ==========
stamp=1234567890.123456s
frame=gnss
recv_dt=1000.000ms
latitude:  31.84537000 deg
longitude: 117.19882000 deg
altitude:  30.000 m
status: FIX
service: 1
position_covariance: [1.0000, 0.0000, 0.0000, 0.0000, 1.0000, 0.0000, 0.0000, 0.0000, 1.0000]
position_covariance_type: 2

Notes:

  • Positioning status: NO_FIX (no fix), FIX (single point fix)

  • Indoors, status is usually NO_FIX; move to an open outdoor area and wait for a fix

6.2.35 Upper Body Control

Note

Switch to the corresponding mode before running. See Upper Body Control.

  1// Upper body control example.
  2//
  3// Usage:
  4//   ros2 run examples upper_body_control <mode>
  5//
  6//   mode:
  7//     head    — head center pose,          requires HEAD_ONLY mode
  8//     claw    — claw half-open,            requires UPPERBODY_REMOTE_SPLIT mode
  9//     joint   — dexterous hand half-open,  requires UPPERBODY_REMOTE_SPLIT mode
 10//     gesture — dexterous hand gesture 1,  requires UPPERBODY_REMOTE_SPLIT mode
 11//
 12// Switch to the desired MC mode before running, e.g. for head:
 13//   ros2 run examples set_mc_action SD     # → STAND_DEFAULT
 14//   ros2 run examples set_mc_action HO     # → HEAD_ONLY
 15// or for claw / joint / gesture:
 16//   ros2 run examples set_mc_action URS    # → UPPERBODY_REMOTE_SPLIT
 17
 18#include <array>
 19#include <chrono>
 20#include <cstdint>
 21#include <cstdio>
 22#include <map>
 23#include <memory>
 24#include <string>
 25#include <vector>
 26
 27#include "aimdk_msgs/msg/message_header.hpp"
 28#include "aimdk_msgs/msg/upper_body_command_array.hpp"
 29#include "rclcpp/rclcpp.hpp"
 30
 31using namespace std::chrono_literals;
 32using Msg = aimdk_msgs::msg::UpperBodyCommandArray;
 33
 34// hand_sub_mode, head_pos, arm_pos, hand_pos
 35struct ModeConfig {
 36  uint8_t hand_sub_mode;
 37  std::array<double, 2> head_pos;
 38  std::array<double, 14> arm_pos;
 39  std::vector<double> hand_pos;
 40};
 41
 42static const std::map<std::string, ModeConfig> &modes() {
 43  static const std::map<std::string, ModeConfig> kModes = {
 44      // head_yaw=0, head_pitch=0 (center)
 45      {"head",
 46       {0,
 47        {{0.0, 0.0}},
 48        {{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
 49          0.0}},
 50        {}}},
 51      // left_open=0.5, right_open=0.5
 52      {"claw",
 53       {Msg::HAND_CLAW_OPEN_CLOSE, // claw (1)
 54        {{0.0, 0.0}},
 55        {{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
 56          0.0}},
 57        {0.5, 0.5}}},
 58      // left 10 joints + right 10 joints, in rad, all half-open
 59      // (each joint at half its URDF range limit). Right is the mirror of left.
 60      // Joint order: [thumb_roll, thumb_abad, thumb_mcp, index_abad, index_pip,
 61      //               middle_pip, ring_abad, ring_pip, pinky_abad, pinky_pip]
 62      {"joint",
 63       {Msg::HAND_DEXTEROUS_JOINT, // dexterous joint (2)
 64        {{0.0, 0.0}},
 65        {{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
 66          0.0}},
 67        {-0.6, 0.8,  -0.4, 0.1,  0.7, 0.7, -0.1, 0.7, -0.1, 0.7,
 68         0.6,  -0.8, 0.4,  -0.1, 0.7, 0.7, 0.1,  0.7, 0.1,  0.7}}},
 69      // left_gesture=1, left_open=1.0, right_gesture=1, right_open=1.0
 70      {"gesture",
 71       {Msg::HAND_DEXTEROUS_GESTURE, // dexterous gesture (3)
 72        {{0.0, 0.0}},
 73        {{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
 74          0.0}},
 75        {1.0, 1.0, 1.0, 1.0}}},
 76  };
 77  return kModes;
 78}
 79
 80class UpperBodyControlNode : public rclcpp::Node {
 81public:
 82  UpperBodyControlNode(const std::string &mode)
 83      : Node("upper_body_control"), seq_(0) {
 84    const auto &cfg = modes().at(mode);
 85    hand_sub_mode_ = cfg.hand_sub_mode;
 86    head_pos_ = cfg.head_pos;
 87    arm_pos_ = cfg.arm_pos;
 88    hand_pos_ = cfg.hand_pos;
 89    pub_ = create_publisher<Msg>("/mc/upper_body_command", 10);
 90    timer_ = create_wall_timer(20ms, [this]() { publish(); });
 91    RCLCPP_INFO(get_logger(), "mode=%s  hand_sub_mode=%d", mode.c_str(),
 92                hand_sub_mode_);
 93  }
 94
 95private:
 96  void publish() {
 97    Msg msg;
 98    auto t = this->now();
 99    msg.header.stamp.sec = static_cast<int32_t>(t.seconds());
100    msg.header.stamp.nanosec =
101        static_cast<uint32_t>(t.nanoseconds() % 1000000000);
102    msg.header.frame_id = "mc_upper_body";
103    msg.header.sequence = seq_++;
104    msg.source = "upper_body_example";
105    msg.hand_sub_mode = hand_sub_mode_;
106    msg.head_pos = head_pos_;
107    msg.arm_pos = arm_pos_;
108    msg.hand_pos = hand_pos_;
109    pub_->publish(msg);
110  }
111
112  rclcpp::Publisher<Msg>::SharedPtr pub_;
113  rclcpp::TimerBase::SharedPtr timer_;
114  uint32_t seq_;
115  uint8_t hand_sub_mode_;
116  std::array<double, 2> head_pos_;
117  std::array<double, 14> arm_pos_;
118  std::vector<double> hand_pos_;
119};
120
121int main(int argc, char **argv) {
122  rclcpp::init(argc, argv);
123
124  std::string mode;
125  if (argc >= 2)
126    mode = argv[1];
127
128  if (modes().find(mode) == modes().end()) {
129    std::string usage = "Usage: upper_body_control <";
130    bool first = true;
131    for (const auto &[k, _] : modes()) {
132      if (!first)
133        usage += "|";
134      usage += k;
135      first = false;
136    }
137    usage += ">";
138    std::printf("%s\n", usage.c_str());
139    rclcpp::shutdown();
140    return 1;
141  }
142
143  rclcpp::spin(std::make_shared<UpperBodyControlNode>(mode));
144  rclcpp::shutdown();
145  return 0;
146}

Usage Instructions:

colcon build --packages-select examples

# Head control (HEAD_ONLY mode)
ros2 run examples upper_body_control head

# Claw (gripper) control (UPPERBODY_REMOTE_SPLIT mode)
ros2 run examples upper_body_control claw

# Dexterous hand joints (UPPERBODY_REMOTE_SPLIT mode)
ros2 run examples upper_body_control joint

# Dexterous hand gesture (UPPERBODY_REMOTE_SPLIT mode)
ros2 run examples upper_body_control gesture

6.2.36 Subscribe to HDS Diagnostics / Query Historical Events

Subscribe to /aima/hds/diag_code_list, /aima/hds/alert_code_list, and /aima/hds/post_proc/signal topics to receive diagnostic codes, alert codes, and post-processing signals in real time; also supports calling the GetHdsEventHistory service to query historical events. For the full list of diagnostic codes, see Diagnostic Code Reference.

  1// HDS Diagnostics Tool — Subscribe to HDS topics or query history events.
  2//
  3// See docs/cn/example/Cpp.md section "Subscribe HDS diagnostics /
  4// query history events" for full usage and field descriptions.
  5
  6#include <chrono>
  7#include <cstdint>
  8#include <functional>
  9#include <iomanip>
 10#include <iostream>
 11#include <memory>
 12#include <sstream>
 13#include <string>
 14#include <vector>
 15
 16#include <rclcpp/rclcpp.hpp>
 17
 18#include "aimdk_msgs/msg/alert_code_array.hpp"
 19#include "aimdk_msgs/msg/diagnostic_info_array.hpp"
 20#include "aimdk_msgs/msg/diagnostic_type.hpp"
 21#include "aimdk_msgs/msg/hds_event_query.hpp"
 22#include "aimdk_msgs/msg/post_proc_sig_array.hpp"
 23#include "aimdk_msgs/srv/get_hds_event_history.hpp"
 24
 25using namespace std::chrono_literals;
 26
 27namespace {
 28
 29std::string DiagTypeName(int32_t value) {
 30  switch (value) {
 31  case aimdk_msgs::msg::DiagnosticType::NORMAL:
 32    return "NORMAL";
 33  case aimdk_msgs::msg::DiagnosticType::TRIGGER_APPEAR:
 34    return "APPEAR";
 35  case aimdk_msgs::msg::DiagnosticType::TRIGGER_DISAPPEAR:
 36    return "DISAPPEAR";
 37  default:
 38    return "UNKNOWN(" + std::to_string(value) + ")";
 39  }
 40}
 41
 42std::string StateName(uint8_t value) {
 43  switch (value) {
 44  case aimdk_msgs::msg::HdsEventQuery::STATE_ALL:
 45    return "ALL";
 46  case aimdk_msgs::msg::HdsEventQuery::STATE_ACTIVE:
 47    return "ACTIVE";
 48  case aimdk_msgs::msg::HdsEventQuery::STATE_RECOVERED:
 49    return "RECOVERED";
 50  default:
 51    return "UNKNOWN";
 52  }
 53}
 54
 55std::string HexCode(uint64_t code, int width = 10) {
 56  std::ostringstream oss;
 57  oss << "0x" << std::hex << std::uppercase << std::setw(width)
 58      << std::setfill('0') << code;
 59  return oss.str();
 60}
 61
 62uint64_t ParseHex(const std::string &s) { return std::stoull(s, nullptr, 0); }
 63
 64// ---------- echo mode ----------
 65
 66class HdsDiagnosticsEcho : public rclcpp::Node {
 67public:
 68  HdsDiagnosticsEcho() : Node("hds_diagnostics_echo") {
 69    // HDS publishers use BEST_EFFORT; match it to receive messages.
 70    auto qos = rclcpp::QoS(10).best_effort().durability_volatile();
 71
 72    sub_diag_ = create_subscription<aimdk_msgs::msg::DiagnosticInfoArray>(
 73        "/aima/hds/diag_code_list", qos,
 74        std::bind(&HdsDiagnosticsEcho::OnDiagnostics, this,
 75                  std::placeholders::_1));
 76
 77    sub_alert_ = create_subscription<aimdk_msgs::msg::AlertCodeArray>(
 78        "/aima/hds/alert_code_list", qos,
 79        std::bind(&HdsDiagnosticsEcho::OnAlerts, this, std::placeholders::_1));
 80
 81    sub_post_ = create_subscription<aimdk_msgs::msg::PostProcSigArray>(
 82        "/aima/hds/post_proc/signal", qos,
 83        std::bind(&HdsDiagnosticsEcho::OnPostProc, this,
 84                  std::placeholders::_1));
 85
 86    RCLCPP_INFO(get_logger(), "Subscribed: diag_code_list / alert_code_list / "
 87                              "post_proc/signal");
 88  }
 89
 90private:
 91  void
 92  OnDiagnostics(const aimdk_msgs::msg::DiagnosticInfoArray::SharedPtr msg) {
 93    if (msg->diagnostics.empty())
 94      return;
 95    std::ostringstream oss;
 96    oss << "[Diagnostics] count=" << msg->diagnostics.size();
 97    for (const auto &d : msg->diagnostics) {
 98      oss << "\n  diag_code=" << HexCode(d.diag_code)
 99          << "  type=" << DiagTypeName(d.type.value)
100          << "  module_id=" << static_cast<int>(d.module_id)
101          << "  ts=" << d.timestamp << "  info=\"" << d.info << "\"";
102    }
103    RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
104  }
105
106  void OnAlerts(const aimdk_msgs::msg::AlertCodeArray::SharedPtr msg) {
107    if (msg->list.empty())
108      return;
109    std::ostringstream oss;
110    oss << "[Alerts] count=" << msg->list.size();
111    for (const auto &a : msg->list) {
112      oss << "\n  code=\"" << a.code << "\"  ts=" << a.timestamp
113          << "  first_ts=" << a.first_timestamp;
114    }
115    RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
116  }
117
118  void OnPostProc(const aimdk_msgs::msg::PostProcSigArray::SharedPtr msg) {
119    if (msg->post_proc_sig.empty())
120      return;
121    std::ostringstream oss;
122    oss << "[PostProc] count=" << msg->post_proc_sig.size();
123    for (const auto &p : msg->post_proc_sig) {
124      const auto &s = p.sig_set;
125      oss << "\n  diag_code=" << HexCode(p.diag_code) << "  ts=" << p.timestamp
126          << "  robot=" << s.robot_sig
127          << " motion=" << static_cast<int>(s.motion_sig)
128          << " sound=" << static_cast<int>(s.sound_sig)
129          << " led=" << static_cast<int>(s.led_sig)
130          << " facial=" << static_cast<int>(s.facial_sig)
131          << " funcdisable=" << HexCode(s.funcdisable_sig, 16);
132    }
133    RCLCPP_INFO(get_logger(), "%s", oss.str().c_str());
134  }
135
136  rclcpp::Subscription<aimdk_msgs::msg::DiagnosticInfoArray>::SharedPtr
137      sub_diag_;
138  rclcpp::Subscription<aimdk_msgs::msg::AlertCodeArray>::SharedPtr sub_alert_;
139  rclcpp::Subscription<aimdk_msgs::msg::PostProcSigArray>::SharedPtr sub_post_;
140};
141
142int RunEcho(int argc, char **argv) {
143  rclcpp::init(argc, argv);
144  rclcpp::spin(std::make_shared<HdsDiagnosticsEcho>());
145  rclcpp::shutdown();
146  return 0;
147}
148
149// ---------- history mode ----------
150
151struct HistoryArgs {
152  uint64_t start_ns = 0;
153  uint64_t end_ns = 0;
154  std::vector<uint64_t> diag_codes;
155  std::vector<std::string> alert_codes;
156  uint8_t state = aimdk_msgs::msg::HdsEventQuery::STATE_ALL;
157  bool desc = false;
158  uint32_t offset = 0;
159  uint32_t limit = 20;
160};
161
162bool ParseHistoryArgs(int argc, char **argv, HistoryArgs *args) {
163  // argv[0] = exe, argv[1] = "history", real options start at 2.
164  for (int i = 2; i < argc; ++i) {
165    std::string a = argv[i];
166    auto need_value = [&](const char *flag) -> const char * {
167      if (i + 1 >= argc) {
168        std::cerr << "Missing value for " << flag << std::endl;
169        return nullptr;
170      }
171      return argv[++i];
172    };
173
174    if (a == "--start-ns") {
175      const char *v = need_value("--start-ns");
176      if (!v)
177        return false;
178      args->start_ns = std::stoull(v);
179    } else if (a == "--end-ns") {
180      const char *v = need_value("--end-ns");
181      if (!v)
182        return false;
183      args->end_ns = std::stoull(v);
184    } else if (a == "--state") {
185      const char *v = need_value("--state");
186      if (!v)
187        return false;
188      args->state = static_cast<uint8_t>(std::stoi(v));
189    } else if (a == "--offset") {
190      const char *v = need_value("--offset");
191      if (!v)
192        return false;
193      args->offset = static_cast<uint32_t>(std::stoul(v));
194    } else if (a == "--limit") {
195      const char *v = need_value("--limit");
196      if (!v)
197        return false;
198      args->limit = static_cast<uint32_t>(std::stoul(v));
199    } else if (a == "--desc") {
200      args->desc = true;
201    } else if (a == "--diag-codes") {
202      while (i + 1 < argc && std::string(argv[i + 1]).rfind("--", 0) != 0) {
203        args->diag_codes.push_back(ParseHex(argv[++i]));
204      }
205    } else if (a == "--alert-codes") {
206      while (i + 1 < argc && std::string(argv[i + 1]).rfind("--", 0) != 0) {
207        args->alert_codes.emplace_back(argv[++i]);
208      }
209    } else {
210      std::cerr << "Unknown option: " << a << std::endl;
211      return false;
212    }
213  }
214  return true;
215}
216
217void PrintRecords(
218    const aimdk_msgs::srv::GetHdsEventHistory::Response &response) {
219  std::cout << "total_count = " << response.total_count
220            << ", returned = " << response.records.size()
221            << ", has_more = " << (response.has_more ? "true" : "false")
222            << std::endl;
223  if (response.records.empty())
224    return;
225
226  std::string sep(80, '-');
227  std::cout << sep << std::endl;
228  for (const auto &r : response.records) {
229    double duration_s = static_cast<double>(r.duration_ns) / 1e9;
230    std::cout << "id=" << r.id << "  diag=" << HexCode(r.diag_code)
231              << "  alert=\"" << r.alert_code << "\"" << std::endl;
232    std::cout << "  first_ts=" << r.first_timestamp
233              << "  last_ts=" << r.last_timestamp << std::endl;
234    std::cout << "  recovered=" << (r.recovered ? "YES" : "NO")
235              << "  recover_ts=" << r.recover_timestamp
236              << "  duration=" << std::fixed << std::setprecision(3)
237              << duration_s << "s" << std::endl;
238    if (!r.alert_location.empty()) {
239      std::cout << "  location=\"" << r.alert_location << "\"" << std::endl;
240    }
241    std::cout << sep << std::endl;
242  }
243}
244
245int RunHistory(int argc, char **argv) {
246  HistoryArgs args;
247  if (!ParseHistoryArgs(argc, argv, &args))
248    return 1;
249
250  rclcpp::init(argc, argv);
251  auto node = std::make_shared<rclcpp::Node>("hds_history_client");
252  auto client = node->create_client<aimdk_msgs::srv::GetHdsEventHistory>(
253      "/aimdk_5Fmsgs/srv/GetHdsEventHistory");
254
255  if (!client->wait_for_service(5s)) {
256    RCLCPP_ERROR(node->get_logger(),
257                 "Service /aimdk_5Fmsgs/srv/GetHdsEventHistory not available");
258    rclcpp::shutdown();
259    return 1;
260  }
261
262  auto request =
263      std::make_shared<aimdk_msgs::srv::GetHdsEventHistory::Request>();
264  auto now = node->now();
265  request->header.stamp.sec = now.seconds();
266  request->header.stamp.nanosec = now.nanoseconds() % 1000000000;
267  request->query.start_time = args.start_ns;
268  request->query.end_time = args.end_ns;
269  request->query.diag_codes = args.diag_codes;
270  request->query.alert_codes = args.alert_codes;
271  request->query.state = args.state;
272  request->query.order_desc = args.desc;
273  request->query.offset = args.offset;
274  request->query.limit = args.limit;
275
276  RCLCPP_INFO(node->get_logger(),
277              "Querying history: state=%s, offset=%u, limit=%u, desc=%s",
278              StateName(args.state).c_str(), args.offset, args.limit,
279              args.desc ? "true" : "false");
280
281  auto future = client->async_send_request(request);
282  if (rclcpp::spin_until_future_complete(node, future, 10s) !=
283      rclcpp::FutureReturnCode::SUCCESS) {
284    RCLCPP_ERROR(node->get_logger(), "Service call timeout");
285    rclcpp::shutdown();
286    return 1;
287  }
288
289  PrintRecords(*future.get());
290  rclcpp::shutdown();
291  return 0;
292}
293
294void PrintUsage(const char *prog) {
295  std::cout << "Usage:\n"
296            << "  " << prog << " echo\n"
297            << "  " << prog
298            << " history [--state N] [--limit N] [--offset N] [--desc]\n"
299            << "                  [--start-ns N] [--end-ns N]\n"
300            << "                  [--diag-codes 0xAAAA 0xBBBB ...]\n"
301            << "                  [--alert-codes \"code1\" \"code2\" ...]\n";
302}
303
304} // namespace
305
306int main(int argc, char **argv) {
307  if (argc < 2) {
308    PrintUsage(argv[0]);
309    return 1;
310  }
311
312  std::string mode = argv[1];
313  if (mode == "echo")
314    return RunEcho(argc, argv);
315  if (mode == "history")
316    return RunHistory(argc, argv);
317
318  PrintUsage(argv[0]);
319  return 1;
320}

Usage Instructions

Real-time subscription (echo):

# Subscribe to all three topics, print diagnostics, alerts, and post-proc signals in real time
ros2 run examples hds_diagnostics echo

Topic

Message Type

/aima/hds/diag_code_list

aimdk_msgs/msg/DiagnosticInfoArray

/aima/hds/alert_code_list

aimdk_msgs/msg/AlertCodeArray

/aima/hds/post_proc/signal

aimdk_msgs/msg/PostProcSigArray

Query historical events (history) — all filter options correspond to HdsEventQuery fields. Usage examples:

# Query latest 20 records (default limit=20, ascending)
ros2 run examples hds_diagnostics history

# Query by time range (start_time / end_time)
ros2 run examples hds_diagnostics history \
  --start-ns 1716000000000000000 \
  --end-ns   1716100000000000000

# Only active alerts (state = STATE_ACTIVE)
ros2 run examples hds_diagnostics history --state 1

# Filter by diagnostic codes (diag_codes, hex supported)
ros2 run examples hds_diagnostics history --diag-codes 0x6200000001 0x6200000002

# Filter by alert codes (alert_codes)
ros2 run examples hds_diagnostics history --alert-codes MOTOR_OVERHEAT COMM_LOST

# Pagination + descending (limit / offset / order_desc, newest first)
ros2 run examples hds_diagnostics history --limit 50 --offset 0 --desc

# Combined: only recovered, 10 per page, newest first
ros2 run examples hds_diagnostics history --state 2 --limit 10 --desc

Response fields correspond to HdsEventRecord.

echo Mode Output Example

[INFO] [hds_diagnostics_echo]: Subscribed: diag_code_list / alert_code_list / post_proc/signal
[INFO] [hds_diagnostics_echo]: [Diagnostics] count=1
  diag_code=0x6200000007  type=APPEAR  module_id=98  ts=1770125762202825297  info="Unable to get robot localization"
[INFO] [hds_diagnostics_echo]: [Alerts] count=1
  code="LOCALIZATION_LOST"  ts=1770125762202825297  first_ts=1770125762202825297
[INFO] [hds_diagnostics_echo]: [PostProc] count=1
  diag_code=0x6200000007  ts=1770125762202825297  robot=1 motion=2 sound=1 led=3 facial=0 funcdisable=0x0000000000000004

history Mode Output Example

[INFO] [hds_history_client]: Querying history: state=ALL, offset=0, limit=20, desc=true
total_count = 5, returned = 5, has_more = false
--------------------------------------------------------------------------------
id=42  diag=0x6200000007  alert="LOCALIZATION_LOST"
  first_ts=1770125762202825297  last_ts=1770125765310145000
  recovered=YES  recover_ts=1770125770000000000  duration=7.797s
--------------------------------------------------------------------------------

Interface Reference

  • Topics: /aima/hds/diag_code_list, /aima/hds/alert_code_list, /aima/hds/post_proc/signal

  • Service: /aimdk_5Fmsgs/srv/GetHdsEventHistory

  • Messages: aimdk_msgs/msg/DiagnosticInfoArray, aimdk_msgs/msg/AlertCodeArray, aimdk_msgs/msg/PostProcSigArray