The Python package talks to the same ABB controllers as the .NET library, with the same features. It is the .NET assembly loaded in your Python process through [pythonnet](https://pythonnet.github.io), so nothing is installed on the robot and no other dependency is needed.

- Python : 3.7 and later
- Operating system : Windows, Linux, macOS
- Controllers : IRC5 with RobotWare 6, and OmniCore with RobotWare 7

## Install from PyPI

```bash
pip install UnderAutomation.ABB
```

`pythonnet` is installed with it. See on PyPI : [https://pypi.org/project/UnderAutomation.ABB](https://pypi.org/project/UnderAutomation.ABB)

We recommend a virtual environment, to keep the dependencies of your project apart :

```bash
python -m venv venv

# Windows
venv\Scripts\activate

# Linux and macOS
source venv/bin/activate
```

On **Linux**, install the .NET runtime as well and tell pythonnet to use it :

```bash
sudo apt-get install -y dotnet-runtime-8.0
```

## Install from source

```bash
git clone https://github.com/underautomation/ABB.py.git
cd ABB.py
pip install -e .
```

The repository also holds runnable examples, one folder per feature : `examples/controller`, `examples/io`, `examples/rapid`, `examples/motion`, and so on.

## First program

Import `AbbController`, connect, and call a service.

**Python : GetStartedPython**
```python
from underautomation.abb.abb_controller import AbbController

##
# The whole SDK is reachable from a single object
robot = AbbController()
robot.connect("192.168.0.1")

# Controller identity
identity = robot.rws.controller.get_identity()
print(f"Connected to {identity.name}")

# RAPID tasks
for task in robot.rws.rapid.get_tasks():
    print(f"{task.name} : {task.execution_state}")

robot.disconnect()
##
```

The default parameters target an OmniCore controller. For an IRC5 running RobotWare 6, set the RWS version. See [Connect to your robot](/abb/documentation/connect) for the full list of connection parameters.

## How the names are written

The Python package follows the .NET API, with Python naming :

| .NET                                          | Python                                            |
| --------------------------------------------- | ------------------------------------------------- |
| `robot.Rws.Controller.GetIdentity()`          | `robot.rws.controller.get_identity()`             |
| `robot.Rws.MotionSystem.GetRobTarget("ROB_1")` | `robot.rws.motion_system.get_rob_target("ROB_1")` |
| `identity.MacAddress`                         | `identity.mac_address`                            |
| `ControllerState.MotorsOn`                    | `ControllerState.MotorsOn`                        |

Classes, methods and properties become snake_case. Enumeration values keep the name they have in .NET. A value whose name is a Python keyword gets a trailing underscore : `RapidRegainMode.Continue_`, `RapidStartCondition.None_`, `RapidTextQueryMode.Try_`.

Each type lives in its own module, named after itself :

```py
from underautomation.abb.abb_controller import AbbController
from underautomation.abb.connection_parameters import ConnectionParameters
from underautomation.abb.rws.rws_version import RwsVersion
from underautomation.abb.rws.data.controller_state import ControllerState
from underautomation.abb.common.pose import Pose
```

## Errors

Every failure reported by the controller raises an `RwsException`. It comes from the .NET runtime, so its members keep their original names : `StatusCode`, `RwsErrorCode`, `RwsErrorMessage`, `ResponseBody`.

**C# : RwsErrorHandling**
```csharp
using UnderAutomation.ABB;
using UnderAutomation.ABB.Rws;

public class RwsErrorHandling
{
    static void Main()
    {
        AbbController robot = new AbbController();
        robot.Connect("192.168.0.1");

        /**/
        try
        {
            robot.Rws.Io.SetSignalValue("Local", "PANEL", "DO_Gripper", 1);
        }
        catch (RwsException ex)
        {
            // StatusCode is the HTTP status code the controller answered
            if (ex.StatusCode == 403)
                Console.WriteLine("Mastership is held elsewhere, or the user account lacks the grant");
            else if (ex.StatusCode == 404)
                Console.WriteLine("This signal does not exist on this controller");
            else
                Console.WriteLine($"RWS error {ex.StatusCode} : {ex.RwsErrorMessage}");
        }
        /**/

        robot.Disconnect();
    }
}
```

**Python : RwsErrorHandling**
```python
from underautomation.abb.abb_controller import AbbController
from UnderAutomation.ABB.Rws import RwsException

robot = AbbController()
robot.connect("192.168.0.1")

##
try:
    robot.rws.io.set_signal_value("Local", "PANEL", "DO_Gripper", 1)
except RwsException as ex:
    # The exception comes from the .NET runtime, so its members keep their original names.
    # StatusCode is the HTTP status code the controller answered
    if ex.StatusCode == 403:
        print("Mastership is held elsewhere, or the user account lacks the grant")
    elif ex.StatusCode == 404:
        print("This signal does not exist on this controller")
    else:
        print(f"RWS error {ex.StatusCode} : {ex.RwsErrorMessage}")
##

robot.disconnect()
```

## Differences with the .NET API

- The asynchronous methods are not wrapped. Every service method is available in its synchronous form.
- The file service reads and writes bytes, not text or streams. Decode and encode in your own code : `bytes(robot.rws.file.get_file_as_bytes(path)).decode("utf-8")`.
- Python has no method overloading, so a .NET method that exists in several forms is wrapped once. The mastership is an example : it is always taken with the domain it applies to, `robot.rws.mastership.request(MastershipDomain.Rapid)`. To hold everything, as the parameterless .NET call does, take the domains one by one :

```py
for domain in robot.rws.mastership.get_domains():
    robot.rws.mastership.request(domain)
```

## HTTPS and the controller certificate

An OmniCore controller answers on HTTPS with a certificate it signed itself. On Windows the SDK runs on the .NET Framework runtime, which refuses that certificate and offers an old TLS version. Relax both once, before connecting :

```py
from System.Net import ServicePointManager, SecurityProtocolType
from System.Net.Security import RemoteCertificateValidationCallback

ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12
ServicePointManager.ServerCertificateValidationCallback = \
    RemoteCertificateValidationCallback(lambda sender, certificate, chain, errors: True)
```

## What to read next

- [Connect to your robot](/abb/documentation/connect) : connection parameters, IRC5 and OmniCore, errors.
- [Test with a RobotStudio virtual controller](/abb/documentation/virtual-controller) : run everything without a real robot.
- [Robot Web Services overview](/abb/documentation/rws) : the list of services and what each one covers.
- [Licensing](/abb/documentation/license) : the 30 day trial and how to register your key.