This is a Viam module for UFactory's xArm 6, xArm 7, xArm 850, and Lite 6 collaborative arms.
Note
For more information on modules, see Modular Resources.
This module is particularly useful in applications that require an xArm to be operated alongside other resources (cameras, sensors, actuators, CV pipelines) offered by the Viam Platform.
- Getting Started
- Configure your xArm
- Error Handling
- DoCommand Reference
- UFactory Studio Proxy
- Gripper
- Gripper Lite
- Vacuum Gripper
- Vacuum Gripper Lite
- UFactory xArm Resources
- Add a machine in the Viam app.
- Navigate to the CONFIGURE tab of your machine's page.
- Click the + icon next to your machine part and select Configuration block.
- Search for and select
ufactory/xArm6,ufactory/xArm7,ufactory/xArm850, orufactory/lite6. - Click Add to machine, enter a name for your arm and click Add to machine once more.
- Set the
hostattribute to your arm's IP address (found on the sticker on the control box).
Copy and paste the following attributes into your JSON configuration:
{
"host": "0.0.0.0",
"speed_degs_per_sec": 30
}| Name | Type | Inclusion | Default | Description |
|---|---|---|---|---|
host |
string | Required | — | IP address of the xArm. Found on the sticker on the control box. See Networking below. |
port |
int | Optional | 502 |
TCP port for the arm's Modbus interface. |
speed_degs_per_sec |
float32 | Optional | 60 |
Joint speed in degrees/second. Must be between 3 and 180. |
acceleration_degs_per_sec_per_sec |
float32 | Optional | 381.67 |
Joint acceleration in degrees/second². Must not exceed 1145. |
collision_sensitivity |
int | Optional | 3 |
Collision detection sensitivity from 0 (off) to 5. Higher values trigger the emergency stop with less force. |
bad-joints |
[]int | Optional | — | List of joint indices that cannot move. The arm will be configured to lock those joints at their current position on startup. |
motion |
string | Optional | builtin |
Name of the motion service to use for MoveToPosition API calls. |
use_urdfs |
bool | Optional | false |
When true, builds the kinematic model from the arm's URDF file, attaching mesh-based collision geometries to each link for more accurate collision checking. |
mesh_decimation_ratios |
[]float64 | Optional | 0.1 per link |
Per-link mesh simplification ratios when use_urdfs is true. Each value must be in [0, 1]; 0.5 reduces a link to 50% of its original triangle count. List length must match the number of joints (6 for xArm6/Lite6, 7 for xArm7/xArm850). |
trajectory_generator |
object | Optional | — | Configuration for an external trajectory generator ML model service. |
ufactory-studio-proxy |
bool | Optional | false |
When true, starts a local reverse proxy to the arm's UFactory Studio web UI. See UFactory Studio Proxy. |
ufactory-studio-proxy-port |
int | Optional | 18333 |
Local port for the Studio proxy. |
- Connect an Ethernet cable between the arm's control box and a USB-C hub connected to your Mac.
- Open System Settings → Network.
- Click the USB-C device under "Other services." Ensure it shows a green indicator (not red/disconnected).
- Click Details... → TCP/IP.
- Set Configure IPv4 to Manually.
- Set the IP address to any address on the same subnet as the arm (e.g., if the arm is
192.168.1.2, use192.168.1.10). - Set the Subnet Mask to
255.255.255.0and click OK. - Verify with
ping <arm-ip>. Use the arm's IP (from the control box sticker) in your Viam config — not the address you assigned to your Mac.
- Connect an Ethernet cable from the arm's control box to your machine.
- Assign your machine an IP address on the same subnet as the arm:
sudo ip addr add 192.168.1.10/24 dev eth0
sudo ip link set eth0 up- Verify connectivity:
ping 192.168.1.2Note
Replace eth0 with your actual Ethernet interface name (find it with ip link show) and replace 192.168.1.10/192.168.1.2 with addresses appropriate for your arm's subnet.
The trajectory_generator attribute connects the arm to an external ML model service (such as trajex) for time-optimal trajectory generation. When configured, all MoveThroughJointPositions calls are routed through the service instead of the built-in interpolator.
{
"trajectory_generator": {
"service": "my-trajex-service",
"path_tolerance_delta_rads": 0.1,
"waypoint_deduplication_tolerance_rads": 0.001
}
}| Field | Type | Default | Description |
|---|---|---|---|
service |
string | Required | Name of the ML model service. |
path_tolerance_delta_rads |
float64 | 0.1 |
Maximum deviation from the straight-line path between waypoints, in radians. |
path_colinearization_ratio |
float64 | 0 (disabled) |
Ratio used to merge nearly-collinear waypoints. |
waypoint_deduplication_tolerance_rads |
float64 | 0.001 |
Waypoints closer than this value (in radians) are treated as duplicates and merged. |
To use your xArm alongside other components, add it to the frame system:
"frame": {
"parent": "world"
}For an attached gripper, set its parent to the arm's name:
{
"name": "gripper",
"api": "rdk:component:gripper",
"model": "viam:ufactory:gripper",
"attributes": {},
"frame": {
"parent": "my-xarm",
"translation": { "x": 0, "y": 0, "z": 0 },
"geometry": {
"type": "box",
"x": 110, "y": 160, "z": 240,
"translation": { "x": 0, "y": 0, "z": 0 }
},
"orientation": {
"type": "ov_degrees",
"value": { "x": 0, "y": 0, "z": 1, "th": 0 }
}
}
}When a collision or other fault occurs, the arm enters an error state and will not accept motion commands. The driver attempts to clear transient errors automatically on each command. A collision (overcurrent error) requires manual intervention:
- Remove any obstacles causing the collision.
- Clear the error using
DoCommand(Go):
xArmComponent.DoCommand(context.Background(), map[string]interface{}{"clear_error": true})Or via Python SDK:
await arm.do_command({"clear_error": True})- The arm will return to normal operation automatically.
To inspect current arm state or error codes:
// Get current arm state
resp, _ := xArmComponent.DoCommand(context.Background(), map[string]interface{}{"get_state": true})
// resp["state"] contains raw state bytes
// Get current error code
resp, _ := xArmComponent.DoCommand(context.Background(), map[string]interface{}{"get_error": true})
// resp["error info"] contains raw error bytesThe following commands are available via DoCommand on the arm component.
Go:
// Set speed (degrees/second)
xArmComponent.DoCommand(ctx, map[string]interface{}{"set_speed": 50.0})
// Set acceleration (degrees/second²)
xArmComponent.DoCommand(ctx, map[string]interface{}{"set_acceleration": 100.0})
// Set both
xArmComponent.DoCommand(ctx, map[string]interface{}{
"set_speed": 50.0,
"set_acceleration": 100.0,
})Python:
await arm.do_command({"set_speed": 50.0, "set_acceleration": 100.0})resp, err := xArmComponent.DoCommand(ctx, map[string]interface{}{"load": ""})
// resp["load"] contains a []float64 of per-joint torque valuesNote
"setup_gripper": true must be included in any gripper move command.
// Open fully
xArmComponent.DoCommand(ctx, map[string]interface{}{
"setup_gripper": true,
"move_gripper": 850.0,
})
// Close
xArmComponent.DoCommand(ctx, map[string]interface{}{
"setup_gripper": true,
"move_gripper": 0.0,
})
// Get position (returns 0–850)
resp, _ := xArmComponent.DoCommand(ctx, map[string]interface{}{"get_gripper": true})
// resp["gripper_position"]
// Set speed (range 1–5000)
xArmComponent.DoCommand(ctx, map[string]interface{}{"set_gripper_speed": 2000.0})
// Get speed
resp, _ := xArmComponent.DoCommand(ctx, map[string]interface{}{"get_gripper_speed": true})
// resp["gripper_speed"]// Activate suction (grab)
xArmComponent.DoCommand(ctx, map[string]interface{}{"grab_vacuum": true})
// Release (open)
xArmComponent.DoCommand(ctx, map[string]interface{}{"open_vacuum": true})
// Get suction state
resp, _ := xArmComponent.DoCommand(ctx, map[string]interface{}{"get_vacuum_state": true})
// resp["vacuum_state"] is a boolNote
grab_vacuum, open_vacuum, and get_vacuum_state accept an optional connection_type ("plugin" or "contact") alongside the command to select the vacuum gripper's wiring interface. When omitted, the arm auto-detects the interface from the arm model.
// Override the auto-detected wiring interface
xArmComponent.DoCommand(ctx, map[string]interface{}{
"grab_vacuum": true,
"connection_type": "contact",
})Manual mode puts the arm into zero-gravity mode, allowing free movement by hand with gravity compensation active. Servos remain engaged — do not disable them.
// Enter manual mode
xArmComponent.DoCommand(ctx, map[string]interface{}{"enter_manual_mode": true})
// Exit manual mode and return to normal operation
xArmComponent.DoCommand(ctx, map[string]interface{}{"exit_manual_mode": true})Caution
Ensure the arm's payload and mounting orientation are correctly configured before entering manual mode, or gravity compensation will be inaccurate and the arm may drift.
The arm hosts UFactory Studio at http://<arm-ip>:18333. When viam-server and the arm are on different subnets (e.g., direct Ethernet connection), Studio may not be reachable from your browser.
Enable ufactory-studio-proxy to start a local reverse proxy on the viam-server host:
{
"host": "192.168.1.2",
"ufactory-studio-proxy": true,
"ufactory-studio-proxy-port": 18333
}Access Studio at http://<viam-server-ip>:18333. If port 18333 is in use, set ufactory-studio-proxy-port to a different value.
Caution
The proxy listens on all interfaces with no authentication. Anyone who can reach that port has full access to UFactory Studio, which can command the arm to move, change settings, and update firmware. Only enable this on trusted networks, or restrict access with firewall rules.
The standard two-finger gripper for xArm6/xArm7.
{
"arm": "my-xarm",
"gripper_speed": 2000
}| Name | Type | Inclusion | Description |
|---|---|---|---|
arm |
string | Required | Name of the arm component this gripper is attached to. |
gripper_speed |
int | Optional | Default speed on startup (1–5000). Uses firmware default if omitted. |
// Get current position (0–850)
resp, _ := gripperComponent.DoCommand(ctx, map[string]interface{}{"get": true})
// resp["pos"]
// Move to a specific position (0–850)
resp, _ := gripperComponent.DoCommand(ctx, map[string]interface{}{"set": 500.0})
// resp["position"]
// Set/get gripper speed (proxied to arm DoCommand)
resp, err := gripperComponent.DoCommand(context.Background(), map[string]interface{}{"set_gripper_speed": 2000})
resp, err := gripperComponent.DoCommand(context.Background(), map[string]interface{}{"get_gripper_speed": true})
// G2 gripper only — set/get the grasp current/torque (0-100%). Affects how hard the gripper squeezes.
resp, err := gripperComponent.DoCommand(context.Background(), map[string]interface{}{"set_gripper_torque": 50})
resp, err := gripperComponent.DoCommand(context.Background(), map[string]interface{}{"get_gripper_torque": true})
// G2 gripper only — close to a position with a force limit
gripperComponent.DoCommand(context.Background(), map[string]interface{}{
"grab_with_torque": map[string]interface{}{
"position": 100, // 0-850
"speed": 3000, // 1-5000
"torque": 100, // 0-100%
},
})Two-finger gripper for the Lite 6.
{
"arm": "my-xarm"
}// Open
gripperLiteComponent.DoCommand(ctx, map[string]interface{}{"gripper_lite_action": "open"})
// Close
gripperLiteComponent.DoCommand(ctx, map[string]interface{}{"gripper_lite_action": "close"})
// Stop
gripperLiteComponent.DoCommand(ctx, map[string]interface{}{"gripper_lite_action": "stop"})
// Check if holding something
resp, _ := gripperLiteComponent.DoCommand(ctx, map[string]interface{}{"gripper_lite_action": "is_closed"})
// resp["gripper_lite_action"]["is_closed"] is a boolFor use with the standard xArm vacuum gripper. Both wiring interfaces are supported: the older plug-in connection (arm drives TGPIO outputs 0/1) and the newer contact connection (TGPIO outputs 3/4). The interface is auto-detected from the arm model — xArm850 and xArm ≥1305 default to contact, other arms default to plugin — and can be overridden with connection_type.
{
"arm": "my-xarm",
"vacuum_length_mm": 48,
"connection_type": "contact"
}| Attribute | Type | Inclusion | Description |
|---|---|---|---|
arm |
string | Required | Name of the arm component this vacuum gripper is attached to. |
vacuum_length_mm |
number | Optional | Length of the vacuum gripper attachment, in millimeters. |
connection_type |
string | Optional | Vacuum wiring interface: "plugin" or "contact". Empty (default) auto-detects from the arm model. Set this explicitly if you're running an older plug-in gripper on an xArm850 or xArm ≥1305, since those arms auto-detect as contact. |
Vacuum gripper for the Lite 6. This model always uses the plug-in connection and ignores connection_type.
{
"arm": "my-xarm",
"vacuum_length_mm": 48
}Model viam:ufactory:ft_sensor exposes the UFactory wrist-mounted 6-axis
Force/Torque sensor as a Viam sensor. It depends on a configured xArm and reads
through the arm's controller connection. Requires controller firmware >= 1.8.3.
Supported arms: xArm6, xArm7, and xArm850. The lite6 is not supported.
Prerequisite: The sensor must be enabled and calibrated in UFactory Studio before use (Externals → Torque Sensor: confirm a real SN/firmware appear, then run payload identification / zeroing). This module only reads the sensor; it does not enable or commission it. If the sensor is not enabled and calibrated, reads will fail or return meaningless values.
{
"arm": "my-xarm"
}| Attribute | Type | Required | Description |
|---|---|---|---|
arm |
string | yes | Name of the xArm this sensor is attached to. |
GetReadings returns the six force/torque values keyed with their units:
{ "Fx_N": -0.987, "Fy_N": -2.923, "Fz_N": -18.356,
"TRx_Nm": -0.0012, "TRy_Nm": -0.0914, "TRz_Nm": 0.00698 }Forces (Fx_N, Fy_N, Fz_N) are in newtons; torques (TRx_Nm, TRy_Nm,
TRz_Nm) are in newton-metres.
| Command | Effect |
|---|---|
{"tare": true} |
Zero the sensor at the current reading. Hold the arm stationary at the unloaded reference pose first. |