Get started with Python
Install the Universal Robots SDK from PyPI and write a first Python program. Python 3.7 to 3.13 on Windows, Linux and macOS, with pythonnet.
This page shows how to install the Universal Robots SDK for Python and write a first program for a UR cobot. The Python package has the same functions as the .NET library, with Python names.
How it works
The package UnderAutomation.UniversalRobots contains the .NET library UnderAutomation.UniversalRobots.dll and a Python layer that calls it through pythonnet. pythonnet runs .NET code in the Python process. Nothing is installed on the robot.
| Item | Supported |
|---|---|
| Python | 3.7 to 3.13 |
| pythonnet | 3.0.5, installed with the package |
| Operating system | Windows, Linux, macOS |
| Robots | CB-Series, e-Series, UR8 Long, UR15, UR18, UR20, UR30, with PolyScope or PolyScope X, and URSim |
- Windows: the DLL runs on the .NET Framework 4.x of Windows. Nothing else to install.
- Linux and macOS: install the .NET runtime (for example .NET 8), then select it for pythonnet before you start Python. Without this, pythonnet uses Mono, its default runtime on Linux and macOS.
sudo apt-get install -y dotnet-runtime-8.0export PYTHONNET_RUNTIME=coreclr
Always write the export: a variable set without it does not reach the Python process. The same choice can be made in code, before the first import of the package:
# Linux and macOS: use the .NET runtime instead of Mono.# Same effect as "export PYTHONNET_RUNTIME=coreclr", before the first import of the SDKfrom pythonnet import loadload("coreclr")from underautomation.universal_robots.ur import UR
Install the package
From PyPI
Install the package in a virtual environment:
python -m venv .venv# Windows.venv\Scripts\activate# Linux and macOSsource .venv/bin/activatepip install UnderAutomation.UniversalRobots
Package page: pypi.org/project/UnderAutomation.UniversalRobots
From the sources
git clone https://github.com/underautomation/UniversalRobots.py.gitcd UniversalRobots.pypip install -e .
The repository also holds runnable examples, one folder per interface: examples/dashboard, examples/primary_interface, examples/rtde, examples/rest, examples/sftp, examples/kinematics and examples/license. Run python examples/launcher.py to choose one from a menu. The first run asks the address of the robot and saves it in examples/robot_config.json.
Use the SDK with an AI agent
An AI agent such as Claude Code, Codex, GitHub Copilot or Cursor can write your Python code with the SDK. Without help, it does not know which protocol to use, which controller option is needed, or the exact names of the API, so it can invent members that do not exist. The UnderAutomation skill gives it the documentation of this version of the SDK: protocols, controller settings, API in Python, errors and safety rules for robot motion.
With the skill installed, you can ask for example:
Install the skill in your project:
npx skills add underautomation/skills --skill underautomation-universal-robots-python
With Claude Code, you can also install it as a plugin:
claude plugin marketplace add underautomation/skillsclaude plugin install universal-robots-python@underautomation
Or paste this prompt to your agent: "Install the UnderAutomation Universal Robots skill: follow https://underautomation.com/universal-robots/documentation/ai-skills.md". The other installation modes are in AI agent skills.
First program
Import UR, connect and read a value.
import timefrom underautomation.universal_robots.ur import UR# Only after the 30 day trial: register your license key# UR.register_license("YourCompanyName", "YOUR_LICENSE_KEY")robot = UR()# Connect with the default services: Primary Interface and Dashboard Serverrobot.connect("192.168.0.1")# The Primary Interface receives the state of the robot at 10 Hztime.sleep(0.5)# Cartesian position of the tool, in meters and radianspose = robot.primary_interface.cartesian_info.as_pose()print(f"X={pose.x} Y={pose.y} Z={pose.z}")# Mode of the robot, read with the Dashboard Servermode = robot.dashboard.get_robot_mode()print(f"Robot mode: {mode.value}")robot.disconnect()
The SDK runs for 30 days without a key. After that, register_license is needed: see Licensing.
How the names are written
The Python package follows the .NET API, with Python names:
| .NET | Python |
|---|---|
robot.PrimaryInterface.JointData | robot.primary_interface.joint_data |
robot.Dashboard.GetProgramState() | robot.dashboard.get_program_state() |
parameters.Rtde.OutputSetup.Add(...) | parameters.rtde.output_setup.add(...) |
UR.RegisterLicense(...) | UR.register_license(...) |
pose.X, pose.Rx | pose.x, pose.rx |
values.InputIntRegisters.X24 | values.input_int_registers.x24 |
RtdeOutputData.ActualTcpPose | RtdeOutputData.ActualTcpPose |
SingularityType.None | SingularityType.None_ |
robot.Rtde.OutputDataReceived += handler | robot.rtde.output_data_received(handler) |
A callback, like Action<ulong> of UploadFile | A Python function: upload_file(local, remote, lambda sent: print(sent)) |
double[], double[][] returned | a .NET array: index it, iterate it, or copy it with list(...) |
Methods and properties become snake_case. Enumerations are Python IntEnum classes that keep the .NET value names, except the Python keywords (None_). Each type is in a module named after it, in the folder of its .NET namespace. A method that takes a .NET array accepts a Python list:
# Main class and connection settingsfrom underautomation.universal_robots.ur import URfrom underautomation.universal_robots.connect_parameters import ConnectParameters# One module per type, in the folder of its .NET namespacefrom underautomation.universal_robots.common.pose import Posefrom underautomation.universal_robots.rtde.rtde_input_data import RtdeInputDatafrom underautomation.universal_robots.rtde.rtde_output_data import RtdeOutputDatafrom underautomation.universal_robots.rtde.rtde_input_values import RtdeInputValuesfrom underautomation.universal_robots.kinematics.kinematics_utils import KinematicsUtilsfrom underautomation.universal_robots.files.ur_program import URProgram
Events
An event of the .NET API is a method in Python: call it with your function. The function receives two arguments, the sender and the event arguments. Read the new values in the properties of the client, or wrap the arguments in their Python class:
static void Main(string[] args){var robot = new UR();var parameters = new ConnectParameters("192.168.0.1");// The socket server is disabled by defaultparameters.SocketCommunication.Enable = true;parameters.SocketCommunication.Port = 50001;robot.Connect(parameters);// URScript: socket_open("192.168.0.10", 50001)robot.SocketCommunication.SocketClientConnection += (sender, e) =>{e.Client.SocketWrite("Welcome");};// URScript: socket_send_string("Hello")robot.SocketCommunication.SocketRequest += (sender, e) =>{Console.WriteLine(e.Client.EndPoint.Address + " says " + e.Message);};// URScript: value := socket_get_var("COUNTER")robot.SocketCommunication.SocketGetVar += (sender, e) =>{if (e.Name == "COUNTER") e.Value = 12; // integer only};// URScript: socket_close()robot.SocketCommunication.SocketClientDisconnection += (sender, e) =>{Console.WriteLine(e.Client.EndPoint + " disconnected");};// Send a message to every connected robotrobot.SocketCommunication.SocketWrite("Start");// Or to one robotforeach (SocketClient client in robot.SocketCommunication.ConnectedClients)client.SocketWrite("Hello " + client.EndPoint.Address);}}
The function runs in a thread of the SDK, not in the main thread of Python.
Errors
An error raised by the SDK comes from the .NET runtime. Import the exception class from its .NET namespace, after the import of the package. Its members keep their .NET names: Message, Service, LicenseInfo.
{var robot = new UR();try{robot.Connect("192.168.0.1");}catch (InvalidLicenseException ex){// The trial is over or the license key is not validConsole.WriteLine(ex.LicenseInfo);}catch (ConnectException ex){// One service did not connect: the other services are closedConsole.WriteLine($"{ex.Service} of {ex.RobotIp}: {ex.Message}");}catch (Exception ex){// For example, the robot does not answer the pingConsole.WriteLine(ex.Message);}// Errors that happen after the connection, in a background threadrobot.InternalErrorOccured += (sender, e) =>{Console.WriteLine($"{e.Status}: {e.Message}");};}}
Limits of the Python package
- A .NET method with an
outparameter, likePose.TryParseandPose.FromRotationVectorToQuaternion, is not usable from Python. GlobalVariable.ToMatrixhas no Python twin.- The SDK has no asynchronous methods.
What to read next
- Connect to the robot: the settings of the robot, the connection parameters, the errors.
- RTDE and Primary Interface: every code sample has a Python tab.
- Licensing: the 30 day trial and the license key.
- AI agent skills: install the Python skill of the SDK in Claude Code, Codex, GitHub Copilot or Cursor.