Dev Center
Dev Center
ControlMode
This page reorganizes the former "Low Level Control" page into Control Mode, summarizing the meaning and switching methods of each control mode supported by IGRIS-C, as well as the message interfaces required for low-level control.
⚠️ The code on this page targets the v2.1.1 SDK. In 2.1.1, topic names (
rt/removed), type names, constant names (enums →uint8constants) and the BMS → PDU terminology changed, and a 2.1.0 SDK client cannot communicate with a 2.1.1 robot (see SDK Overview).
IGRIS-C's behavior hierarchy is switched via Control Mode. Mode-switch commands are sent to the controller through the SDK's
IgrisC_Client::SendControlModeCommand()(internally aControlModeCommandRequestRequest/Response), and the controller maps this to an internalRobotControlModeto change the control loop's behavior.
struct ControlModeCommandRequest_Request {
std_msgs/Header header;
string request_id; // ID for matching requests
uint8 command_type; // Mode to switch to (see list below, CONTROL_MODE_CMD_* constants)
string preset_id; // MOTION_PRESET only
boolean is_cyclic; // MOTION_PRESET_CYCLIC_TOGGLE only
};
// Response: ControlModeCommandRequest_Response { header, request_id, success, message, error_code(0=success) }1. List of Control Modes
command_type (uint8 constants, 19 types total, values 0–18). Controller behavior is based on robotHandler dispatch.
# | command_type | Controller mapping/behavior | Layer |
|---|---|---|---|
0 |
|
| High-Level |
1 |
|
| High-Level |
2 |
| → | - |
3 |
|
| High-Level |
4 |
| → | - |
5 |
| → | Low-Level |
6 |
| → | Low-Level |
7 |
| → | High-Level |
8 |
| → | High-Level |
9–18 |
| (internal use only) | Not provided |
command_type values are
uint8constants of the formigris_c_sdk::srv::dds_::ControlModeCommandRequest_Request_Constants::CONTROL_MODE_CMD_<NAME>(e.g.,CONTROL_MODE_CMD_MOTION_PRESET). In Python they areigc_sdk.CONTROL_MODE_CMD_<NAME>.
⚠️
CUSTOM_MODE_1–CUSTOM_MODE_10(9–18) are not currently provided to users. These are internal/extension-only entries with reserved slots in the enum only — do not use them when working with the SDK.
The two modes below cannot be requested by command; the controller enters them on its own when their conditions are met (details in Section 2, "Automatic Safety Modes").
Controller mode | Entered when | control_state |
|---|---|---|
| Right after startup, and automatically whenever power or torque turns OFF or the E-stop is pressed (renamed from 2.1.0's |
|
| Entered automatically when the walking policy detects a fall (new in 2.1.1) |
|
2. Description of Each Mode
Low-Level Control
The SDK directly commands each motor's targets (q·dq·tau·kp·kd) via
lowcmd. In this mode, control loop responsibility resides externally (with the SDK). (See section 5 for the message interface.)LOW_LEVEL_JOINT_CONTROL(5) —LOW_LEVEL_MODE. Direct joint control via SDK LowCmd.LOW_LEVEL_WALKMODE_ON(6) —LOW_LEVEL_WALK_MODE. Walking mode based on low-level control.
Low-level commands in MS (motor space) are projected onto the parallel links' motor-space safe set before they are applied. Measured values reported in
lowstateare not projected, so depending on the actual posture a joint value beyond its limit can be reported.External
handcmdis applied only inLOW_LEVEL_MODEandLOW_LEVEL_WALK_MODE(the HIGH_LEVEL modes use the preset's hand motion; all other modes ignore it).
High-Level Control
The controller's built-in logic receives higher-level commands and generates/maintains motion. Motion presets run at this layer; a preset requested from another mode switches to this layer automatically and then starts (see "Motion Preset" below).
HIGH_LEVEL_JOINT_CONTROL(8) —HIGH_LEVEL_MODE. Controller's high-level joint control.HIGH_LEVEL_WALKMODE_ON(7) —HIGH_LEVEL_WALK_MODE. High-level walking mode.
Walk
WALKMODE_ON(4) —WALK_MODE. Enters walking mode.Walk velocity commands are sent on the
cmd_veltopic (geometry_msgs/TwistStamped, the same on every lane). Examples:cyclonedds_twist_publisher,twist_publisher.py,ros_twist_publisher.⚠️ From 2.1.1, entering
WALKMODE_ONno longer moves the arms slowly first (the 2.1.0 slow start of 0.4 rad/s for 3 s is gone). The walking policy controls the whole body immediately, so clear the area around the robot — especially the arms' range of motion — before requesting it.WALKMODE_ONis not rejected inDAMPING_MODEeither, so make sure the robot is standing upright before requesting it.
Motion Preset
MOTION_PRESET(0) — Plays back a predefined motion specified bypreset_id(startMotionPreset). Runs inHIGH_LEVEL_MODEorHIGH_LEVEL_WALK_MODE. When requested from another mode, the robot switches automatically — toHIGH_LEVEL_WALK_MODEfromLOW_LEVEL_WALK_MODEorWALK_MODE, otherwise toHIGH_LEVEL_MODE— and then starts the preset. In the stopped state (below) onlyHOMEcan be run. Populate thepreset_idargument in the request.MOTION_PRESET_CYCLIC_TOGGLE(1) — Toggles whether the preset repeats cyclically (setMotionPresetCyclic). Uses theis_cyclicargument in the request.
Stop / Hold
JOINT_POSITION_HOLD(2) —JOINT_HOLD_MODE. Maintains (holds) the current joint positions. When the request succeeds, the robot enters the stopped state described below.MOTION_STOP(3) — Stops the ongoing motion and maintains the current posture (holdCurrentPosition). Only supported fromHIGH_LEVEL_MODE/HIGH_LEVEL_WALK_MODE.
Stopped state (Motion-Stop latch)
When
MOTION_STOPorJOINT_POSITION_HOLDsucceeds, the robot latches into the stopped state, andcontrol_stateisSTOP(2.1.0 reportedHIGH_MOTION_STANDBY/IDLE).In the stopped state, the only accepted requests are
MOTION_PRESET"HOME"and switches to the safe-stop modes (JOINT_HOLD_MODE,PASSIVE_MODE,DAMPING_MODE; PASSIVE and DAMPING are entered automatically). Every other mode switch or preset is rejected, with the reason in the responsemessage. (2.1.0 accepted onlyHOME.)Starting the
HOMEpreset releases the stopped state. If needed, the robot first switches toHIGH_LEVEL_MODE(orHIGH_LEVEL_WALK_MODE) automatically.
Automatic Safety Modes (DAMPING / PASSIVE)
DAMPING_MODE— a safe-stop mode with kp=0 that keeps only kd. It cannot be requested from the SDK or the app; the walking policy enters it automatically when it detects a fall inWALK_MODE,HIGH_LEVEL_WALK_MODEorLOW_LEVEL_WALK_MODE(default policy: pelvis tilt above 0.8 rad, or above 0.4 rad with angular velocity above 1.57 rad/s).control_stateisSTOP.⚠️ Whether DAMPING_MODE is entered depends only on the tilt criteria above, not on the hoist. Even while hanging on a hoist, the robot switches and goes limp once its tilt exceeds them.
PASSIVE_MODE— a standby mode that outputs no command (2.1.0'sTORQUE_OFF_MODE). Mode switches are accepted only while body power and torque are ON and the E-stop is released; a request in any other condition is rejected and the controller switches toPASSIVE_MODE.
Custom — Currently Not Provided
CUSTOM_MODE_1–CUSTOM_MODE_10(9–18) — Internal/extension-only slots not currently provided to users. These are defined in the enum but are not modes callable by SDK users, so do not use them in normal control.Note: the safety profile of these slots changed in 2.1.1. The waist and ankle parallel links use
SATURATE+SOFTand the wristsCLIP+HARD(2.1.0 usedCLIP+HARDeverywhere).
3. Mode Switching API (SDK)
Mode switching is performed via the single method IgrisC_Client::SendControlModeCommand().
igris_c_sdk::msg::dds_::ServiceResponse_ SendControlModeCommand(
uint8_t command_type, // ControlModeCommandRequest_Request_Constants::CONTROL_MODE_CMD_*
const std::string& preset_id = "", // MOTION_PRESET only
bool is_cyclic = false, // MOTION_PRESET_CYCLIC_TOGGLE only
int timeout_ms = 5000);Usage example (based on cyclonedds/cyclonedds_service.cpp):
using namespace igris_c_sdk::srv::dds_; // ControlModeCommandRequest_Request_Constants
// Execute motion preset "HOME" (at the HIGH_LEVEL layer)
res = client.SendControlModeCommand(
ControlModeCommandRequest_Request_Constants::CONTROL_MODE_CMD_MOTION_PRESET, "HOME", false, 60000);
// Hold current position
res = client.SendControlModeCommand(
ControlModeCommandRequest_Request_Constants::CONTROL_MODE_CMD_JOINT_POSITION_HOLD, "", false, 60000);
// Stop ongoing motion
res = client.SendControlModeCommand(
ControlModeCommandRequest_Request_Constants::CONTROL_MODE_CMD_MOTION_STOP, "", false, 60000);
// Switch to low-level joint control mode (use lowcmd afterward)
res = client.SendControlModeCommand(
ControlModeCommandRequest_Request_Constants::CONTROL_MODE_CMD_LOW_LEVEL_JOINT_CONTROL, "", false, 60000);Check the result via the returned ServiceResponse's success / error_code (0=success) / message.
Requesting the mode the robot is already in returns
success=false("Control mode is already …").On a mode change, the command blends from the previous mode's command to the new one over 0.5 s. Switches into or out of
PASSIVE_MODE,JOINT_HOLD_MODEorDAMPING_MODEtake effect immediately.
4. Service Topics Summary
Request/Response service channels used by the SDK client:
Function | Service Topic | SDK Method |
|---|---|---|
PDU/motor initialization |
|
|
Torque ON/OFF |
|
|
Hand initialization |
|
|
Control mode switching |
|
|
Mujoco simulation control (sim only) |
|
|
On the cyclonedds lanes (
cyclonedds_native/cyclonedds_ros_compatible), each service operates as a pair:<topic>/request(publishes the request) ·<topic>/response(subscribes to the response). Example:service/pdu_init/request·service/pdu_init/response. (The table above shows the base topic.)
How services are offered depends on the robot's transport mode (SDK Overview, Section 3). On the cyclonedds lanes they are the request/response topic pairs above, called with
IgrisC_Client. These pairs are not ROS 2 services, so they do not appear inros2 service list, even oncyclonedds_ros_compatible.On the
roslane, real rclcpp services with the same names are offered (/<ns>/service/pdu_initetc., typesigris_c_sdk/srv/PduInitCmd·TorqueCmd·HandInitRequest·ControlModeCommandRequest·MujocoSimCmd); call them with theros_serviceexample inros2/.Response types are per service (
PduInitCmd_Response_,TorqueCmd_Response_,HandInitRequest_Response_,ControlModeCommandRequest_Response_,MujocoSimCmd_Response_). They all carry the same fields —header,request_id,success,message,error_code— and theIgrisC_Clientmethods return them as the commonigris_c_sdk::msg::dds_::ServiceResponse_.
Low-level streams: lowcmd (LowCmd, commands) / lowstate (LowState, status).
5. Low-Level Control Message Interface
IGRIS-C Topic List
Low-level Cmd & State (high frequency):
lowcmd/lowstateService State (low frequency):
pdustate/robotstate
Common (igris_c_sdk::msg::dds_ / igris_c_sdk::srv::dds_)
const uint32 N_JOINTS = 31; // Fixed DOF Num
// Kinematic Space (MS=Motor Space/AB, PJS=Parallel Joint Space/PR)
// uint8 constants, not an enum (LowCmd_Constants)
uint8 KINEMATIC_MODE_MS = 0;
uint8 KINEMATIC_MODE_PJS = 1;
// Motor control is HYBRID mode only. (The former MotorMode { CURRENT, HYBRID } enum has been removed.)Command Side
struct MotorCmd { // HYBRID only
uint16 id; // MS: motor index / PJS: virtual joint index
float q; // Position (rad)
float dq; // Velocity (rad/s)
float tau; // Feedforward Torque (Nm)
float kp; // kP
float kd; // kD
};
struct LowCmd {
std_msgs/Header header;
uint8 kinematic_modes[5]; // Per parallel-link group: waist / L_ankle / R_ankle / L_wrist / R_wrist
MotorCmd motors[31]; // Fixed length, 31 DOF
};// Usage example: set all groups to the same mode
cmd.kinematic_modes().fill(igris_c_sdk::msg::dds_::LowCmd_Constants::KINEMATIC_MODE_PJS);State Side
// imu_state is the standard sensor_msgs/Imu, not a custom struct:
// std_msgs/Header header;
// Quaternion orientation; // named x/y/z/w fields
// double orientation_covariance[9];
// Vector3 angular_velocity; // rad/s
// double angular_velocity_covariance[9];
// Vector3 linear_acceleration; // m/s^2
// double linear_acceleration_covariance[9];
// There is no rpy field - compute it from orientation if you need it.
struct MotorState {
float q; // Position (rad)
float dq; // Velocity (rad/s)
float tau_est; // Estimated Torque (Nm)
int16 temperature; // Motor Temperature (°C)
uint32 status_bits; // fault/limit bit flags; values are MotorState constants
};
struct JointState {
float q; // PJS Position (rad)
float dq; // PJS Velocity (rad/s)
float tau_est; // PJS Estimated Torque (Nm)
uint32 status_bits; // Status Bits
};
struct LowState {
std_msgs/Header header;
sensor_msgs/Imu imu_state; // Base IMU state (standard type)
MotorState motor_state[N_JOINTS]; // MS raw state
JointState joint_state[N_JOINTS]; // PJS derived state
};The error flags in
status_bitsare provided asMotorStatemessage constants (MOTOR_STALL_ERROR,OVER_CURRENT_ERROR, etc.):igris_c_sdk::msg::dds_::MotorState_Constants::*in C++,igc_sdk.MOTOR_STALL_ERRORetc. in Python. They are bit flags, so test them with(status_bits & FLAG), not equality.IMU angles are in radians. If you need roll/pitch/yaw, compute them from the
orientationquaternion.
All types are defined in the
igris_c_sdk::msg::dds_/igris_c_sdk::srv::dds_namespaces (the IDL'smodule). In actual use, reference them asigris_c_sdk::msg::dds_::TypeName_. The standard types arestd_msgs::msg::dds_::Header_andsensor_msgs::msg::dds_::Imu_.
6. System & Mode Control API (Initialization / Torque / Hand)
#include <igris_c_sdk/channel_factory.hpp>
#include <igris_c_sdk/igris_c_client.hpp>
using namespace igris_c_sdk;
int main() {
int domain_id = 0;
ChannelFactory::Instance()->Init(domain_id, "igris_c_IG01"); // domain + robot unit namespace (see SDK Overview section 4)
if (!ChannelFactory::Instance()->IsInitialized()) { return 1; }
IgrisC_Client client;
client.Init();
client.SetTimeout(10.0f); // 10-second timeout
// ... subsequent service calls
}PDU / Motor Initialization
using namespace igris_c_sdk::srv::dds_; // PduInitCmd_Request_Constants
igris_c_sdk::msg::dds_::ServiceResponse_ res = client.InitPdu(PduInitCmd_Request_Constants::PDU_INIT, 30000); // PDU power ON
res = client.InitPdu(PduInitCmd_Request_Constants::MOTOR_INIT, 30000); // Motor initialization only
res = client.InitPdu(PduInitCmd_Request_Constants::PDU_AND_MOTOR_INIT, 30000); // Power ON + motor (full sequence)
res = client.InitPdu(PduInitCmd_Request_Constants::PDU_INIT_NONE, 30000); // PDU power OFFinit_type(uint8, constants inPduInitCmd_Request_Constants) — Values actually handled by the bridge:PDU_INIT_NONE(0) — PDU power OFF (setPduPowerAsync(false))PDU_INIT(1) — PDU power ONMOTOR_INIT(2) — Motor initializationPDU_AND_MOTOR_INIT(3) — Power ON + motor initialization (full sequence)PDU_OFF(4) — PDU power OFF (handled the same asPDU_INIT_NONE)
timeout_ms— Timeout in ms (default 5000)Returned
ServiceResponse_—request_id,success,message,error_code(0=success)
Torque Control
using namespace igris_c_sdk::srv::dds_; // TorqueCmd_Request_Constants
igris_c_sdk::msg::dds_::ServiceResponse_ res = client.SetTorque(TorqueCmd_Request_Constants::TORQUE_ON, 30000);
res = client.SetTorque(TorqueCmd_Request_Constants::TORQUE_OFF, 30000);torque(uint8, constants inTorqueCmd_Request_Constants) —TORQUE_NONE(0, reserved) /TORQUE_ON(1) /TORQUE_OFF(2)timeout_ms— Timeout in ms (default 5000)Requesting
TORQUE_ONwhile torque is already on does nothing and returns success (success=true). (2.1.0 warned and re-ran the torque-on sequence.)
Hand Initialization
igris_c_sdk::msg::dds_::ServiceResponse_ res = client.InitHand(30000); // End-effector calibration/homing(For detailed hand control, see the Hand page)
PDU Status Query (pdustate, low frequency)
header, body_power(RELAY_OFF/ON), legs_power, estop, connect, battery(percent, 0–100), pdu_init_state
pdu_init_state— bit0 = PDU, bit1 = motor:PDU_NOT_INITIALIZED(0) /PDU_INITIALIZED(1) /MOTOR_INITIALIZED(2) /BOTH_INITIALIZED(3). The constants are inigris_c_sdk::msg::dds_::PduState_Constants(Pythonigc_sdk.BOTH_INITIALIZEDetc.).
Control State Query (robotstate)
The state field (control_state) of RobotState is a summary computed from the current controller mode and the preset playback status. The constants are igris_c_sdk::msg::dds_::RobotState_Constants::ROBOT_STATE_* (Python igc_sdk.ROBOT_STATE_*), and environment is ROBOT_ENV_REAL(0) / ROBOT_ENV_SIM(1).
Value | control_state | Description |
|---|---|---|
0 |
| Body power or torque is off, or the E-stop is engaged (regardless of mode) |
1 |
| No preset in |
2 |
| Stopped state ( |
3 |
|
|
4 |
| Preset playing in |
5 |
| Preset loaded and waiting in |
6 |
|
|
7 |
| Preset playing in |
8 |
|
|
9 |
| Reserved (not currently used) |
10 |
|
|
11–20 |
|
|
7. Python Binding
Installing the dist/*.whl from the igris_c_sdk_public repository into a Python environment allows usage with the same getter/setter pattern as C++. Examples are located in the python folder.
Message class names end in
_as in C++ (igc_sdk.LowCmd_()etc.). Constants are module-level constants (igc_sdk.PDU_INIT,igc_sdk.TORQUE_ON,igc_sdk.CONTROL_MODE_CMD_*,igc_sdk.KINEMATIC_MODE_PJS, etc.).In Python,
timeout_msis a required argument ofInitPdu/SetTorque/InitHand(no default).
import igris_c_sdk as igc_sdk
channel_instance = igc_sdk.ChannelFactory.Instance()
channel_instance.Init(0, "igris_c_IG01") # domain_id + robot unit namespace (see SDK Overview section 4)
# if the robot is on cyclonedds_ros_compatible:
# channel_instance.Init(0, "igris_c_IG01", "", igc_sdk.parse_topic_naming("ros_compatible"))
client = igc_sdk.IgrisC_Client()
client.Init()
client.SetTimeout(20.0)Real-time lowstate Subscription
lowstate_subscriber = igc_sdk.LowStateSubscriber("lowstate", igc_sdk.QosProfile.SensorData())
def lowstate_callback(msg):
imu = msg.imu_state()
q = imu.orientation() # q.x() q.y() q.z() q.w()
gyro = imu.angular_velocity()
acc = imu.linear_acceleration() # no rpy: compute it from q if you need it
m0 = msg.motor_state()[0]
print(m0.q(), m0.dq(), m0.tau_est(), m0.temperature(), m0.status_bits())
lowstate_subscriber.init(lowstate_callback)Service Calls (init / torque / control mode)
# PDU / Motor
client.InitPdu(igc_sdk.PDU_INIT, 5000)
client.InitPdu(igc_sdk.PDU_AND_MOTOR_INIT, 30000)
# Torque
client.SetTorque(igc_sdk.TORQUE_ON, 5000)
# Control mode (same as C++ SendControlModeCommand)
client.SendControlModeCommand(
igc_sdk.CONTROL_MODE_CMD_MOTION_PRESET, "HOME", False, 60000)
client.SendControlModeCommand(
igc_sdk.CONTROL_MODE_CMD_JOINT_POSITION_HOLD, "", False, 60000)
# Hand
client.InitHand(5000)Sending LowCmd (e.g., controlling neck yaw/pitch motors)
lowcmd_publisher = igc_sdk.LowCmdPublisher("lowcmd", igc_sdk.QosProfile.SensorData())
lowcmd_publisher.init()
low_cmd_msg = igc_sdk.LowCmd_()
for i in range(0, 31):
motor_cmd = low_cmd_msg.motors()[i]
motor_cmd.id(i); motor_cmd.q(0.0); motor_cmd.dq(0.0); motor_cmd.tau(0.0) # defaults
if i == 30: # Neck Pitch
motor_cmd.q(0.3); motor_cmd.kp(5); motor_cmd.kd(0.1)
elif i == 29:
motor_cmd.kp(2); motor_cmd.kd(0.05)
else:
motor_cmd.kp(0.0); motor_cmd.kd(0.0)
# kinematic_modes is a 5-element array by parallel-link group (waist/L_ankle/R_ankle/L_wrist/R_wrist).
# To change it, pass the full 5-element list to the setter (editing the list the getter returns has no effect).
low_cmd_msg.kinematic_modes([igc_sdk.KINEMATIC_MODE_PJS] * 5)
lowcmd_publisher.write(low_cmd_msg)The full example can be found in the GitHub repository.
ControlMode
This page reorganizes the former "Low Level Control" page into Control Mode, summarizing the meaning and switching methods of each control mode supported by IGRIS-C, as well as the message interfaces required for low-level control.
⚠️ The code on this page targets the v2.1.1 SDK. In 2.1.1, topic names (
rt/removed), type names, constant names (enums →uint8constants) and the BMS → PDU terminology changed, and a 2.1.0 SDK client cannot communicate with a 2.1.1 robot (see SDK Overview).
IGRIS-C's behavior hierarchy is switched via Control Mode. Mode-switch commands are sent to the controller through the SDK's
IgrisC_Client::SendControlModeCommand()(internally aControlModeCommandRequestRequest/Response), and the controller maps this to an internalRobotControlModeto change the control loop's behavior.
struct ControlModeCommandRequest_Request {
std_msgs/Header header;
string request_id; // ID for matching requests
uint8 command_type; // Mode to switch to (see list below, CONTROL_MODE_CMD_* constants)
string preset_id; // MOTION_PRESET only
boolean is_cyclic; // MOTION_PRESET_CYCLIC_TOGGLE only
};
// Response: ControlModeCommandRequest_Response { header, request_id, success, message, error_code(0=success) }1. List of Control Modes
command_type (uint8 constants, 19 types total, values 0–18). Controller behavior is based on robotHandler dispatch.
# | command_type | Controller mapping/behavior | Layer |
|---|---|---|---|
0 |
|
| High-Level |
1 |
|
| High-Level |
2 |
| → | - |
3 |
|
| High-Level |
4 |
| → | - |
5 |
| → | Low-Level |
6 |
| → | Low-Level |
7 |
| → | High-Level |
8 |
| → | High-Level |
9–18 |
| (internal use only) | Not provided |
command_type values are
uint8constants of the formigris_c_sdk::srv::dds_::ControlModeCommandRequest_Request_Constants::CONTROL_MODE_CMD_<NAME>(e.g.,CONTROL_MODE_CMD_MOTION_PRESET). In Python they areigc_sdk.CONTROL_MODE_CMD_<NAME>.
⚠️
CUSTOM_MODE_1–CUSTOM_MODE_10(9–18) are not currently provided to users. These are internal/extension-only entries with reserved slots in the enum only — do not use them when working with the SDK.
The two modes below cannot be requested by command; the controller enters them on its own when their conditions are met (details in Section 2, "Automatic Safety Modes").
Controller mode | Entered when | control_state |
|---|---|---|
| Right after startup, and automatically whenever power or torque turns OFF or the E-stop is pressed (renamed from 2.1.0's |
|
| Entered automatically when the walking policy detects a fall (new in 2.1.1) |
|
2. Description of Each Mode
Low-Level Control
The SDK directly commands each motor's targets (q·dq·tau·kp·kd) via
lowcmd. In this mode, control loop responsibility resides externally (with the SDK). (See section 5 for the message interface.)LOW_LEVEL_JOINT_CONTROL(5) —LOW_LEVEL_MODE. Direct joint control via SDK LowCmd.LOW_LEVEL_WALKMODE_ON(6) —LOW_LEVEL_WALK_MODE. Walking mode based on low-level control.
Low-level commands in MS (motor space) are projected onto the parallel links' motor-space safe set before they are applied. Measured values reported in
lowstateare not projected, so depending on the actual posture a joint value beyond its limit can be reported.External
handcmdis applied only inLOW_LEVEL_MODEandLOW_LEVEL_WALK_MODE(the HIGH_LEVEL modes use the preset's hand motion; all other modes ignore it).
High-Level Control
The controller's built-in logic receives higher-level commands and generates/maintains motion. Motion presets run at this layer; a preset requested from another mode switches to this layer automatically and then starts (see "Motion Preset" below).
HIGH_LEVEL_JOINT_CONTROL(8) —HIGH_LEVEL_MODE. Controller's high-level joint control.HIGH_LEVEL_WALKMODE_ON(7) —HIGH_LEVEL_WALK_MODE. High-level walking mode.
Walk
WALKMODE_ON(4) —WALK_MODE. Enters walking mode.Walk velocity commands are sent on the
cmd_veltopic (geometry_msgs/TwistStamped, the same on every lane). Examples:cyclonedds_twist_publisher,twist_publisher.py,ros_twist_publisher.⚠️ From 2.1.1, entering
WALKMODE_ONno longer moves the arms slowly first (the 2.1.0 slow start of 0.4 rad/s for 3 s is gone). The walking policy controls the whole body immediately, so clear the area around the robot — especially the arms' range of motion — before requesting it.WALKMODE_ONis not rejected inDAMPING_MODEeither, so make sure the robot is standing upright before requesting it.
Motion Preset
MOTION_PRESET(0) — Plays back a predefined motion specified bypreset_id(startMotionPreset). Runs inHIGH_LEVEL_MODEorHIGH_LEVEL_WALK_MODE. When requested from another mode, the robot switches automatically — toHIGH_LEVEL_WALK_MODEfromLOW_LEVEL_WALK_MODEorWALK_MODE, otherwise toHIGH_LEVEL_MODE— and then starts the preset. In the stopped state (below) onlyHOMEcan be run. Populate thepreset_idargument in the request.MOTION_PRESET_CYCLIC_TOGGLE(1) — Toggles whether the preset repeats cyclically (setMotionPresetCyclic). Uses theis_cyclicargument in the request.
Stop / Hold
JOINT_POSITION_HOLD(2) —JOINT_HOLD_MODE. Maintains (holds) the current joint positions. When the request succeeds, the robot enters the stopped state described below.MOTION_STOP(3) — Stops the ongoing motion and maintains the current posture (holdCurrentPosition). Only supported fromHIGH_LEVEL_MODE/HIGH_LEVEL_WALK_MODE.
Stopped state (Motion-Stop latch)
When
MOTION_STOPorJOINT_POSITION_HOLDsucceeds, the robot latches into the stopped state, andcontrol_stateisSTOP(2.1.0 reportedHIGH_MOTION_STANDBY/IDLE).In the stopped state, the only accepted requests are
MOTION_PRESET"HOME"and switches to the safe-stop modes (JOINT_HOLD_MODE,PASSIVE_MODE,DAMPING_MODE; PASSIVE and DAMPING are entered automatically). Every other mode switch or preset is rejected, with the reason in the responsemessage. (2.1.0 accepted onlyHOME.)Starting the
HOMEpreset releases the stopped state. If needed, the robot first switches toHIGH_LEVEL_MODE(orHIGH_LEVEL_WALK_MODE) automatically.
Automatic Safety Modes (DAMPING / PASSIVE)
DAMPING_MODE— a safe-stop mode with kp=0 that keeps only kd. It cannot be requested from the SDK or the app; the walking policy enters it automatically when it detects a fall inWALK_MODE,HIGH_LEVEL_WALK_MODEorLOW_LEVEL_WALK_MODE(default policy: pelvis tilt above 0.8 rad, or above 0.4 rad with angular velocity above 1.57 rad/s).control_stateisSTOP.⚠️ Whether DAMPING_MODE is entered depends only on the tilt criteria above, not on the hoist. Even while hanging on a hoist, the robot switches and goes limp once its tilt exceeds them.
PASSIVE_MODE— a standby mode that outputs no command (2.1.0'sTORQUE_OFF_MODE). Mode switches are accepted only while body power and torque are ON and the E-stop is released; a request in any other condition is rejected and the controller switches toPASSIVE_MODE.
Custom — Currently Not Provided
CUSTOM_MODE_1–CUSTOM_MODE_10(9–18) — Internal/extension-only slots not currently provided to users. These are defined in the enum but are not modes callable by SDK users, so do not use them in normal control.Note: the safety profile of these slots changed in 2.1.1. The waist and ankle parallel links use
SATURATE+SOFTand the wristsCLIP+HARD(2.1.0 usedCLIP+HARDeverywhere).
3. Mode Switching API (SDK)
Mode switching is performed via the single method IgrisC_Client::SendControlModeCommand().
igris_c_sdk::msg::dds_::ServiceResponse_ SendControlModeCommand(
uint8_t command_type, // ControlModeCommandRequest_Request_Constants::CONTROL_MODE_CMD_*
const std::string& preset_id = "", // MOTION_PRESET only
bool is_cyclic = false, // MOTION_PRESET_CYCLIC_TOGGLE only
int timeout_ms = 5000);Usage example (based on cyclonedds/cyclonedds_service.cpp):
using namespace igris_c_sdk::srv::dds_; // ControlModeCommandRequest_Request_Constants
// Execute motion preset "HOME" (at the HIGH_LEVEL layer)
res = client.SendControlModeCommand(
ControlModeCommandRequest_Request_Constants::CONTROL_MODE_CMD_MOTION_PRESET, "HOME", false, 60000);
// Hold current position
res = client.SendControlModeCommand(
ControlModeCommandRequest_Request_Constants::CONTROL_MODE_CMD_JOINT_POSITION_HOLD, "", false, 60000);
// Stop ongoing motion
res = client.SendControlModeCommand(
ControlModeCommandRequest_Request_Constants::CONTROL_MODE_CMD_MOTION_STOP, "", false, 60000);
// Switch to low-level joint control mode (use lowcmd afterward)
res = client.SendControlModeCommand(
ControlModeCommandRequest_Request_Constants::CONTROL_MODE_CMD_LOW_LEVEL_JOINT_CONTROL, "", false, 60000);Check the result via the returned ServiceResponse's success / error_code (0=success) / message.
Requesting the mode the robot is already in returns
success=false("Control mode is already …").On a mode change, the command blends from the previous mode's command to the new one over 0.5 s. Switches into or out of
PASSIVE_MODE,JOINT_HOLD_MODEorDAMPING_MODEtake effect immediately.
4. Service Topics Summary
Request/Response service channels used by the SDK client:
Function | Service Topic | SDK Method |
|---|---|---|
PDU/motor initialization |
|
|
Torque ON/OFF |
|
|
Hand initialization |
|
|
Control mode switching |
|
|
Mujoco simulation control (sim only) |
|
|
On the cyclonedds lanes (
cyclonedds_native/cyclonedds_ros_compatible), each service operates as a pair:<topic>/request(publishes the request) ·<topic>/response(subscribes to the response). Example:service/pdu_init/request·service/pdu_init/response. (The table above shows the base topic.)
How services are offered depends on the robot's transport mode (SDK Overview, Section 3). On the cyclonedds lanes they are the request/response topic pairs above, called with
IgrisC_Client. These pairs are not ROS 2 services, so they do not appear inros2 service list, even oncyclonedds_ros_compatible.On the
roslane, real rclcpp services with the same names are offered (/<ns>/service/pdu_initetc., typesigris_c_sdk/srv/PduInitCmd·TorqueCmd·HandInitRequest·ControlModeCommandRequest·MujocoSimCmd); call them with theros_serviceexample inros2/.Response types are per service (
PduInitCmd_Response_,TorqueCmd_Response_,HandInitRequest_Response_,ControlModeCommandRequest_Response_,MujocoSimCmd_Response_). They all carry the same fields —header,request_id,success,message,error_code— and theIgrisC_Clientmethods return them as the commonigris_c_sdk::msg::dds_::ServiceResponse_.
Low-level streams: lowcmd (LowCmd, commands) / lowstate (LowState, status).
5. Low-Level Control Message Interface
IGRIS-C Topic List
Low-level Cmd & State (high frequency):
lowcmd/lowstateService State (low frequency):
pdustate/robotstate
Common (igris_c_sdk::msg::dds_ / igris_c_sdk::srv::dds_)
const uint32 N_JOINTS = 31; // Fixed DOF Num
// Kinematic Space (MS=Motor Space/AB, PJS=Parallel Joint Space/PR)
// uint8 constants, not an enum (LowCmd_Constants)
uint8 KINEMATIC_MODE_MS = 0;
uint8 KINEMATIC_MODE_PJS = 1;
// Motor control is HYBRID mode only. (The former MotorMode { CURRENT, HYBRID } enum has been removed.)Command Side
struct MotorCmd { // HYBRID only
uint16 id; // MS: motor index / PJS: virtual joint index
float q; // Position (rad)
float dq; // Velocity (rad/s)
float tau; // Feedforward Torque (Nm)
float kp; // kP
float kd; // kD
};
struct LowCmd {
std_msgs/Header header;
uint8 kinematic_modes[5]; // Per parallel-link group: waist / L_ankle / R_ankle / L_wrist / R_wrist
MotorCmd motors[31]; // Fixed length, 31 DOF
};// Usage example: set all groups to the same mode
cmd.kinematic_modes().fill(igris_c_sdk::msg::dds_::LowCmd_Constants::KINEMATIC_MODE_PJS);State Side
// imu_state is the standard sensor_msgs/Imu, not a custom struct:
// std_msgs/Header header;
// Quaternion orientation; // named x/y/z/w fields
// double orientation_covariance[9];
// Vector3 angular_velocity; // rad/s
// double angular_velocity_covariance[9];
// Vector3 linear_acceleration; // m/s^2
// double linear_acceleration_covariance[9];
// There is no rpy field - compute it from orientation if you need it.
struct MotorState {
float q; // Position (rad)
float dq; // Velocity (rad/s)
float tau_est; // Estimated Torque (Nm)
int16 temperature; // Motor Temperature (°C)
uint32 status_bits; // fault/limit bit flags; values are MotorState constants
};
struct JointState {
float q; // PJS Position (rad)
float dq; // PJS Velocity (rad/s)
float tau_est; // PJS Estimated Torque (Nm)
uint32 status_bits; // Status Bits
};
struct LowState {
std_msgs/Header header;
sensor_msgs/Imu imu_state; // Base IMU state (standard type)
MotorState motor_state[N_JOINTS]; // MS raw state
JointState joint_state[N_JOINTS]; // PJS derived state
};The error flags in
status_bitsare provided asMotorStatemessage constants (MOTOR_STALL_ERROR,OVER_CURRENT_ERROR, etc.):igris_c_sdk::msg::dds_::MotorState_Constants::*in C++,igc_sdk.MOTOR_STALL_ERRORetc. in Python. They are bit flags, so test them with(status_bits & FLAG), not equality.IMU angles are in radians. If you need roll/pitch/yaw, compute them from the
orientationquaternion.
All types are defined in the
igris_c_sdk::msg::dds_/igris_c_sdk::srv::dds_namespaces (the IDL'smodule). In actual use, reference them asigris_c_sdk::msg::dds_::TypeName_. The standard types arestd_msgs::msg::dds_::Header_andsensor_msgs::msg::dds_::Imu_.
6. System & Mode Control API (Initialization / Torque / Hand)
#include <igris_c_sdk/channel_factory.hpp>
#include <igris_c_sdk/igris_c_client.hpp>
using namespace igris_c_sdk;
int main() {
int domain_id = 0;
ChannelFactory::Instance()->Init(domain_id, "igris_c_IG01"); // domain + robot unit namespace (see SDK Overview section 4)
if (!ChannelFactory::Instance()->IsInitialized()) { return 1; }
IgrisC_Client client;
client.Init();
client.SetTimeout(10.0f); // 10-second timeout
// ... subsequent service calls
}PDU / Motor Initialization
using namespace igris_c_sdk::srv::dds_; // PduInitCmd_Request_Constants
igris_c_sdk::msg::dds_::ServiceResponse_ res = client.InitPdu(PduInitCmd_Request_Constants::PDU_INIT, 30000); // PDU power ON
res = client.InitPdu(PduInitCmd_Request_Constants::MOTOR_INIT, 30000); // Motor initialization only
res = client.InitPdu(PduInitCmd_Request_Constants::PDU_AND_MOTOR_INIT, 30000); // Power ON + motor (full sequence)
res = client.InitPdu(PduInitCmd_Request_Constants::PDU_INIT_NONE, 30000); // PDU power OFFinit_type(uint8, constants inPduInitCmd_Request_Constants) — Values actually handled by the bridge:PDU_INIT_NONE(0) — PDU power OFF (setPduPowerAsync(false))PDU_INIT(1) — PDU power ONMOTOR_INIT(2) — Motor initializationPDU_AND_MOTOR_INIT(3) — Power ON + motor initialization (full sequence)PDU_OFF(4) — PDU power OFF (handled the same asPDU_INIT_NONE)
timeout_ms— Timeout in ms (default 5000)Returned
ServiceResponse_—request_id,success,message,error_code(0=success)
Torque Control
using namespace igris_c_sdk::srv::dds_; // TorqueCmd_Request_Constants
igris_c_sdk::msg::dds_::ServiceResponse_ res = client.SetTorque(TorqueCmd_Request_Constants::TORQUE_ON, 30000);
res = client.SetTorque(TorqueCmd_Request_Constants::TORQUE_OFF, 30000);torque(uint8, constants inTorqueCmd_Request_Constants) —TORQUE_NONE(0, reserved) /TORQUE_ON(1) /TORQUE_OFF(2)timeout_ms— Timeout in ms (default 5000)Requesting
TORQUE_ONwhile torque is already on does nothing and returns success (success=true). (2.1.0 warned and re-ran the torque-on sequence.)
Hand Initialization
igris_c_sdk::msg::dds_::ServiceResponse_ res = client.InitHand(30000); // End-effector calibration/homing(For detailed hand control, see the Hand page)
PDU Status Query (pdustate, low frequency)
header, body_power(RELAY_OFF/ON), legs_power, estop, connect, battery(percent, 0–100), pdu_init_state
pdu_init_state— bit0 = PDU, bit1 = motor:PDU_NOT_INITIALIZED(0) /PDU_INITIALIZED(1) /MOTOR_INITIALIZED(2) /BOTH_INITIALIZED(3). The constants are inigris_c_sdk::msg::dds_::PduState_Constants(Pythonigc_sdk.BOTH_INITIALIZEDetc.).
Control State Query (robotstate)
The state field (control_state) of RobotState is a summary computed from the current controller mode and the preset playback status. The constants are igris_c_sdk::msg::dds_::RobotState_Constants::ROBOT_STATE_* (Python igc_sdk.ROBOT_STATE_*), and environment is ROBOT_ENV_REAL(0) / ROBOT_ENV_SIM(1).
Value | control_state | Description |
|---|---|---|
0 |
| Body power or torque is off, or the E-stop is engaged (regardless of mode) |
1 |
| No preset in |
2 |
| Stopped state ( |
3 |
|
|
4 |
| Preset playing in |
5 |
| Preset loaded and waiting in |
6 |
|
|
7 |
| Preset playing in |
8 |
|
|
9 |
| Reserved (not currently used) |
10 |
|
|
11–20 |
|
|
7. Python Binding
Installing the dist/*.whl from the igris_c_sdk_public repository into a Python environment allows usage with the same getter/setter pattern as C++. Examples are located in the python folder.
Message class names end in
_as in C++ (igc_sdk.LowCmd_()etc.). Constants are module-level constants (igc_sdk.PDU_INIT,igc_sdk.TORQUE_ON,igc_sdk.CONTROL_MODE_CMD_*,igc_sdk.KINEMATIC_MODE_PJS, etc.).In Python,
timeout_msis a required argument ofInitPdu/SetTorque/InitHand(no default).
import igris_c_sdk as igc_sdk
channel_instance = igc_sdk.ChannelFactory.Instance()
channel_instance.Init(0, "igris_c_IG01") # domain_id + robot unit namespace (see SDK Overview section 4)
# if the robot is on cyclonedds_ros_compatible:
# channel_instance.Init(0, "igris_c_IG01", "", igc_sdk.parse_topic_naming("ros_compatible"))
client = igc_sdk.IgrisC_Client()
client.Init()
client.SetTimeout(20.0)Real-time lowstate Subscription
lowstate_subscriber = igc_sdk.LowStateSubscriber("lowstate", igc_sdk.QosProfile.SensorData())
def lowstate_callback(msg):
imu = msg.imu_state()
q = imu.orientation() # q.x() q.y() q.z() q.w()
gyro = imu.angular_velocity()
acc = imu.linear_acceleration() # no rpy: compute it from q if you need it
m0 = msg.motor_state()[0]
print(m0.q(), m0.dq(), m0.tau_est(), m0.temperature(), m0.status_bits())
lowstate_subscriber.init(lowstate_callback)Service Calls (init / torque / control mode)
# PDU / Motor
client.InitPdu(igc_sdk.PDU_INIT, 5000)
client.InitPdu(igc_sdk.PDU_AND_MOTOR_INIT, 30000)
# Torque
client.SetTorque(igc_sdk.TORQUE_ON, 5000)
# Control mode (same as C++ SendControlModeCommand)
client.SendControlModeCommand(
igc_sdk.CONTROL_MODE_CMD_MOTION_PRESET, "HOME", False, 60000)
client.SendControlModeCommand(
igc_sdk.CONTROL_MODE_CMD_JOINT_POSITION_HOLD, "", False, 60000)
# Hand
client.InitHand(5000)Sending LowCmd (e.g., controlling neck yaw/pitch motors)
lowcmd_publisher = igc_sdk.LowCmdPublisher("lowcmd", igc_sdk.QosProfile.SensorData())
lowcmd_publisher.init()
low_cmd_msg = igc_sdk.LowCmd_()
for i in range(0, 31):
motor_cmd = low_cmd_msg.motors()[i]
motor_cmd.id(i); motor_cmd.q(0.0); motor_cmd.dq(0.0); motor_cmd.tau(0.0) # defaults
if i == 30: # Neck Pitch
motor_cmd.q(0.3); motor_cmd.kp(5); motor_cmd.kd(0.1)
elif i == 29:
motor_cmd.kp(2); motor_cmd.kd(0.05)
else:
motor_cmd.kp(0.0); motor_cmd.kd(0.0)
# kinematic_modes is a 5-element array by parallel-link group (waist/L_ankle/R_ankle/L_wrist/R_wrist).
# To change it, pass the full 5-element list to the setter (editing the list the getter returns has no effect).
low_cmd_msg.kinematic_modes([igc_sdk.KINEMATIC_MODE_PJS] * 5)
lowcmd_publisher.write(low_cmd_msg)The full example can be found in the GitHub repository.

