This page shows how to use the Staubli SDK from Matlab, to read and control a Staubli CS8 or CS9 controller from a Matlab script. Matlab loads the .NET DLL of the SDK with its .NET interface: nothing else is installed, on the PC or on the controller.

## Requirements

| Item            | Supported                                                                                                        |
| --------------- | ---------------------------------------------------------------------------------------------------------------- |
| Windows         | Matlab with the .NET Framework 4.6.2 or later (the default on Windows 10 and 11). Load the `net48` DLL           |
| Linux and macOS | Matlab R2024b or later with the .NET runtime, selected by `dotnetenv("core")`. Not tested by UnderAutomation yet |
| Controllers     | CS8 and CS9, and the emulator of Staubli Robotics Suite                                                          |

Matlab documents its .NET interface in [Call .NET from MATLAB](https://www.mathworks.com/help/matlab/using-net-libraries-in-matlab.html).

## Load the SDK

1. Download [UnderAutomation.Staubli.zip](https://github.com/underautomation/Staubli.NET/releases/latest/download/UnderAutomation.Staubli.zip) from the latest release of [Staubli.NET](https://github.com/underautomation/Staubli.NET/releases/latest).
2. On Windows, unblock the zip before you extract it: right click, `Properties`, check `Unblock`.
3. Copy its `net48` folder next to your script.
4. Load the DLL with `NET.addAssembly`, then import the namespaces you use.

**Matlab : MatlabLoadAssembly**
```matlab
%%
% The net48 folder of UnderAutomation.Staubli.zip, copied next to this script
dll = fullfile(pwd, 'net48', 'UnderAutomation.Staubli.dll');

if ~NET.isNETSupported
    error('No supported .NET Framework found on this computer');
end

% Load the SDK once per Matlab session
NET.addAssembly(dll);

% Import the namespaces you use
import UnderAutomation.Staubli.*
import UnderAutomation.Staubli.Soap.Data.*
%%
```

`NET.addAssembly` needs an absolute path: `fullfile(pwd, ...)` builds one that works on every operating system. Matlab cannot unload a .NET assembly: restart Matlab to load a new version of the DLL.

On Linux and macOS, Matlab R2024b and later can use the .NET runtime instead of the .NET Framework. Set the environment variable `DOTNET_ROOT` to the folder of the .NET runtime, call `dotnetenv("core")` before `NET.addAssembly`, and load the DLL of the `net8.0` folder. See [dotnetenv](https://www.mathworks.com/help/matlab/ref/dotnetenv.html) in the Matlab documentation.

## First program

**C# : ConnectQuick**
```csharp
using UnderAutomation.Staubli;

public class ConnectQuick
{
    static void Main()
    {
        /**/
        var controller = new StaubliController();

        // Default parameters: SOAP port 851, user "default", password "default"
        controller.Connect("192.168.0.254");

        bool connected = controller.Enabled;
        /**/

        controller.Disconnect();
    }
}
```

**Python : ConnectQuick**
```python
from underautomation.staubli.staubli_controller import StaubliController

##
controller = StaubliController()

# Default parameters: SOAP port 851, user "default", password "default"
controller.connect("192.168.0.254")

connected = controller.enabled
##

controller.disconnect()
```

**Matlab : ConnectQuick**
```matlab
NET.addAssembly(fullfile(pwd, 'net48', 'UnderAutomation.Staubli.dll'));
import UnderAutomation.Staubli.*

%%
controller = StaubliController();

% Default parameters: SOAP port 851, user "default", password "default"
controller.Connect('192.168.0.254');

connected = controller.Enabled;
%%

controller.Disconnect();
```

The SDK runs for 30 days without a key. After that, register your license once per Matlab session:

**C# : License**
```csharp
using UnderAutomation.Staubli;
using UnderAutomation.Staubli.License;

public class License
{
    static void Main()
    {
        /**/
        // Register the license once, before the first connection
        StaubliController.RegisterLicense("YourCompanyName", "YOUR_LICENSE_KEY");

        LicenseInfo info = StaubliController.LicenseInfo;

        // Number of trial days remaining
        int? evaluationDaysLeft = info.EvaluationDaysLeft;

        bool licenseValid = info.State == LicenseState.Licensed;

        // A readable description of the current state
        Console.WriteLine(info);

        // Check the license once at startup, rather than catching
        // the exception on every connection
        if (!info.IsLicensed)
        {
            Console.WriteLine(info);
            return;
        }
        /**/

        try
        {
            var controller = new StaubliController();
            controller.Connect("192.168.0.254");
        }
        catch (InvalidLicenseException ex)
        {
            Console.WriteLine(ex.Message);
            Console.WriteLine(ex.LicenseInfo.State);
        }
    }
}
```

**Python : License**
```python
from underautomation.staubli.staubli_controller import StaubliController
from underautomation.staubli.license.license_state import LicenseState
from UnderAutomation.Staubli.License import InvalidLicenseException

##
# Register the license once, before the first connection
StaubliController.register_license("YourCompanyName", "YOUR_LICENSE_KEY")

info = StaubliController.license_info

# Number of trial days remaining
evaluation_days_left = info.evaluation_days_left

license_valid = info.state == LicenseState.Licensed

# A readable description of the current state
print(info)

# Check the license once at startup, rather than catching
# the exception on every connection
if not info.is_licensed:
    print(info)
    raise SystemExit(0)
##

try:
    controller = StaubliController()
    controller.connect("192.168.0.254")
except InvalidLicenseException as ex:
    # The exception comes from the .NET runtime, so its members keep their original names
    print(ex.Message)
    print(ex.LicenseInfo.State)
```

**Matlab : License**
```matlab
NET.addAssembly(fullfile(pwd, 'net48', 'UnderAutomation.Staubli.dll'));
import UnderAutomation.Staubli.*

%%
% Register the license once, before the first connection
StaubliController.RegisterLicense('YourCompanyName', 'YOUR_LICENSE_KEY');

info = StaubliController.LicenseInfo;

% A readable description of the current state
disp(char(info.ToString()));

% Check the license once at startup
if ~info.IsLicensed
    error(char(info.ToString()));
end
%%
```

## From .NET to Matlab

The names of the classes, methods and properties are the .NET names of the other pages. The C# samples of the documentation translate line by line, with these rules:

| .NET                                    | Matlab                                                          |
| --------------------------------------- | --------------------------------------------------------------- |
| `new StaubliController()`               | `StaubliController()`, after `import UnderAutomation.Staubli.*` |
| `"192.168.0.254"`                       | `'192.168.0.254'`, a char vector                                |
| `GetCurrentJointPosition(robot: 0)`     | `GetCurrentJointPosition(0)`: no named arguments                |
| An optional argument, like `tool`       | Pass every argument. `[]` passes `null`                         |
| `double[]` returned                     | A .NET array: `double(joints)` converts it to a Matlab vector   |
| `double[]` expected                     | A Matlab vector, for example `[0, 0, pi/2, 0, pi/2, 0]`         |
| `string[]` expected                     | `NET.createArray('System.String', n)`, then fill it             |
| `robots[0]`                             | `robots(1)`: Matlab indexes .NET arrays from 1                  |
| `robots.Length`                         | `robots.Length`                                                 |
| An enumeration value, like `task.State` | `char(task.State)` gives its name                               |

## Examples

### Read the position

**C# : PositionRead**
```csharp
using UnderAutomation.Staubli;
using UnderAutomation.Staubli.Soap.Data;

public class PositionRead
{
    static void Main()
    {
        var controller = new StaubliController();
        controller.Connect("192.168.0.254");

        /**/
        // Joints and flange position of the first robot, in one request
        CartesianJointPosition position = controller.Soap.GetCurrentCartesianJointPosition(robot: 0);

        // Joint values, in radians
        double[] joints = position.JointsPosition;

        // Flange position in the world frame: X, Y, Z in meters, Rx, Ry, Rz in radians
        CartesianPosition flange = position.CartesianPosition;
        Console.WriteLine($"X={flange.X} Y={flange.Y} Z={flange.Z}");
        Console.WriteLine($"Rx={flange.Rx} Ry={flange.Ry} Rz={flange.Rz}");
        /**/

        controller.Disconnect();
    }
}
```

**Python : PositionRead**
```python
from underautomation.staubli.staubli_controller import StaubliController

controller = StaubliController()
controller.connect("192.168.0.254")

##
# Joints and flange position of the first robot, in one request
position = controller.soap.get_current_cartesian_joint_position(0)

# Joint values, in radians
joints = list(position.joints_position)

# Flange position in the world frame: X, Y, Z in meters, Rx, Ry, Rz in radians
flange = position.cartesian_position
print(f"X={flange.x} Y={flange.y} Z={flange.z}")
print(f"Rx={flange.rx} Ry={flange.ry} Rz={flange.rz}")
##

controller.disconnect()
```

**Matlab : PositionRead**
```matlab
NET.addAssembly(fullfile(pwd, 'net48', 'UnderAutomation.Staubli.dll'));
import UnderAutomation.Staubli.*

controller = StaubliController();
controller.Connect('192.168.0.254');

%%
% Joints and flange position of the first robot, in one request.
% Pass [] for the optional tool and frame.
position = controller.Soap.GetCurrentCartesianJointPosition(0, [], []);

% Joint values in radians, as a Matlab row vector
joints = double(position.JointsPosition);

% Flange position in the world frame: X, Y, Z in meters, Rx, Ry, Rz in radians
flange = position.CartesianPosition;
fprintf('X=%.4f Y=%.4f Z=%.4f\n', flange.X, flange.Y, flange.Z);
fprintf('Rx=%.4f Ry=%.4f Rz=%.4f\n', flange.Rx, flange.Ry, flange.Rz);
%%

controller.Disconnect();
```

### Forward kinematics

**C# : KinematicsForward**
```csharp
using UnderAutomation.Staubli;
using UnderAutomation.Staubli.Soap.Data;

public class KinematicsForward
{
    static void Main()
    {
        var controller = new StaubliController();
        controller.Connect("192.168.0.254");

        /**/
        // Joint values in radians, here the current ones
        double[] joints = controller.Soap.GetCurrentJointPosition(robot: 0);

        // The controller computes the flange frame for these joints
        IForwardKinematics fk = controller.Soap.ForwardKinematics(robot: 0, joints);

        // Position of the frame origin, and its orientation as a rotation matrix
        Frame frame = fk.Position;
        Console.WriteLine($"P = [{frame.Px}, {frame.Py}, {frame.Pz}]");

        // Configuration of the arm for this position (shoulder, elbow, wrist)
        Config config = fk.Config;
        Console.WriteLine(config);
        /**/

        controller.Disconnect();
    }
}
```

**Python : KinematicsForward**
```python
from underautomation.staubli.staubli_controller import StaubliController

controller = StaubliController()
controller.connect("192.168.0.254")

##
# Joint values in radians, here the current ones
joints = controller.soap.get_current_joint_position(0)

# The controller computes the flange frame for these joints
fk = controller.soap.forward_kinematics(0, joints)

# Position of the frame origin, and its orientation as a rotation matrix
frame = fk.position
print(f"P = [{frame.px}, {frame.py}, {frame.pz}]")

# Configuration of the arm for this position (shoulder, elbow, wrist)
config = fk.config
print(config)
##

controller.disconnect()
```

**Matlab : KinematicsForward**
```matlab
NET.addAssembly(fullfile(pwd, 'net48', 'UnderAutomation.Staubli.dll'));
import UnderAutomation.Staubli.*

controller = StaubliController();
controller.Connect('192.168.0.254');

%%
% Joint values in radians. A Matlab vector is passed as a .NET array.
joints = [0, 0, pi/2, 0, pi/2, 0];

% The controller computes the flange frame for these joints
fk = controller.Soap.ForwardKinematics(0, joints);

% Position of the frame origin
frame = fk.Position;
fprintf('P = [%.4f, %.4f, %.4f]\n', frame.Px, frame.Py, frame.Pz);

% Rotation matrix of the frame, columns N, O, A
R = [frame.Nx frame.Ox frame.Ax; frame.Ny frame.Oy frame.Ay; frame.Nz frame.Oz frame.Az];
%%

controller.Disconnect();
```

### List, read and write I/O

**C# : IoList**
```csharp
using UnderAutomation.Staubli;
using UnderAutomation.Staubli.Soap.Data;

public class IoList
{
    static void Main()
    {
        var controller = new StaubliController();
        controller.Connect("192.168.0.254");

        /**/
        // Every physical I/O of the controller
        PhysicalIo[] ios = controller.Soap.GetAllPhysicalIos();

        foreach (PhysicalIo io in ios)
        {
            // TypeStr: din, dout, ain, serial...
            Console.WriteLine($"{io.Name} [{io.TypeStr}] {io.Description} lockable={io.Lockable}");
        }
        /**/

        controller.Disconnect();
    }
}
```

**Python : IoList**
```python
from underautomation.staubli.staubli_controller import StaubliController

controller = StaubliController()
controller.connect("192.168.0.254")

##
# Every physical I/O of the controller
ios = controller.soap.get_all_physical_ios()

for io in ios:
    # type_str: din, dout, ain, serial...
    print(f"{io.name} [{io.type_str}] {io.description} lockable={io.lockable}")
##

controller.disconnect()
```

**Matlab : IoList**
```matlab
NET.addAssembly(fullfile(pwd, 'net48', 'UnderAutomation.Staubli.dll'));
import UnderAutomation.Staubli.*

controller = StaubliController();
controller.Connect('192.168.0.254');

%%
% Every physical I/O of the controller
ios = controller.Soap.GetAllPhysicalIos();

% .NET arrays are indexed from 1 in Matlab
for i = 1:ios.Length
    io = ios(i);
    fprintf('%s [%s] %s\n', char(io.Name), char(io.TypeStr), char(io.Description));
end
%%

controller.Disconnect();
```

**C# : IoRead**
```csharp
using UnderAutomation.Staubli;
using UnderAutomation.Staubli.Soap.Data;

public class IoRead
{
    static void Main()
    {
        var controller = new StaubliController();
        controller.Connect("192.168.0.254");

        /**/
        // Names as returned by GetAllPhysicalIos
        string[] names = { @"BasicIO-1\%I0", @"BasicIO-1\%Q0" };

        // One state per name, in the same order
        PhysicalIoState[] states = controller.Soap.ReadIos(names);

        for (int i = 0; i < names.Length; i++)
        {
            PhysicalIoState state = states[i];

            // State tells if the name exists on this controller
            if (state.State != PhysicalIoEnumState.Defined)
            {
                Console.WriteLine($"{names[i]}: {state.State}");
                continue;
            }

            Console.WriteLine($"{names[i]} = {state.Value} locked={state.Locked} simulated={state.Simulated}");
        }
        /**/

        controller.Disconnect();
    }
}
```

**Python : IoRead**
```python
from underautomation.staubli.staubli_controller import StaubliController
from underautomation.staubli.soap.data.physical_io_enum_state import PhysicalIoEnumState

controller = StaubliController()
controller.connect("192.168.0.254")

##
# Names as returned by get_all_physical_ios
names = [r"BasicIO-1\%I0", r"BasicIO-1\%Q0"]

# One state per name, in the same order
states = controller.soap.read_ios(names)

for name, state in zip(names, states):
    # state tells if the name exists on this controller
    if state.state != PhysicalIoEnumState.Defined:
        print(f"{name}: {state.state.name}")
        continue

    print(f"{name} = {state.value} locked={state.locked} simulated={state.simulated}")
##

controller.disconnect()
```

**Matlab : IoRead**
```matlab
NET.addAssembly(fullfile(pwd, 'net48', 'UnderAutomation.Staubli.dll'));
import UnderAutomation.Staubli.*

controller = StaubliController();
controller.Connect('192.168.0.254');

%%
% Names as returned by GetAllPhysicalIos, in a .NET string array
names = NET.createArray('System.String', 2);
names(1) = 'BasicIO-1\%I0';
names(2) = 'BasicIO-1\%Q0';

% One state per name, in the same order
states = controller.Soap.ReadIos(names);

for i = 1:states.Length
    fprintf('%s = %g\n', char(names(i)), states(i).Value);
end
%%

controller.Disconnect();
```

**C# : IoWrite**
```csharp
using UnderAutomation.Staubli;
using UnderAutomation.Staubli.Soap.Data;

public class IoWrite
{
    static void Main()
    {
        var controller = new StaubliController();
        controller.Connect("192.168.0.254");

        /**/
        string[] names = { @"BasicIO-1\%Q0", @"BasicIO-1\%Q1" };

        // One value per name. Digital outputs: 1 is on, 0 is off
        double[] values = { 1, 0 };

        PhysicalIoWriteResponse[] responses = controller.Soap.WriteIos(names, values);

        for (int i = 0; i < names.Length; i++)
        {
            if (!responses[i].Found)
                Console.WriteLine($"{names[i]}: unknown name");
            else if (!responses[i].Success)
                Console.WriteLine($"{names[i]}: write refused");
        }
        /**/

        controller.Disconnect();
    }
}
```

**Python : IoWrite**
```python
from underautomation.staubli.staubli_controller import StaubliController

controller = StaubliController()
controller.connect("192.168.0.254")

##
names = [r"BasicIO-1\%Q0", r"BasicIO-1\%Q1"]

# One value per name. Digital outputs: 1 is on, 0 is off
values = [1.0, 0.0]

responses = controller.soap.write_ios(names, values)

for name, response in zip(names, responses):
    if not response.found:
        print(f"{name}: unknown name")
    elif not response.success:
        print(f"{name}: write refused")
##

controller.disconnect()
```

**Matlab : IoWrite**
```matlab
NET.addAssembly(fullfile(pwd, 'net48', 'UnderAutomation.Staubli.dll'));
import UnderAutomation.Staubli.*

controller = StaubliController();
controller.Connect('192.168.0.254');

%%
names = NET.createArray('System.String', 2);
names(1) = 'BasicIO-1\%Q0';
names(2) = 'BasicIO-1\%Q1';

% One value per name. Digital outputs: 1 is on, 0 is off
values = [1, 0];

responses = controller.Soap.WriteIos(names, values);

for i = 1:responses.Length
    if ~responses(i).Found || ~responses(i).Success
        fprintf('%s: write refused\n', char(names(i)));
    end
end
%%

controller.Disconnect();
```

### VAL 3 tasks

**C# : TaskList**
```csharp
using UnderAutomation.Staubli;
using UnderAutomation.Staubli.Soap.Data;

public class TaskList
{
    static void Main()
    {
        var controller = new StaubliController();
        controller.Connect("192.168.0.254");

        /**/
        ControllerTask[] tasks = controller.Soap.GetTasks();

        foreach (ControllerTask task in tasks)
        {
            // State: Idle, Transition, Running, Stepping, Stopped
            Console.WriteLine($"{task.Name} ({task.State}), priority {task.Priority}, created by {task.CreatedBy}");

            // Line of the VAL 3 program that the task executes
            ProgramLine line = task.ProgramLine;
            if (line != null)
                Console.WriteLine($"  {line.ProgramName}:{line.LineNumber} {line.LineContent}");

            // Runtime error of the task, 0 when there is none
            if (task.RuntimeError != 0)
                Console.WriteLine($"  Error {task.RuntimeError}: {task.RuntimeErrorDescription}");
        }
        /**/

        controller.Disconnect();
    }
}
```

**Python : TaskList**
```python
from underautomation.staubli.staubli_controller import StaubliController

controller = StaubliController()
controller.connect("192.168.0.254")

##
tasks = controller.soap.get_tasks()

for task in tasks:
    # state: Idle, Transition, Running, Stepping, Stopped
    print(f"{task.name} ({task.state.name}), priority {task.priority}, created by {task.created_by}")

    # Line of the VAL 3 program that the task executes
    line = task.program_line
    if line is not None:
        print(f"  {line.program_name}:{line.line_number} {line.line_content}")

    # Runtime error of the task, 0 when there is none
    if task.runtime_error != 0:
        print(f"  Error {task.runtime_error}: {task.runtime_error_description}")
##

controller.disconnect()
```

**Matlab : TaskList**
```matlab
NET.addAssembly(fullfile(pwd, 'net48', 'UnderAutomation.Staubli.dll'));
import UnderAutomation.Staubli.*

controller = StaubliController();
controller.Connect('192.168.0.254');

%%
tasks = controller.Soap.GetTasks();

for i = 1:tasks.Length
    task = tasks(i);

    % char() gives the name of an enumeration value: Idle, Running, Stopped...
    fprintf('%s (%s), created by %s\n', char(task.Name), char(task.State), char(task.CreatedBy));

    % Runtime error of the task, 0 when there is none
    if task.RuntimeError ~= 0
        fprintf('  Error %d: %s\n', task.RuntimeError, char(task.RuntimeErrorDescription));
    end
end
%%

controller.Disconnect();
```

## Known limits

- Matlab has no named arguments: pass all the arguments of a .NET method, in order.
- An exception of the SDK is a `NET.NetException` in Matlab. Its `ExceptionObject` property holds the .NET exception, with its `Message`.
- Matlab keeps the DLL loaded until it closes.

See also the Matlab pages on the limits of [.NET arrays](https://www.mathworks.com/help/matlab/matlab_external/limitations-to-support-of-net-arrays.html), [.NET methods](https://www.mathworks.com/help/matlab/matlab_external/limitations-to-support-of-net-methods.html) and [.NET enumerations](https://www.mathworks.com/help/matlab/matlab_external/limitations-to-net-enumerations.html).

## What to read next

- [Connect to your robot](/staubli/documentation/connect): connection parameters and errors.
- [SOAP overview](/staubli/documentation/soap-overview): the functions of the SDK, one page per topic.
- [Licensing](/staubli/documentation/license): the 30 day trial and the license key.