ROBROS

Human First,

line

Always

Dev Center

Last Updated On: 2026-07-10

Hand

1. End Effector Overview

IGRIS-C supports several types of end effectors depending on the application. Regardless of type, all share the same DDS control interface (rt/handcmd / rt/handstate / rt/service/hand_init); only the number of drive motors, motor IDs, and communication speed differ by type.

Type

Description

Motor IDs

Baud

Dexterous Hand

Five-finger hand (left/right)

R 11–16, L 21–26 (12 motors)

1 Mbps

1-DoF

Single-degree-of-freedom gripper

R 11, L 21

1 Mbps

Magnet

Electromagnet end effector

R 11·12, L 21·22 (4 motors)

57600 bps

The end effector type is preconfigured at shipping based on the robot's configuration. The control interface described in this document applies identically regardless of type.

Appearance by type

Dexterous Hand

1-DoF

Magnet

2. Dexterous Hand

  • The thumb has 2 independently driven degrees of freedom, while the remaining four fingers each operate with 1 coupled drive degree of freedom. All fingers additionally have 1 DoF for passive adaptation, providing grasping performance that naturally conforms to object shape.

  • The hand is divided into left/right hands with a symmetric structure. Left/right are identified by different motor ID ranges (R 11–16, L 21–26), and are controlled together based on ID over a single DDS channel (rt/handcmd / rt/handstate).

  • The 1-DoF gripper drives 2 motors, R(11)/L(21); the magnet drives 4 motors, R(11·12)/L(21·22) (11/21 = magnet on/off, 12/22 = position servo), and both are controlled by their respective IDs over the same rt/handcmd channel.

3. IGRIS-C Hand Node Usage Guide

Node Overview

igris_c_hand_node is a C++ node that allows the IGRIS-C end effector to be controlled entirely through Igris_c_sdk DDS channels. It performs initialization, target pose transmission, and status reception using only the HandCmd / HandState / HandInitRequest / ServiceResponse messages. By default, the node uses the /dev/igris_c_hand port.

Subscribe / Publish Interface

The node communicates via Igris_c_sdk DDS channels, not standard ROS2 topics.

Channels the node subscribes to

  1. rt/handcmd (igris_c_sdk::HandCmd) — Target position command. motor_cmd is a variable-length sequence of MotorCmd, with each entry in (id, q, ...) format.

    • For entries whose id is one of the valid IDs (hand: {11–16, 21–26}, 1-DoF: {11, 21}, magnet: {11, 12, 21, 22}), the q value is stored as a normalized (0.0–1.0) target for that joint and applied immediately. Out-of-range values are clamped, and IDs not in the mapping are ignored.

    • ⚠️ Initialization is no longer handled via rt/handcmd. (The former id=99 trigger method has been removed.) Use the rt/service/hand_init service below for initialization.

Channels the node publishes

  1. rt/handstate (igris_c_sdk::HandState) — Publishes an array of measured motor states at a fixed interval. Each MotorState field:

    • q: Current position (0–1)

    • dq: Current velocity

    • tau_est: Estimated current value (the hand carries motor current in this field)

    • temperature: Motor temperature (°C)

    • status_bits: Motor error/status bits

Initialization (Calibration) Service — rt/service/hand_init

Initialization is handled as a separate Request/Response service.

  • Request: Publish HandInitRequest { Header header; string request_id } to rt/service/hand_init/request.

  • Response: ServiceResponse { request_id, success, message, error_code } is returned on rt/service/hand_init/response (error_code == 0 indicates success). The request and response are matched by request_id.

  • Upon receiving the request, the node resets the targets to 0 and performs effector->initialize() (type-specific calibration/homing).

  • High-level convenience method: Calling the SDK's IgrisC_Client::InitHand(timeout_ms) handles the above request/response internally. The e.4 example below uses the lower-level approach of handling the channel directly.

Sequence Example

  1. The node may auto-initialize at startup depending on the robot configuration. If reinitialization is needed at runtime, publish a HandInitRequest (with request_id filled in) to rt/service/hand_init/request.

  2. Publish target positions by populating motor_cmd on rt/handcmd. E.g., only the right index finger to 0.8 → [{id:12, q:0.8}]; the same pose for both hands → publish a sequence including all 12 IDs.

  3. Status check: subscribe to rt/handstate and monitor the motor_state array.

  4. Initialization result check: subscribe to rt/service/hand_init/response and check success/error_code.

Debugging Tips

  • HandState.status_bits contains per-motor fault/limit bits, so use it for alerts/status display in a GUI or logger.

  • HandCmd.motor_cmd[].q is a normalized value from 0–1. If scaling is needed to match the hand's structure, apply a linear transformation in the upper-level application.

  • If origin calibration is suspected to have drifted, request init again via rt/service/hand_init/request to recalibrate. Check the response on rt/service/hand_init/response.

4. IGRIS-C Hand Example

Full runnable example: examples/cyclonedds/cyclonedds_hand_example.cpp (C++), examples/python/hand_example.py (Python). Below is an excerpt of the core flow; topics are resolved under the robot unit namespace as described in b.3.

// Example: drive the igris_c_hand node entirely through igris_c_sdk DDS channels.
//   * Trigger initialization via rt/service/hand_init/request (HandInitRequest)
//   * Send target vectors via rt/handcmd (MotorCmd.id + q)
//   * React to rt/handstate and rt/service/hand_init/response for logging/verification
//
// Build against igris_c_sdk and run after sourcing the workspace.

#include <algorithm>
#include <array>
#include <chrono>
#include <thread>

#include <igris_c_sdk/channel_factory.hpp>
#include <igris_c_sdk/publisher.hpp>
#include <igris_c_sdk/subscriber.hpp>
#include <igris_c_sdk/types.hpp>

// types.hpp aliases HandCmd / HandState / MotorCmd / HandInitRequest / ServiceResponse and the
// infra (ChannelFactory / Publisher / Subscriber / QosProfile) into the igris_c_sdk namespace.
using namespace igris_c_sdk;

static constexpr std::array<uint16_t, 12> kMotorIds = {
    11, 12, 13, 14, 15, 16, 21, 22, 23, 24, 25, 26};

static HandCmd make_targets(const std::array<float, 12>& q) {
    HandCmd cmd;
    for (size_t i = 0; i < kMotorIds.size(); ++i) {
        MotorCmd m;
        m.id(kMotorIds[i]);
        m.q(std::clamp(q[i], 0.0f, 1.0f));
        cmd.motor_cmd().push_back(m);
    }
    return cmd;
}

int main() {
    ChannelFactory::Instance()->Init(0, "igris_c_IG01");  // domain 0, robot unit namespace (see b.3)

    Publisher<HandCmd> cmd_pub("rt/handcmd", QosProfile::SensorData());
    cmd_pub.init();

    // Initialization is a request/response service (no more id=99 in rt/handcmd).
    Publisher<HandInitRequest> init_req_pub("rt/service/hand_init/request", QosProfile::Services());
    init_req_pub.init();

    Subscriber<HandState> state_sub("rt/handstate", QosProfile::SensorData());
    state_sub.init([](const HandState& s) {
        if (s.motor_state().size() >= 3) {
            printf("q[0..2]=%.3f %.3f %.3f\n",
                   s.motor_state()[0].q(),
                   s.motor_state()[1].q(),
                   s.motor_state()[2].q());
        }
    });

    Subscriber<ServiceResponse> init_res_sub(
        "rt/service/hand_init/response", QosProfile::Services());
    init_res_sub.init([](const ServiceResponse& r) {
        printf("[hand_init] success=%d code=%d msg=%s\n",
               r.success(), r.error_code(), r.message().c_str());
    });

    // 1) Trigger initialization via the hand_init service
    HandInitRequest init_req;
    init_req.request_id() = "hand_init";
    init_req_pub.write(init_req);
    std::this_thread::sleep_for(std::chrono::milliseconds(1500));

    // 2) Fully open
    cmd_pub.write(make_targets({0.05f, 0.05f, 0.05f, 0.05f, 0.05f, 0.05f,
                                0.05f, 0.05f, 0.05f, 0.05f, 0.05f, 0.05f}));
    std::this_thread::sleep_for(std::chrono::seconds(2));

    // 3) Close all joints
    cmd_pub.write(make_targets({0.85f, 0.85f, 0.85f, 0.85f, 0.85f, 0.85f,
                                0.85f, 0.85f, 0.85f, 0.85f, 0.85f, 0.85f}));
    std::this_thread::sleep_for(std::chrono::milliseconds(2500));

    // 4) Basic pinch (right thumb + index + middle), others relaxed
    std::array<float, 12> pinch = {};
    pinch[0] = 0.80f;  // right thumb  (id=11)
    pinch[1] = 0.75f;  // right index  (id=12)
    pinch[2] = 0.60f;  // right middle (id=13)
    cmd_pub.write(make_targets(pinch));
    std::this_thread::sleep_for(std::chrono::seconds(2));

    // 5) Back to open
    cmd_pub.write(make_targets({0.05f, 0.05f, 0.05f, 0.05f, 0.05f, 0.05f,
                                0.05f, 0.05f, 0.05f, 0.05f, 0.05f, 0.05f}));
    std::this_thread::sleep_for(std::chrono::milliseconds(1500));

    cmd_pub.stop();
    state_sub.stop();
    init_res_sub.stop();
    init_req_pub.stop();
    return 0;
}