Universal Robots e-Series control toolkit built on ur_rtde
Project description
URKit
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)
- Network:
☰→System→System→Network. 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:
172.31.1.42/ Subnet255.255.255.0 - PC:
172.31.1.1/ Netmask255.255.255.0 - Verify with
ping 172.31.1.42. Connect via direct Ethernet cable or a switch.
- Robot:
- Remote Control:
☰→System→Remote Control: Enable. Press the remote/local button on the pendant. - Security:
☰→Security→Services: 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 ☰ → Settings → System → URCaps.
from urkit import URRobot, ROBOTIQ_HAND_E # or ROBOTIQ_2F_85, ROBOTIQ_2F_140, or gripper=None
robot = URRobot(ip="172.31.1.42", 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:
- Teach points. Use the CLI to position the robot and save named waypoints.
- Write code. Create a robot, move to points by name, apply offsets, run sequences.
- 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 172.31.1.42 # 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 | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
|
| Gripper | Points | Controls | |||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
|
|||||||||||||||||||||||||||||||||||||||||
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 172.31.1.42 --expert
# config.yaml
expert_mode: true
API Reference
Connecting
from urkit import URRobot, ROBOTIQ_HAND_E
robot = URRobot(ip="172.31.1.42", points="points.db", gripper=ROBOTIQ_HAND_E)
With custom motion defaults:
robot = URRobot(
ip="172.31.1.42",
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(20) # 20mm open (Robotiq only, 0 = 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="172.31.1.42", 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="172.31.1.42",
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="172.31.1.42")
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
robot.move_until_contact([0, 0, -0.02, 0, 0, 0])
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:
- Explicit path via
--configflag orload_config("path") - Project root (where
src/urkitlives) - Current working directory
Keys
| Key | Description | Example |
|---|---|---|
robot_ip |
Robot IP address | 172.31.1.42 |
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
- CLI flags.
urkit teach 172.31.1.42 --gripper none - Config file. Values from
config.yaml - 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 172.31.1.42 --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": "172.31.1.42", "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="172.31.1.42", 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.
Recent Changes
- v3.2 — Gripper speed/force keys (6/7), force/speed display
- v3.1 — Async motion, Space cancel, progress bar, joint display
- v0.3.0 — Safe/expert mode, speed_stop on exit, safety features
- v0.2.1 —
get_pose()method - v0.2.0 — README restructure
- v0.1.1 —
--gripper none,--configflag, save config on demand - v0.1.0 — Initial release
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file urkit-0.3.2.tar.gz.
File metadata
- Download URL: urkit-0.3.2.tar.gz
- Upload date:
- Size: 89.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f7c4f681be63c59ef2d59437028c05d5a4ce8085e08174f4cff199ccbb95d5bc
|
|
| MD5 |
38ff2f541ef6e7ea8166b5d3b25dd461
|
|
| BLAKE2b-256 |
64f8ab7c820e5d1813ab4ae63adf78239d763497a799c52a4335ae12cde51b0d
|
Provenance
The following attestation bundles were made for urkit-0.3.2.tar.gz:
Publisher:
publish.yml on rodolfo-verde/urkit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
urkit-0.3.2.tar.gz -
Subject digest:
f7c4f681be63c59ef2d59437028c05d5a4ce8085e08174f4cff199ccbb95d5bc - Sigstore transparency entry: 2060164016
- Sigstore integration time:
-
Permalink:
rodolfo-verde/urkit@bb81e0fb4fa52fe083c64b6aa604d68cc736e414 -
Branch / Tag:
refs/tags/v0.3.1 - Owner: https://github.com/rodolfo-verde
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@bb81e0fb4fa52fe083c64b6aa604d68cc736e414 -
Trigger Event:
push
-
Statement type:
File details
Details for the file urkit-0.3.2-py3-none-any.whl.
File metadata
- Download URL: urkit-0.3.2-py3-none-any.whl
- Upload date:
- Size: 81.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b09bab51b5bccb7c7da0b51c881d8cb0b6c053b278a31fc54c6e7a68dcf1ca18
|
|
| MD5 |
a1625de49fb049fe49b79f0b1fc3bcb1
|
|
| BLAKE2b-256 |
e1c8b98ea9622ae927a9ab2b3e8afd9c43379753d920844b6066b7b801d961f7
|
Provenance
The following attestation bundles were made for urkit-0.3.2-py3-none-any.whl:
Publisher:
publish.yml on rodolfo-verde/urkit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
urkit-0.3.2-py3-none-any.whl -
Subject digest:
b09bab51b5bccb7c7da0b51c881d8cb0b6c053b278a31fc54c6e7a68dcf1ca18 - Sigstore transparency entry: 2060164240
- Sigstore integration time:
-
Permalink:
rodolfo-verde/urkit@bb81e0fb4fa52fe083c64b6aa604d68cc736e414 -
Branch / Tag:
refs/tags/v0.3.1 - Owner: https://github.com/rodolfo-verde
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@bb81e0fb4fa52fe083c64b6aa604d68cc736e414 -
Trigger Event:
push
-
Statement type: