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 (handcmd / handstate / 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 (
handcmd/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
handcmdchannel.On the 1-DoF gripper,
q=0 is OPEN. Starting with 2.1.1, the gripper joint in the robot model (URDF) and the simulator also uses q=0 = open.The electromagnet gripper was updated to the v2 design in 2.1.1. Its mass in the robot model (URDF) changed from 0.324 kg to 0.427 kg per side.
3. IGRIS-C Hand Node Usage Guide
Node Overview
igris_c_hand_node is the on-robot node that drives the IGRIS-C end effector.
The hand topics and service used by external clients (handcmd / handstate / service/hand_init) are not opened by the hand node itself; the robot's external communication node (bridge) serves them together with the other SDK topics, according to the robot's transport. Initialization, target pose transmission, and status reception use only the HandCmd / HandState messages and the HandInitRequest service.
⚠️ External handcmd is applied to the hand only in LOW_LEVEL_MODE and LOW_LEVEL_WALK_MODE. In HIGH_LEVEL_MODE and HIGH_LEVEL_WALK_MODE the motion preset's hand motion takes priority, and in all other modes (WALK_MODE, PASSIVE_MODE, JOINT_HOLD_MODE, DAMPING_MODE) handcmd is ignored and the hand keeps its last target pose. 2.1.0 followed handcmd in every mode, so code that drives the hand directly should first check that the robot is in one of these two modes (see the ControlMode page). The initialization service (
service/hand_init) andhandstatework in every mode.
Subscribe / Publish Interface
Code uses the bare name (handcmd); the actual topic name and the way the service is offered are determined by the robot's transport. See “3. Transport Mode” on the SDK Overview page for details.
Transport | Actual topic name of | Initialization service ( |
|---|---|---|
|
| Request/response topic pair |
|
| Same topic pair as above. Not listed by |
|
| Real ROS 2 service |
Channels the robot subscribes to
handcmd(igris_c_sdk::msg::dds_::HandCmd_) — Target position command.motor_cmdis a fixed 12-slot array ofMotorCmd, with each slot in(id, q, ...)format.The length is fixed, so fill the slots by index instead of
push_back(C++cmd.motor_cmd()[i]; in Python pass a list of exactly 12MotorCmd_). The target motor is chosen byid, not by slot position; slots left atid0 (the default) are ignored.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
handcmd. (The formerid=99trigger method has been removed.) Use theservice/hand_initservice below for initialization.
Channels the robot publishes
handstate(igris_c_sdk::msg::dds_::HandState_) — Publishes a fixed 12-slot array of measured motor states (motor_state) at the same rate aslowstate(much faster than in 2.1.0).MotorStatehas noidfield;motor_state[i]follows the ID order 11–16, 21–26. The 1-DoF gripper uses only slots 0 and 6, the magnet only slots 0, 1, 6 and 7, and the remaining slots are 0. The 2.1.0HandState.imu_statefield has been removed. EachMotorStatefield:q: Current position (0–1)dq: Current velocitytau_est: Motor current (mA). Unlike the torque unit in the message definition, the hand carries motor current in this field.temperature: Motor temperature (°C)status_bits: Motor error/status bits
Initialization (Calibration) Service — service/hand_init
Initialization is handled as a separate Request/Response service.
Request: Publish
HandInitRequest_Request_ { std_msgs/Header header; string request_id }toservice/hand_init/request.Response:
HandInitRequest_Response_ { header, request_id, success, message, error_code }is returned onservice/hand_init/response(error_code == 0indicates success). The request and response are matched byrequest_id.⚠️ Always subscribe to the response as
HandInitRequest_Response_. The standaloneServiceResponse_has the same fields but a different DDS type name, so subscribing with it receives nothing and reports no error.On the
rostransport, call the real ROS 2 service/<ns>/service/hand_init(igris_c_sdk/srv/HandInitRequest) with an rclcpp service client instead of the topic pair.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 (cyclonedds transports;timeout_msis a required argument in Python). The section 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) toservice/hand_init/request.With the robot in LOW_LEVEL_MODE or LOW_LEVEL_WALK_MODE, publish target positions by populating
motor_cmdonhandcmd. E.g., only the right index finger to 0.8 → put{id:12, q:0.8}in one slot and leave the other slots atid0; the same pose for both hands → fill all 12 slots with the 12 IDs.Status check: subscribe to
handstateand monitor themotor_statearray.Initialization result check: subscribe to
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. The flag values are constants ofMotorState(MOTOR_STALL_ERROR,OVER_CURRENT_ERROR,COMPONENT_OVERTEMPERATURE_ERROR, etc.). They are bit flags, so test them with(status_bits & FLAG), not with equality. C++:igris_c_sdk::msg::dds_::MotorState_Constants::MOTOR_STALL_ERROR, Python:igc_sdk.MOTOR_STALL_ERROR.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
service/hand_init/requestto recalibrate. Check the response onservice/hand_init/response.If the hand does not move on
handcmd, first check the robot's control mode (LOW_LEVEL_MODE / LOW_LEVEL_WALK_MODE) and that the transport and topic layout match.
4. IGRIS-C Hand Example
The full runnable examples are listed below. The code is an excerpt of the core flow of
examples/cyclonedds/cyclonedds_hand_example.cpp; topics are resolved under the robot unit namespace described in SDK Overview. The hand motions (steps 2–5) take effect only while the robot is in LOW_LEVEL_MODE or LOW_LEVEL_WALK_MODE.
Example | Transport | Description |
|---|---|---|
|
| Init → open → close → pinch → open (C++) |
|
| GUI with 12 motor sliders, state table and a Hand Init button (C++) |
|
| Same flow as the C++ example (Python) |
|
| Same screen as the C++ hand GUI (Python) |
|
| Calls the init service, then the same flow (ROS 2) |
|
| Same screen as the C++ hand GUI (ROS 2) |
cyclonedds_hand_gui_client in action: after hand_init, Closed Pose (0.6) was sent and the 12-axis state comes back through HandState (simulation).

# cyclonedds_native / cyclonedds_ros_compatible
./cyclonedds_hand_example <domain_id> <namespace> [--dds-topic-naming native|ros_compatible]
python3 hand_example.py --domain-id <domain_id> --namespace <namespace> [--dds-topic-naming native|ros_compatible]
# ros
ros2 run igris_c_sdk ros_hand_example --ros-args -p robot_namespace:=<namespace>// Example: drive the igris_c_hand node entirely through igris_c_sdk DDS channels.
// * Trigger initialization via service/hand_init/request (HandInitRequest)
// * Send target vectors via handcmd (MotorCmd.id + q)
// * React to handstate and 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 deliberately has no aliases for the contract types: the full name is what appears on the
// wire, in ddsls output and in DDS type-mismatch errors, so it is spelled out at every use.
// The namespace below brings in only the infrastructure (ChannelFactory / Publisher / Subscriber / QosProfile).
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 igris_c_sdk::msg::dds_::HandCmd_ make_targets(const std::array<float, 12>& q) {
igris_c_sdk::msg::dds_::HandCmd_ cmd;
// motor_cmd is a fixed std::array<MotorCmd_, 12> - index it, do not push_back.
for (size_t i = 0; i < kMotorIds.size(); ++i) {
auto& m = cmd.motor_cmd()[i];
m.id(kMotorIds[i]);
m.q(std::clamp(q[i], 0.0f, 1.0f));
}
return cmd;
}
int main() {
ChannelFactory::Instance()->Init(0, "igris_c_IG01"); // domain 0, robot unit namespace (see SDK Overview)
Publisher<igris_c_sdk::msg::dds_::HandCmd_> cmd_pub("handcmd", QosProfile::SensorData());
cmd_pub.init();
// Initialization is a request/response service (no more id=99 in handcmd).
Publisher<igris_c_sdk::srv::dds_::HandInitRequest_Request_> init_req_pub("service/hand_init/request",
QosProfile::Services());
init_req_pub.init();
Subscriber<igris_c_sdk::msg::dds_::HandState_> state_sub("handstate", QosProfile::SensorData());
state_sub.init([](const igris_c_sdk::msg::dds_::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());
}
});
// The response type is the service's own _Response_, NOT the standalone ServiceResponse -
// they are field-identical but register different DDS type names, so the wrong one here
// subscribes successfully and then never receives anything.
Subscriber<igris_c_sdk::srv::dds_::HandInitRequest_Response_> init_res_sub(
"service/hand_init/response", QosProfile::Services());
init_res_sub.init([](const igris_c_sdk::srv::dds_::HandInitRequest_Response_& 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
igris_c_sdk::srv::dds_::HandInitRequest_Request_ 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 (handcmd / handstate / 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 (
handcmd/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
handcmdchannel.On the 1-DoF gripper,
q=0 is OPEN. Starting with 2.1.1, the gripper joint in the robot model (URDF) and the simulator also uses q=0 = open.The electromagnet gripper was updated to the v2 design in 2.1.1. Its mass in the robot model (URDF) changed from 0.324 kg to 0.427 kg per side.
3. IGRIS-C Hand Node Usage Guide
Node Overview
igris_c_hand_node is the on-robot node that drives the IGRIS-C end effector.
The hand topics and service used by external clients (handcmd / handstate / service/hand_init) are not opened by the hand node itself; the robot's external communication node (bridge) serves them together with the other SDK topics, according to the robot's transport. Initialization, target pose transmission, and status reception use only the HandCmd / HandState messages and the HandInitRequest service.
⚠️ External handcmd is applied to the hand only in LOW_LEVEL_MODE and LOW_LEVEL_WALK_MODE. In HIGH_LEVEL_MODE and HIGH_LEVEL_WALK_MODE the motion preset's hand motion takes priority, and in all other modes (WALK_MODE, PASSIVE_MODE, JOINT_HOLD_MODE, DAMPING_MODE) handcmd is ignored and the hand keeps its last target pose. 2.1.0 followed handcmd in every mode, so code that drives the hand directly should first check that the robot is in one of these two modes (see the ControlMode page). The initialization service (
service/hand_init) andhandstatework in every mode.
Subscribe / Publish Interface
Code uses the bare name (handcmd); the actual topic name and the way the service is offered are determined by the robot's transport. See “3. Transport Mode” on the SDK Overview page for details.
Transport | Actual topic name of | Initialization service ( |
|---|---|---|
|
| Request/response topic pair |
|
| Same topic pair as above. Not listed by |
|
| Real ROS 2 service |
Channels the robot subscribes to
handcmd(igris_c_sdk::msg::dds_::HandCmd_) — Target position command.motor_cmdis a fixed 12-slot array ofMotorCmd, with each slot in(id, q, ...)format.The length is fixed, so fill the slots by index instead of
push_back(C++cmd.motor_cmd()[i]; in Python pass a list of exactly 12MotorCmd_). The target motor is chosen byid, not by slot position; slots left atid0 (the default) are ignored.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
handcmd. (The formerid=99trigger method has been removed.) Use theservice/hand_initservice below for initialization.
Channels the robot publishes
handstate(igris_c_sdk::msg::dds_::HandState_) — Publishes a fixed 12-slot array of measured motor states (motor_state) at the same rate aslowstate(much faster than in 2.1.0).MotorStatehas noidfield;motor_state[i]follows the ID order 11–16, 21–26. The 1-DoF gripper uses only slots 0 and 6, the magnet only slots 0, 1, 6 and 7, and the remaining slots are 0. The 2.1.0HandState.imu_statefield has been removed. EachMotorStatefield:q: Current position (0–1)dq: Current velocitytau_est: Motor current (mA). Unlike the torque unit in the message definition, the hand carries motor current in this field.temperature: Motor temperature (°C)status_bits: Motor error/status bits
Initialization (Calibration) Service — service/hand_init
Initialization is handled as a separate Request/Response service.
Request: Publish
HandInitRequest_Request_ { std_msgs/Header header; string request_id }toservice/hand_init/request.Response:
HandInitRequest_Response_ { header, request_id, success, message, error_code }is returned onservice/hand_init/response(error_code == 0indicates success). The request and response are matched byrequest_id.⚠️ Always subscribe to the response as
HandInitRequest_Response_. The standaloneServiceResponse_has the same fields but a different DDS type name, so subscribing with it receives nothing and reports no error.On the
rostransport, call the real ROS 2 service/<ns>/service/hand_init(igris_c_sdk/srv/HandInitRequest) with an rclcpp service client instead of the topic pair.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 (cyclonedds transports;timeout_msis a required argument in Python). The section 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) toservice/hand_init/request.With the robot in LOW_LEVEL_MODE or LOW_LEVEL_WALK_MODE, publish target positions by populating
motor_cmdonhandcmd. E.g., only the right index finger to 0.8 → put{id:12, q:0.8}in one slot and leave the other slots atid0; the same pose for both hands → fill all 12 slots with the 12 IDs.Status check: subscribe to
handstateand monitor themotor_statearray.Initialization result check: subscribe to
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. The flag values are constants ofMotorState(MOTOR_STALL_ERROR,OVER_CURRENT_ERROR,COMPONENT_OVERTEMPERATURE_ERROR, etc.). They are bit flags, so test them with(status_bits & FLAG), not with equality. C++:igris_c_sdk::msg::dds_::MotorState_Constants::MOTOR_STALL_ERROR, Python:igc_sdk.MOTOR_STALL_ERROR.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
service/hand_init/requestto recalibrate. Check the response onservice/hand_init/response.If the hand does not move on
handcmd, first check the robot's control mode (LOW_LEVEL_MODE / LOW_LEVEL_WALK_MODE) and that the transport and topic layout match.
4. IGRIS-C Hand Example
The full runnable examples are listed below. The code is an excerpt of the core flow of
examples/cyclonedds/cyclonedds_hand_example.cpp; topics are resolved under the robot unit namespace described in SDK Overview. The hand motions (steps 2–5) take effect only while the robot is in LOW_LEVEL_MODE or LOW_LEVEL_WALK_MODE.
Example | Transport | Description |
|---|---|---|
|
| Init → open → close → pinch → open (C++) |
|
| GUI with 12 motor sliders, state table and a Hand Init button (C++) |
|
| Same flow as the C++ example (Python) |
|
| Same screen as the C++ hand GUI (Python) |
|
| Calls the init service, then the same flow (ROS 2) |
|
| Same screen as the C++ hand GUI (ROS 2) |
cyclonedds_hand_gui_client in action: after hand_init, Closed Pose (0.6) was sent and the 12-axis state comes back through HandState (simulation).

# cyclonedds_native / cyclonedds_ros_compatible
./cyclonedds_hand_example <domain_id> <namespace> [--dds-topic-naming native|ros_compatible]
python3 hand_example.py --domain-id <domain_id> --namespace <namespace> [--dds-topic-naming native|ros_compatible]
# ros
ros2 run igris_c_sdk ros_hand_example --ros-args -p robot_namespace:=<namespace>// Example: drive the igris_c_hand node entirely through igris_c_sdk DDS channels.
// * Trigger initialization via service/hand_init/request (HandInitRequest)
// * Send target vectors via handcmd (MotorCmd.id + q)
// * React to handstate and 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 deliberately has no aliases for the contract types: the full name is what appears on the
// wire, in ddsls output and in DDS type-mismatch errors, so it is spelled out at every use.
// The namespace below brings in only the infrastructure (ChannelFactory / Publisher / Subscriber / QosProfile).
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 igris_c_sdk::msg::dds_::HandCmd_ make_targets(const std::array<float, 12>& q) {
igris_c_sdk::msg::dds_::HandCmd_ cmd;
// motor_cmd is a fixed std::array<MotorCmd_, 12> - index it, do not push_back.
for (size_t i = 0; i < kMotorIds.size(); ++i) {
auto& m = cmd.motor_cmd()[i];
m.id(kMotorIds[i]);
m.q(std::clamp(q[i], 0.0f, 1.0f));
}
return cmd;
}
int main() {
ChannelFactory::Instance()->Init(0, "igris_c_IG01"); // domain 0, robot unit namespace (see SDK Overview)
Publisher<igris_c_sdk::msg::dds_::HandCmd_> cmd_pub("handcmd", QosProfile::SensorData());
cmd_pub.init();
// Initialization is a request/response service (no more id=99 in handcmd).
Publisher<igris_c_sdk::srv::dds_::HandInitRequest_Request_> init_req_pub("service/hand_init/request",
QosProfile::Services());
init_req_pub.init();
Subscriber<igris_c_sdk::msg::dds_::HandState_> state_sub("handstate", QosProfile::SensorData());
state_sub.init([](const igris_c_sdk::msg::dds_::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());
}
});
// The response type is the service's own _Response_, NOT the standalone ServiceResponse -
// they are field-identical but register different DDS type names, so the wrong one here
// subscribes successfully and then never receives anything.
Subscriber<igris_c_sdk::srv::dds_::HandInitRequest_Response_> init_res_sub(
"service/hand_init/response", QosProfile::Services());
init_res_sub.init([](const igris_c_sdk::srv::dds_::HandInitRequest_Response_& 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
igris_c_sdk::srv::dds_::HandInitRequest_Request_ 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;
}



