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.
| Item | Value |
|---|---|
| Runtime name | EndEffectorCore |
| Domain ID | 123 |
| Supported hardware | FDH-6 and FDH-12 dexterous hands |
| Default hands | Left and right hand enabled |
| Default IPs | Left: 192.168.137.19; Right: 192.168.137.39 |
| Group names | left_end_effector, right_end_effector |
2. Runtime Workflow
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
| Topic | Message Type | Rate | Description |
|---|---|---|---|
end_effector_cmd | fourier_msgs_pod/msg/EndEffectorGroupCmd | On receive | Joint command for the left and right hand |
Published
| Topic | Message Type | Rate | Description |
|---|---|---|---|
end_effector_state | fourier_msgs_pod/msg/EndEffectorState | 100 Hz | Concatenated hand joint state: position, velocity, effort |
end_effector_tactile | fourier_msgs/msg/GripperSensorData | 100 Hz | Two tactile surfaces, one per hand; data is populated for FDH-12 |
end_effector_metainfo | std_msgs/msg/BaseDataType | 1 Hz | Device name, type, driver version, and hardware version |
hal_endeffector_error_code | fourier_msgs/msg/ErrorCodes | 10 Hz | HAL-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.
| Field | Usage |
|---|---|
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:
| Field | Usage |
|---|---|
position | Current joint positions |
velocity | Current joint velocities |
effort | Current joint effort/current channel from PVC data |
state_size | left_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:
| Index | Meaning |
|---|---|
0 | Left hand device name, for example fdhv1 or fdhv2 |
1 | Left hand type |
2 | Left hand driver version |
3 | Left hand hardware version |
4 | Right hand device name |
5 | Right hand type |
6 | Right hand driver version |
7 | Right 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:
| Field | Value |
|---|---|
sensor_count | 2 |
tactile_sensors[0].sensor_id | 0 |
tactile_sensors[0].sensor_name | left_end_effector |
tactile_sensors[1].sensor_id | 1 |
tactile_sensors[1].sensor_name | right_end_effector |
uint8_data.rows, uint8_data.cols, uint8_data.data | Row-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:
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:
- The robot-side
EndEffectorCoreruntime is running. - The client environment includes
fourierdds_pyand the generated Fourier message packages. - The client can communicate on DDS domain
123. - 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.
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
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
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
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
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.
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.