Skip to main content

GR-EndEffector

1. Overview

GR-EndEffector is the DDS bridge for Fourier dexterous hands. The runtime application EndEffectorCore receives hand joint commands from DDS, writes PVC commands to the physical hand SDK, and publishes hand state, tactile data, device metadata, and HAL error codes back to DDS.

The current startup path initializes JointDataNode directly. The FDH6 and FDH12 plugin path is not loaded by the default runtime configuration.

ItemValue
Runtime nameEndEffectorCore
Domain ID123
Supported hardwareFDH-6 and FDH-12 dexterous hands
Default handsLeft and right hand enabled
Default IPsLeft: 192.168.137.19; Right: 192.168.137.39
Group namesleft_end_effector, right_end_effector

2. Runtime Workflow

Text
User DDS publisher
-> end_effector_cmd
-> JointDataNode command callback
-> per-hand PVC command buffer
-> per-hand worker loop
-> DexHand SDK set_pvc() / get_pvc()
-> end_effector_state, end_effector_tactile, end_effector_metainfo, hal_endeffector_error_code

Each enabled hand has a worker loop with an approximately 10 ms cycle. The worker reads PVC state from the SDK, writes the latest PVC command, reads tactile data when the detected device is FDH-12, and reads raw hardware error codes.

3. DDS Interface

3.1 Topics

Subscribed

TopicMessage TypeRateDescription
end_effector_cmdfourier_msgs_pod/msg/EndEffectorGroupCmdOn receiveJoint command for the left and right hand

Published

TopicMessage TypeRateDescription
end_effector_statefourier_msgs_pod/msg/EndEffectorState100 HzConcatenated hand joint state: position, velocity, effort
end_effector_tactilefourier_msgs/msg/GripperSensorData100 HzTwo tactile surfaces, one per hand; data is populated for FDH-12
end_effector_metainfostd_msgs/msg/BaseDataType1 HzDevice name, type, driver version, and hardware version
hal_endeffector_error_codefourier_msgs/msg/ErrorCodes10 HzHAL-format end-effector error-code list

3.2 Command Fields

EndEffectorGroupCmd routes commands by group_names. The runtime only consumes entries whose group name is exactly left_end_effector or right_end_effector.

FieldUsage
group_names[i]Target hand group name
commands[i].position[j]Target joint position for joint j
commands[i].velocity[j]Target joint velocity for joint j
commands[i].acceleration[j]Present in the command type; the runtime does not copy it to the SDK PVC buffer
commands[i].effort[j]Target effort / feed-forward value for joint j

3.3 State Fields

EndEffectorState is published as flat arrays rather than separate per-hand groups:

FieldUsage
positionCurrent joint positions
velocityCurrent joint velocities
effortCurrent joint effort/current channel from PVC data
state_sizeleft_dof + right_dof

The first left_dof entries are the left hand, followed by right_dof entries for the right hand.

3.4 Metadata Fields

end_effector_metainfo publishes BaseDataType.arg_string with eight positions:

IndexMeaning
0Left hand device name, for example fdhv1 or fdhv2
1Left hand type
2Left hand driver version
3Left hand hardware version
4Right hand device name
5Right hand type
6Right hand driver version
7Right hand hardware version

FDH-12 is detected when the SDK device name is fdhv2; only then does the runtime read tactile matrices for that hand.

3.5 Tactile Fields

end_effector_tactile always publishes two TactileSensorSurface entries:

FieldValue
sensor_count2
tactile_sensors[0].sensor_id0
tactile_sensors[0].sensor_nameleft_end_effector
tactile_sensors[1].sensor_id1
tactile_sensors[1].sensor_nameright_end_effector
uint8_data.rows, uint8_data.cols, uint8_data.dataRow-major tactile matrix when available

For a hand without tactile support, the corresponding matrix remains empty.

3.6 Error-Code Fields

hal_endeffector_error_code publishes ErrorCodes with source = 3. Each non-status hardware error is converted to ErrorCode.high32:

Text
bits [31:28] sub-module, currently 0
bits [27:12] raw hardware error code
bits [11:4] instance ID: 0x13 left hand, 0x27 right hand
bits [3:0] severity: 1 status, 2 warning, 3 fault

ErrorCode.low32 is set to 0.

4. Runtime Assumptions

The examples below assume the robot has already been delivered with the EndEffectorCore runtime installed and started by the product deployment. Users interact with the running module through DDS clients.

Before running client-side DDS examples, confirm:

  1. The robot-side EndEffectorCore runtime is running.
  2. The client environment includes fourierdds_py and the generated Fourier message packages.
  3. The client can communicate on DDS domain 123.
  4. The left and right hand IPs have been configured during deployment.

5. Basic Examples

5.1 Publish a Hand Command

This example fills both group names, waits for DDS discovery, and publishes the same target position to both hands.

Python
import time

from fourierdds_py import DDSInterface, PublisherQosProfile
import fourier_msgs_pod.msg.RobotJointCmd as RobotJointCmd

DOMAIN_ID = 123
DOF = 12
GROUP_NAMES = ("left_end_effector", "right_end_effector")

def set_string(char_array, value):
data = value.encode("utf-8")
if len(data) >= 64:
raise ValueError("group name must be shorter than 64 bytes")
for i, byte in enumerate(data):
char_array[i] = byte
char_array[len(data)] = 0

def set_positions(cmd, positions):
for i, position in enumerate(positions):
cmd.position()[i] = float(position)
cmd.velocity()[i] = 0.0
cmd.acceleration()[i] = 0.0
cmd.effort()[i] = 0.0

dds = DDSInterface(domain_id=DOMAIN_ID)
pub = dds.create_publisher(
RobotJointCmd.EndEffectorGroupCmdPubSubType,
"end_effector_cmd",
qos_profile=PublisherQosProfile.default(),
)
time.sleep(3)

msg = RobotJointCmd.EndEffectorGroupCmd()
set_string(msg.group_names()[0].content(), GROUP_NAMES[0])
set_string(msg.group_names()[1].content(), GROUP_NAMES[1])

target = [0.5] * DOF
set_positions(msg.commands()[0], target)
set_positions(msg.commands()[1], target)

pub.publish(msg)

5.2 Subscribe to Hand State

Python
import time

from fourierdds_py import DDSInterface, SubscriberQosProfile
import fourier_msgs_pod.msg.RobotJointState as RobotJointState

dds = DDSInterface(domain_id=123)

def on_state(msg):
size = msg.state_size()
pos = msg.position()
vel = msg.velocity()
eff = msg.effort()
print(
f"state_size={size} "
f"joint[0]: pos={pos[0]:.4f}, vel={vel[0]:.4f}, effort={eff[0]:.4f}"
)

dds.create_subscription(
RobotJointState.EndEffectorStatePubSubType,
"end_effector_state",
on_state,
qos_profile=SubscriberQosProfile.default(),
)

while True:
time.sleep(0.1)

5.3 Subscribe to Device Metadata

Python
import time

from fourierdds_py import DDSInterface, SubscriberQosProfile
import std_msgs.msg.BaseStructType as BaseStructType

DDS_DOMAIN_ID = 123
FIELDS = ("name", "type", "driver_ver", "hw_ver")

dds = DDSInterface(domain_id=DDS_DOMAIN_ID)

def on_metainfo(msg):
values = msg.arg_string()
size = values.size() if hasattr(values, "size") else len(values)

for side, offset in (("left", 0), ("right", 4)):
print(f"{side} hand:")
for index, field in enumerate(FIELDS):
pos = offset + index
value = values[pos] if pos < size else ""
print(f" {field}: {value or '(empty)'}")

dds.create_subscription(
BaseStructType.BaseDataTypePubSubType,
"end_effector_metainfo",
on_metainfo,
qos_profile=SubscriberQosProfile.default(),
)

while True:
time.sleep(0.1)

5.4 Subscribe to Tactile Data

Python
import time

from fourierdds_py import DDSInterface, SubscriberQosProfile
import fourier_msgs.msg.TactileSensor as TactileSensor

dds = DDSInterface(domain_id=123)

def on_tactile(msg):
sensors = msg.tactile_sensors()
count = sensors.size() if hasattr(sensors, "size") else len(sensors)
print(f"sensor_count={msg.sensor_count()}")

for index in range(count):
surface = sensors[index]
matrix = surface.uint8_data()
rows = matrix.rows()
cols = matrix.cols()
data = matrix.data()

print(f"[{surface.sensor_id()}] {surface.sensor_name()}: {rows}x{cols}")
if rows == 0 or cols == 0:
continue

for row in range(rows):
values = [data[row * cols + col] for col in range(min(cols, 16))]
print(" " + " ".join(f"{value:3d}" for value in values))

dds.create_subscription(
TactileSensor.GripperSensorDataPubSubType,
"end_effector_tactile",
on_tactile,
qos_profile=SubscriberQosProfile.default(),
)

while True:
time.sleep(0.1)

5.5 Subscribe to HAL Error Codes

Python
import time

from fourierdds_py import DDSInterface, SubscriberQosProfile
import fourier_msgs.msg.ErrorCodes as ErrorCodes

SEVERITY_MAP = {
1: "STATUS",
2: "WARNING",
3: "FAULT",
}

INSTANCE_MAP = {
0x13: "LEFT",
0x27: "RIGHT",
}

dds = DDSInterface(domain_id=123)

def decode_high32(high32):
return {
"raw_code": (high32 >> 12) & 0xFFFF,
"instance_id": (high32 >> 4) & 0xFF,
"severity": high32 & 0x0F,
}

def on_error_codes(msg):
codes = msg.error_codes()
count = codes.size() if hasattr(codes, "size") else len(codes)
print(f"source={msg.source()} count={count}")

if count == 0:
print(" no active end-effector errors")
return

for index in range(count):
item = codes[index]
decoded = decode_high32(item.high32())
hand = INSTANCE_MAP.get(decoded["instance_id"], "UNKNOWN")
severity = SEVERITY_MAP.get(decoded["severity"], "UNKNOWN")
print(
f"[{index}] hand={hand} severity={severity} "
f"raw=0x{decoded['raw_code']:04X} "
f"high32=0x{item.high32():08X} low32=0x{item.low32():08X}"
)

dds.create_subscription(
ErrorCodes.ErrorCodesPubSubType,
"hal_endeffector_error_code",
on_error_codes,
qos_profile=SubscriberQosProfile.default(),
)

while True:
time.sleep(0.1)

6. Troubleshooting and Reminders

6.1 Group Names Must Match Exactly

Commands are routed by group_names. If the strings do not match left_end_effector or right_end_effector, the runtime ignores that command entry.

6.2 Wait for DDS Discovery

After creating a publisher, wait about 3 seconds before publishing the first command so DDS discovery can complete.

6.3 Check Power and Network

If the runtime reports that no device is connected, check hand power, emergency stop state, and network reachability to the configured hand IPs.

Bash
ping 192.168.137.19
ping 192.168.137.39

6.4 Tactile Data Requires FDH-12

The tactile topic can still publish two surfaces when a hand has no tactile support, but the matrix for that hand is empty.

6.5 Keep Commands Within Hardware Limits

The examples use 0.0 for open and 1.0 for closed. Confirm the valid position range for the specific hand hardware before sending other values.