Dev Center
Dev Center
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/handcmdchannel.
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
rt/handcmd(igris_c_sdk::HandCmd) — Target position command.motor_cmdis a variable-length sequence ofMotorCmd, with each entry in(id, q, ...)format.For entries whose
idis one of the valid IDs (hand: {11–16, 21–26}, 1-DoF: {11, 21}, magnet: {11, 12, 21, 22}), theqvalue 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 formerid=99trigger method has been removed.) Use thert/service/hand_initservice below for initialization.
Channels the node publishes
rt/handstate(igris_c_sdk::HandState) — Publishes an array of measured motor states at a fixed interval. EachMotorStatefield:q: Current position (0–1)dq: Current velocitytau_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 }tort/service/hand_init/request.Response:
ServiceResponse { request_id, success, message, error_code }is returned onrt/service/hand_init/response(error_code == 0indicates success). The request and response are matched byrequest_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
The node may auto-initialize at startup depending on the robot configuration. If reinitialization is needed at runtime, publish a
HandInitRequest(withrequest_idfilled in) tort/service/hand_init/request.Publish target positions by populating
motor_cmdonrt/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.Status check: subscribe to
rt/handstateand monitor themotor_statearray.Initialization result check: subscribe to
rt/service/hand_init/responseand checksuccess/error_code.
Debugging Tips
HandState.status_bitscontains per-motor fault/limit bits, so use it for alerts/status display in a GUI or logger.HandCmd.motor_cmd[].qis 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/requestto recalibrate. Check the response onrt/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;
}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/handcmdchannel.
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
rt/handcmd(igris_c_sdk::HandCmd) — Target position command.motor_cmdis a variable-length sequence ofMotorCmd, with each entry in(id, q, ...)format.For entries whose
idis one of the valid IDs (hand: {11–16, 21–26}, 1-DoF: {11, 21}, magnet: {11, 12, 21, 22}), theqvalue 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 formerid=99trigger method has been removed.) Use thert/service/hand_initservice below for initialization.
Channels the node publishes
rt/handstate(igris_c_sdk::HandState) — Publishes an array of measured motor states at a fixed interval. EachMotorStatefield:q: Current position (0–1)dq: Current velocitytau_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 }tort/service/hand_init/request.Response:
ServiceResponse { request_id, success, message, error_code }is returned onrt/service/hand_init/response(error_code == 0indicates success). The request and response are matched byrequest_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
The node may auto-initialize at startup depending on the robot configuration. If reinitialization is needed at runtime, publish a
HandInitRequest(withrequest_idfilled in) tort/service/hand_init/request.Publish target positions by populating
motor_cmdonrt/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.Status check: subscribe to
rt/handstateand monitor themotor_statearray.Initialization result check: subscribe to
rt/service/hand_init/responseand checksuccess/error_code.
Debugging Tips
HandState.status_bitscontains per-motor fault/limit bits, so use it for alerts/status display in a GUI or logger.HandCmd.motor_cmd[].qis 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/requestto recalibrate. Check the response onrt/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;
}



