Skip to main content

Universal Robots e-Series control toolkit built on ur_rtde

Project description

URKit

PyPI

URKit is a Python toolkit for Universal Robots e-Series robots that makes the common stuff simple and gets out of the way for everything else.

Built on ur_rtde, it packages the operations you reach for most: connecting, moving to named points, gripper control, telemetry, and I/O, while exposing the raw RTDE interfaces for anything deeper.


Table of Contents


Quick Start

pip install -U urkit

The -U (upgrade) flag ensures you always get the latest version — this project is in early development and changes frequently.

Requires Python 3.8+ and a Universal Robots e-Series (UR3e to UR30).

Robot Setup (one-time)

  1. Network: SystemSystemNetwork. Set a static IP on the robot and a matching one on your PC. Both addresses must share the same first three octets (the network), with a different last octet (the host). For example:
    • Robot: 192.168.1.50 / Subnet 255.255.255.0
    • PC: 192.168.1.1 / Netmask 255.255.255.0
    • Verify with ping 192.168.1.50. Connect via direct Ethernet cable or a switch.
  2. Remote Control: SystemRemote Control: Enable. Press the remote/local button on the pendant.
  3. Security: SecurityServices: enable RTDE and disable EtherNet/IP, PROFINET, or MODBUS if they're claiming RTDE registers. Save and restart.

That's it. No .urp files to run, no extra programs needed.

Example

If you have a Robotiq gripper, install the Robotiq Gripper Control URCap first: download from robotiq.com/support, copy the .urcap to a USB drive, and install via SettingsSystemURCaps.

from urkit import URRobot, ROBOTIQ_HAND_E  # or ROBOTIQ_2F_85, ROBOTIQ_2F_140, or gripper=None

robot = URRobot(ip="192.168.1.50", points="points.db", gripper=ROBOTIQ_HAND_E)
robot.gripper.activate()

robot.move_to("home")
robot.move_to("pick", offset=[0, 0, 0.05, 0, 0, 0])
robot.gripper.close()
robot.move_to("place")
robot.gripper.open()

The typical workflow:

  1. Teach points. Use the CLI to position the robot and save named waypoints.
  2. Write code. Create a robot, move to points by name, apply offsets, run sequences.
  3. Iterate. Add more points, tweak your code, repeat.

Interactive CLI

URKit provides two CLI tools: teach for interactive robot control, and points for browsing saved waypoints.

Teach Mode

The interactive teach pendant for moving the robot, saving points, and checking telemetry:

urkit teach 192.168.1.50             # with robot IP
urkit teach                          # reads IP from config.yaml

Flags:

Flag Description
ip Robot IP address (positional, overrides config)
--gripper Gripper preset: 2f-85, 2f-140, hand-e, digital, none
--gripper-pin Digital gripper output pin (default: 0)
--gripper-force Robotiq force 0-100
--gripper-speed Robotiq speed 0-100
--gripper-close-on-high Digital polarity: true or false
--points Path to points.db file (overrides config)
--config Path to config file (default: config.yaml in project root or CWD)
-e, --expert Disable safety speed clamping (full speed for goto/tcp-down)
-v, --verbose Show verbose output (debug connection issues)

Points Explorer

Browse saved waypoints with real-time search filtering. No robot connection needed:

urkit points                          # uses default points.db
urkit points test_points.db           # use specific database

Key Map

All movement and orientation keys support hold-to-repeat.

Movement Orientation Step Size
KeyAction
W / S+X / -X
A / D+Y / -Y
Q / E+Z / -Z
KeyAction
U / ORoll - / +
I / KPitch - / +
J / LYaw - / +
KeyAction
1Set linear step (mm)
2Set angular step (deg)
.Reset defaults (5 mm / 2°)
Gripper Points Controls
KeyAction
XOpen
CClose
VSet position (mm)
6Set speed (0-100)
7Set force (0-100)
Gripper line shows: `Connected 25.0mm (50%) F=100 S=100`
KeyAction
BSave current position
GGo to saved point (Space to cancel mid-move, ESC to exit)
HDelete saved point
POpen points explorer
RRename saved point
KeyAction
FFreedrive (OFF → ALL → XYZ+Rz)
MToggle frame (BASE / TOOL)
NGo To mode (Cartesian / Joint)
TOrient TCP down (180°)
YSave config to file
ESCExit

Joint Display

The teach pendant shows live joint angles alongside TCP position and orientation:

 Position      X=+0.432  Y=+0.111  Z=+0.227
 Orientation   R=+131.3  P=-121.0  Y= +8.0
 Joints        J1=+150.0  J2=+020.0  J3=+160.0
               J4=+050.0  J5=-080.0  J6=+157.0

Joint angles color-code proximity to mechanical limits:

  • Yellow: within 10% of joint range (warning)
  • Red: within 5% of joint range (danger)

UR e-Series joint limits:

Joint Range Notes
J1 (shoulder pan) ±360° Full rotation
J2 (shoulder lift) ±360° Full rotation
J3 (elbow) ±180° Physically restricted — shoulder lift gets in the way
J4 (wrist 1) ±360° Full rotation
J5 (wrist 2) ±360° Full rotation
J6 (wrist 3) ±360° Tool flange unlimited rotation

Thresholds scale with each joint's range, so warning zones feel proportional across all joints.

Safety

By default, Go To and TCP Down movements use a slow velocity (0.125 m/s) so its safer for anyone standing near the robot. The user's speed slider still applies as a global multiplier on top of this.

Delta movements (W/S/A/D/Q/E) use step-size-based velocities that scale with the speed slider set by the user.

To disable the slow default and use full speed, pass --expert or set expert_mode: true in your config:

urkit teach 192.168.1.50 --expert
# config.yaml
expert_mode: true

API Reference

Connecting

from urkit import URRobot, ROBOTIQ_HAND_E

robot = URRobot(ip="192.168.1.50", points="points.db", gripper=ROBOTIQ_HAND_E)

With custom motion defaults:

robot = URRobot(
    ip="192.168.1.50",
    points="points.db",
    gripper=ROBOTIQ_HAND_E,
    default_vel=0.5,    # m/s
    default_acc=0.3,    # m/s²
)

From a config file:

robot = URRobot.from_config("config.yaml")
robot = URRobot.from_config("config.yaml", ip="10.0.0.50")  # override IP

Grippers

Three built-in presets:

Preset Description
ROBOTIQ_HAND_E Robotiq 2F-140-E (Hand-E series)
ROBOTIQ_2F_85 Robotiq 2F-85
ROBOTIQ_2F_140 Robotiq 2F-140
robot.gripper.activate()              # required before open/close (Robotiq only)
robot.gripper.is_activated()          # check activation state

robot.gripper.open()                  # fully open (blocking by default)
robot.gripper.close()                 # fully closed, stops on contact
robot.gripper.open(wait=False)        # non-blocking return
robot.gripper.set_position_mm(20)     # 20mm open (Robotiq only, 0 = closed)
robot.gripper.set_position_percent(50) # 50% open (Robotiq only, 0 = open, 100 = closed)
robot.gripper.set_force(50)           # grip force: 0-100 (Robotiq only)
robot.gripper.set_speed(80)           # movement speed: 0-100 (Robotiq only)

Override preset values for custom fingers:

robot = URRobot(ip="192.168.1.50", points="points.db", gripper=ROBOTIQ_HAND_E, max_mm=120)

Digital I/O Grippers

Robotiq grippers use a serial protocol over the robot's RS485 port. If you have a suction cup, solenoid, or any actuator controlled by a single digital output pin, use DigitalGripperConfig instead. It just turns that pin on (close) and off (open).

from urkit import URRobot, DigitalGripperConfig

robot = URRobot(
    ip="192.168.1.50",
    points="points.db",
    gripper=DigitalGripperConfig(pin=3),  # pin 3 goes high = close
)

robot.gripper.open()    # turn pin 3 off
robot.gripper.close()   # turn pin 3 on

set_force() and set_speed() are not available for digital grippers.

Points & Motion

The points database is optional. Create a robot without one and attach later:

robot = URRobot(ip="192.168.1.50")
robot.points_db = "points.db"

Moving to Points

robot.move_to("pick")                      # linear move (default)
robot.move_to("pick", linear=False)        # joint move
robot.move_to("pick", vel=1.0, acc=0.5)    # override speed
robot.move_to("pick", asynchronous=True)   # non-blocking, returns immediately
  • Linear (moveL): TCP moves in a straight line. Predictable path, slower near complex orientations.
  • Joint (moveJ): Each joint moves simultaneously. Faster, but the TCP follows an arc.
  • Asynchronous: When asynchronous=True, the move runs in the UR controller's background thread and the method returns immediately.

Non-Blocking Moves

Start a move and do other things while it runs:

robot.move_to("pick", asynchronous=True)

# Poll until done
while robot.is_moving():
    time.sleep(0.01)

# Or cancel mid-move
robot.move_to("pick", asynchronous=True)
while robot.is_moving():
    if should_cancel:
        robot.stop()  # sends stopL + stopJ
        break
    time.sleep(0.01)

Teach pendant Go To uses this pattern internally — Space cancels the move and returns to the menu.

Pose Format

A pose is [x, y, z, rx, ry, rz]: position in meters and orientation as a rotation vector (axis-angle in radians). This is not RPY (roll/pitch/yaw). The teach pendant displays RPY in degrees, which is a different representation. Values you see on the pendant won't match get_tcp_pose() directly.

Offsets

Offsets are 6-element lists [dx, dy, dz, drx, dry, drz]:

robot.move_to("pick", offset=[0, 0, 0.05, 0, 0, 0])  # 5cm above pick

Resolve a Pose

Get a pose without moving. Useful for logging, comparisons, or custom motion:

pose = robot.get_pose("pick")
pose = robot.get_pose("pick", offset=[0, 0, 0.05, 0, 0, 0])  # with offset
robot.move_to(pose)  # move to the resolved pose later

Coordinate Frame

from urkit import MoveFrame

robot.move_frame = MoveFrame.TOOL   # default is BASE
robot.move_relative([0, 0, 0.05, 0, 0, 0])  # 5cm along tool Z
  • BASE (default): delta relative to robot base
  • TOOL: delta relative to TCP orientation

Points are tool-agnostic

Points are stored in the active TCP frame, so they work with any tool. If you swap grippers and set the correct TCP offset, your saved points remain valid.

Point Management

robot.save_point("here")
robot.point_names()           # ["home", "pick", "place"]
robot.rename_point("old", "new")
robot.delete_point("old")
robot.export_points("backup.json")
robot.import_points("backup.json")

Relative Moves

robot.move_relative([0, 0.01, 0, 0, 0, 0])  # 1cm along Y
robot.move_relative([0, 0, 0.05, 0, 0, 0], frame=MoveFrame.TOOL)  # 5cm along tool Z

Sequences with Blending

robot.move_sequence(["a", "b", "c"])
robot.move_sequence(["a", "b", "c"], blend_radius=0.02)

Contact Detection

# Zeros FT sensor automatically, then moves until force exceeds threshold
robot.move_until_contact([0, 0, -0.02, 0, 0, 0])

# Custom threshold (default: 5.0 N/Nm)
robot.move_until_contact([0, 0, -0.02, 0, 0, 0], threshold=10.0)

# Skip zeroing if you need absolute force values
robot.move_until_contact([0, 0, -0.02, 0, 0, 0], zero_first=False)

# Manual zero (e.g. before custom force-based logic)
robot.zero_ft_sensor()

Velocity Control

robot.move_velocity([0, 0, -0.02, 0, 0, 0], duration=1.0)

Freedrive Mode

from urkit import FreedriveMode

robot.enable_freedrive()              # all 6 axes free
robot.enable_freedrive(FreedriveMode.XYZ)      # linear axes + Rz rotation
robot.enable_freedrive(FreedriveMode.ROTATION) # rotation only
robot.disable_freedrive()             # disable before sending motion commands
robot.is_freedrive_active             # check state

Speed Control

robot.stop()                          # stop current move immediately (stopL + stopJ)
robot.speed_stop()                    # stop velocity-controlled motion (not E-stop)
robot.set_speed_slider(0.5)           # 50% velocity cap
robot.get_speed_slider()              # read current slider (0.0-1.0)

The speed slider controls the pendant's speed multiplier. It's global, persistent, and affects all motion commands.

Inverse Kinematics

joints = robot.inverse_kinematics([0.5, 0, 0.3, 0, 0, 0])

Telemetry

pose = robot.get_tcp_pose()           # [x, y, z, rx, ry, rz]
joints = robot.get_joint_positions()  # [j0..j5]
force = robot.get_tcp_force()         # [fx, fy, fz, mx, my, mz]
mode = robot.get_robot_mode()         # "REMOTE_CONTROL", "SERVOING", etc.
payload = robot.get_payload()         # kg
robot.is_moving()                     # bool — any joint/TCP velocity non-zero
robot.is_protective_stopped()         # bool
robot.is_emergency_stopped()          # bool
robot.current_point()                 # {"pose": [...], "joints": [...]}
robot.is_at_pose(target)              # bool — TCP within 1mm / 0.5°
robot.is_at_joints(target)            # bool — all joints within 0.001 rad

is_moving() is the simplest way to poll — is_at_pose() / is_at_joints() for precise target checking:

robot.move_to("pick", asynchronous=True)

# Simple: wait until robot stops
while robot.is_moving():
    time.sleep(0.01)

# Precise: check against specific target
while not robot.is_at_pose(target_pose):
    time.sleep(0.01)

Digital I/O

robot.set_digital_output(0, True)
robot.set_digital_outputs({0: True, 1: False, 8: True})
robot.set_digital_outputs(False)      # clear all

robot.get_digital_input(0)
robot.get_analog_input(0)
robot.get_tool_input(0)

robot.wait_for_input(0, True, timeout=10.0)  # block until pin 0 goes high

Configuration

URKit uses a YAML config file (config.yaml) to persist settings between sessions.

Location

URKit searches for config.yaml in this order:

  1. Explicit path via --config flag or load_config("path")
  2. Project root (where src/urkit lives)
  3. Current working directory

Keys

Key Description Example
robot_ip Robot IP address 192.168.1.50
points_path Path to SQLite points database points.db
gripper Gripper preset name hand-e, 2f-85, 2f-140, digital
default_vel Default linear velocity (m/s) 0.5
default_acc Default linear acceleration (m/s²) 0.3
expert_mode Disable safety speed clamping false

Gripper Config

gripper: digital
gripper_config:
  pin: 3
  close_on_high: true
gripper: hand-e
gripper_config:
  force: 50
  speed: 80

CLI Override Precedence

  1. CLI flags. urkit teach 192.168.1.50 --gripper none
  2. Config file. Values from config.yaml
  3. Built-in defaults. points.db, no gripper, 0.5 m/s velocity

Saving Config

The CLI never modifies your config file automatically. Press Y inside the teach pendant to save. This way you only save settings you've actually tested.

urkit teach 192.168.1.50 --gripper hand-e  # test, then press Y
urkit teach                               # next time: reads from config

Multiple workcells:

urkit teach --config station_a.yaml   # press Y to save
urkit teach --config station_b.yaml   # separate config

Programmatic

from urkit import load_config, resolve_config

config = load_config()                          # auto-resolve
config = load_config("/path/to/my.yaml")        # explicit path
path = resolve_config()                         # returns Path or None
robot = URRobot.from_config({"robot_ip": "192.168.1.50", "gripper": "2f-85"})

Advanced

Raw RTDE Access

URKit doesn't try to wrap everything. Access the raw ur_rtde interfaces for advanced features:

robot.rtde_control.forceMode(...)
robot.rtde_control.servoJ(...)
robot.rtde_receive.getActualCurrent()

Full ur_rtde documentation: https://sdurobotics.gitlab.io/ur_rtde/

Connection Monitoring

robot.connection_lost       # bool: check if RTDE dropped
robot.reconnect_rtde()      # reconnect after a drop

disconnect() is called automatically when the robot object is garbage collected.

Error Handling

from urkit import URKitError, MotionError, PointError

try:
    robot = URRobot(ip="192.168.1.50", points="points.db")
except RobotNotInRemoteModeError:
    print("Enable remote control on the teach pendant!")
except RtdeRegisterConflictError:
    print("Disable EtherNet/IP, PROFINET, or MODBUS!")
except URKitError as e:
    print(f"Error: {e}")

Common runtime errors:

Exception When
MotionError Unreachable pose, bad TCP offset, freedrive failure
PointError Point not found, no points database configured
GripperError Gripper activation or communication failure
URKitIOError Invalid pin number, I/O read/write failure
TelemetryError Cannot read pose, joints, force, etc.

When the robot enters protective stop or the RTDE connection drops, motion commands raise URKitConnectionError and the program should exit. The CLI handles this automatically.

Connection Notes

The URRobot constructor takes a few seconds on first call: it validates the connection, checks remote mode, powers on the robot, releases brakes, and connects RTDE. Subsequent calls are faster if the robot is already running.


Changelog

See CHANGELOG.md for the full version history.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

urkit-0.3.7.tar.gz (91.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

urkit-0.3.7-py3-none-any.whl (83.1 kB view details)

Uploaded Python 3

File details

Details for the file urkit-0.3.7.tar.gz.

File metadata

  • Download URL: urkit-0.3.7.tar.gz
  • Upload date:
  • Size: 91.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for urkit-0.3.7.tar.gz
Algorithm Hash digest
SHA256 725ed186d902273392045808ae463f4bb54e12f785b21fa69599c9afe19bb2af
MD5 e426a4ec51fb1dbf43e457419f6efbec
BLAKE2b-256 92c368aeb25963bd13e6ed5a1fea36c11edebb34c54f0e2998b1fc53c831c09c

See more details on using hashes here.

Provenance

The following attestation bundles were made for urkit-0.3.7.tar.gz:

Publisher: publish.yml on rodolfo-verde/urkit

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file urkit-0.3.7-py3-none-any.whl.

File metadata

  • Download URL: urkit-0.3.7-py3-none-any.whl
  • Upload date:
  • Size: 83.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for urkit-0.3.7-py3-none-any.whl
Algorithm Hash digest
SHA256 f5388cb425615bf959f5c7363a0809e6f14df5ceea12397ffaa49caa952fc5a2
MD5 6d90fdcd56697509c2272c6627b7351f
BLAKE2b-256 a8236c731673858621c05509ed58e6c196254057cda66419b5f4941bb2313019

See more details on using hashes here.

Provenance

The following attestation bundles were made for urkit-0.3.7-py3-none-any.whl:

Publisher: publish.yml on rodolfo-verde/urkit

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page