`robot.Rws.MotionSystem` covers everything about how the robot stands and how it moves: the mechanical units of the system and their axes, where the tool currently is, jogging, the kinematics calculations, the collision supervision and the calibration.

This is the service that moves a real robot. Reading is always safe. Writing needs the [mastership](/abb/documentation/rws-mastership) of the `Motion` domain, and most of the time also a controller in manual mode with the motors on, which is read and changed through the [control panel](/abb/documentation/rws-panel).

## Geometry types

The positions of the SDK are built from a few small classes of the `UnderAutomation.ABB.Common` namespace. They are the same types everywhere, so a position read here can be written into a RAPID variable without conversion.

| Type                 | Holds                                                                                   |
| -------------------- | --------------------------------------------------------------------------------------- |
| `Position`           | `X`, `Y`, `Z`                                                                           |
| `Quaternion`         | `Q1` to `Q4`, an orientation as a unit quaternion                                       |
| `Pose`               | a `Position` plus an `Orientation`                                                      |
| `RobotConfiguration` | `Quarter1`, `Quarter4`, `Quarter6` and `QuarterX`, which say in which turn the axes sit |
| `RobotJoints`        | `Axis1` to `Axis6`, the six axes of the arm                                             |
| `ExternalJoints`     | `AxisA` to `AxisF`, the six external axes                                               |
| `RobTarget`          | a `Pose` plus a `Configuration` and the `ExternalAxes`                                  |
| `JointTarget`        | `RobotAxes` plus `ExternalAxes`                                                         |

A pose alone does not say how the robot reaches it. The same point in space is usually reachable in several ways, and `RobotConfiguration` is what tells them apart.

An external axis the system does not use is reported with a large value instead of a real one. Compare it with the constant `ExternalJoints.NotInUse` rather than with zero.

Units are not the same everywhere, and this is the convention of the controller, not a choice of the SDK:

| Where                                 | Units                                   |
| ------------------------------------- | --------------------------------------- |
| Positions, axis poses and base frames | millimetres, and degrees for the joints |
| The four kinematics calculations      | metres and radians                      |

**Members of Common.Position**
```csharp
public class Position {
    // Initializes a new position at the origin
    public Position()

    // Initializes a new position
    public Position(double x, double y, double z)

    // Returns a string representation of this position
    public override string ToString()

    // Coordinate along the X axis
    public double X { get; set; }

    // Coordinate along the Y axis
    public double Y { get; set; }

    // Coordinate along the Z axis
    public double Z { get; set; }
}
```

**Members of Common.Quaternion**
```csharp
public class Quaternion {
    // Initializes a new quaternion with no rotation at all (1, 0, 0, 0)
    public Quaternion()

    // Initializes a new quaternion
    public Quaternion(double q1, double q2, double q3, double q4)

    // Real component of the quaternion
    public double Q1 { get; set; }

    // First imaginary component of the quaternion
    public double Q2 { get; set; }

    // Second imaginary component of the quaternion
    public double Q3 { get; set; }

    // Third imaginary component of the quaternion
    public double Q4 { get; set; }

    // Returns a string representation of this orientation
    public override string ToString()
}
```

**Members of Common.Pose**
```csharp
public class Pose : Position {
    // Initializes a new pose at the origin, with no rotation
    public Pose()

    // Initializes a new pose
    public Pose(double x, double y, double z, double q1, double q2, double q3, double q4)

    // Initializes a new pose
    public Pose(double x, double y, double z, Quaternion orientation)

    // Orientation held at this position. Never null: a pose built without one carries the identity rotation.
    public Quaternion Orientation { get; set; }

    // Returns a string representation of this pose
    public override string ToString()
}
```

**Members of Common.RobotConfiguration**
```csharp
public class RobotConfiguration {
    // Initializes a new configuration with every quarter revolution set to zero
    public RobotConfiguration()

    // Initializes a new configuration
    public RobotConfiguration(int quarter1, int quarter4, int quarter6, int quarterX)

    // Quarter revolution axis 1 sits in
    public int Quarter1 { get; set; }

    // Quarter revolution axis 4 sits in
    public int Quarter4 { get; set; }

    // Quarter revolution axis 6 sits in
    public int Quarter6 { get; set; }

    // Index of the arm configuration, which tells the remaining joint combinations apart
    public int QuarterX { get; set; }

    // Returns a string representation of this configuration
    public override string ToString()
}
```

**Members of Common.RobotJoints**
```csharp
public class RobotJoints {
    // Initializes the six axes to zero
    public RobotJoints()

    // Initializes the six axes
    public RobotJoints(double axis1, double axis2, double axis3, double axis4, double axis5, double axis6)

    // Value of axis 1
    public double Axis1 { get; set; }

    // Value of axis 2
    public double Axis2 { get; set; }

    // Value of axis 3
    public double Axis3 { get; set; }

    // Value of axis 4
    public double Axis4 { get; set; }

    // Value of axis 5
    public double Axis5 { get; set; }

    // Value of axis 6
    public double Axis6 { get; set; }

    // Returns a string representation of these joint values
    public override string ToString()
}
```

**Members of Common.ExternalJoints**
```csharp
public class ExternalJoints {
    // Initializes the six external axes to zero
    public ExternalJoints()

    // Initializes the six external axes
    public ExternalJoints(double axisA, double axisB, double axisC, double axisD, double axisE, double axisF)

    // Value of external axis A
    public double AxisA { get; set; }

    // Value of external axis B
    public double AxisB { get; set; }

    // Value of external axis C
    public double AxisC { get; set; }

    // Value of external axis D
    public double AxisD { get; set; }

    // Value of external axis E
    public double AxisE { get; set; }

    // Value of external axis F
    public double AxisF { get; set; }

    // Value the controller reports for an external axis that is not in use
    public const double NotInUse = 9000000000

    // Returns a string representation of these external axis values
    public override string ToString()
}
```

**Members of Common.RobTarget**
```csharp
public class RobTarget : Pose {
    // Initializes a new target at the origin, with no rotation
    public RobTarget()

    // Initializes a new target
    public RobTarget(double x, double y, double z, Quaternion orientation, RobotConfiguration configuration, ExternalJoints externalAxes)

    // Axis configuration used to reach the pose. Never null.
    public RobotConfiguration Configuration { get; set; }

    // Values of the six external axes, null when the reading does not report them
    public ExternalJoints ExternalAxes { get; set; }

    // Returns a string representation of this target
    public override string ToString()
}
```

**Members of Common.JointTarget**
```csharp
public class JointTarget {
    // Initializes a new joint target with every axis at zero
    public JointTarget()

    // Initializes a new joint target
    public JointTarget(RobotJoints robotAxes, ExternalJoints externalAxes)

    // Values of the six external axes. Never null.
    public ExternalJoints ExternalAxes { get; set; }

    // Values of the six axes of the robot arm. Never null.
    public RobotJoints RobotAxes { get; set; }

    // Returns a string representation of this joint target
    public override string ToString()
}
```

## Mechanical units

> **Available on** RWS 1.0 (IRC5) : yes | RWS 2.0 (OmniCore) : yes.

A mechanical unit is anything the controller drives: the robot arm itself, a track, a positioner. `GetMechanicalUnits` lists them, `GetMechanicalUnit` describes one of them, and `SetMechanicalUnit` changes its properties.

**C# : MotionUnits**
```csharp
using UnderAutomation.ABB;
using UnderAutomation.ABB.Common;
using UnderAutomation.ABB.Rws.Data;

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

        /**/
        // Every mechanical unit of the system, with its activation state
        foreach (MechanicalUnitItem unit in robot.Rws.MotionSystem.GetMechanicalUnits())
        {
            Console.WriteLine($"{unit.Name} : {unit.Mode}, drive module {unit.DriveModule}");
        }

        // Everything the controller knows about one unit
        MechanicalUnitInfo info = robot.Rws.MotionSystem.GetMechanicalUnit("ROB_1");
        Console.WriteLine($"{info.Type}, task {info.TaskName}, status {info.Status}");
        Console.WriteLine($"tool {info.ToolName}, work object {info.WorkObjectName}, payload {info.PayloadName}");
        Console.WriteLine($"{info.Axes} axes, jog mode {info.JogMode}, frame {info.CoordinateSystem}");
        /**/

        /**/
        // Axis by axis. Axes are numbered from 1.
        int axisCount = robot.Rws.MotionSystem.GetAxisCount("ROB_1");

        for (int axis = 1; axis <= axisCount; axis++)
        {
            AxisInfo axisInfo = robot.Rws.MotionSystem.GetAxis("ROB_1", axis);
            Pose axisPose = robot.Rws.MotionSystem.GetAxisPose("ROB_1", axis);

            Console.WriteLine($"axis {axisInfo.Number} : {axisInfo.Status}, at {axisPose}");
        }

        // Where the base of the unit stands, in millimetres
        BaseFrame baseFrame = robot.Rws.MotionSystem.GetBaseFrame("ROB_1");
        Console.WriteLine($"base frame {baseFrame}, type {baseFrame.Type}");
        /**/

        /**/
        // Changing a property of a unit needs the mastership of the motion domain.
        // Give only the properties you want to change, leave the others null.
        robot.Rws.Mastership.Request(MastershipDomain.Motion);

        try
        {
            robot.Rws.MotionSystem.SetMechanicalUnit("ROB_1",
                                                     tool: "tGripper",
                                                     jogMode: JogMode.Cartesian,
                                                     coordinateSystem: CoordinateSystem.Base);
        }
        finally
        {
            robot.Rws.Mastership.Release();
        }

        // The controller answers success even when it could not apply one of the properties.
        // Read the unit back to see what it really did.
        Console.WriteLine(robot.Rws.MotionSystem.GetMechanicalUnit("ROB_1").ToolName);
        /**/

        // Declaring where a base or an axis sits changes the calibration of the cell.
        // The user account needs the matching UAS grant.
        Pose newBase = new Pose(0, 0, 0, new Quaternion(1, 0, 0, 0));
        robot.Rws.MotionSystem.SetBaseFrame("ROB_1", newBase);
        robot.Rws.MotionSystem.SetAxisPose("ROB_1", 1, newBase);

        robot.Disconnect();
    }
}
```

**Python : MotionUnits**
```python
from underautomation.abb.abb_controller import AbbController
from underautomation.abb.common.pose import Pose
from underautomation.abb.rws.data.coordinate_system import CoordinateSystem
from underautomation.abb.rws.data.jog_mode import JogMode
from underautomation.abb.rws.data.mastership_domain import MastershipDomain

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

##
# Every mechanical unit of the system, with its activation state
for unit in robot.rws.motion_system.get_mechanical_units():
    print(f"{unit.name} : {unit.mode}, drive module {unit.drive_module}")

# Everything the controller knows about one unit
info = robot.rws.motion_system.get_mechanical_unit("ROB_1")
print(f"{info.type}, task {info.task_name}, status {info.status}")
print(f"tool {info.tool_name}, work object {info.work_object_name}, payload {info.payload_name}")
print(f"{info.axes} axes, jog mode {info.jog_mode}, frame {info.coordinate_system}")
##

##
# Axis by axis. Axes are numbered from 1.
axis_count = robot.rws.motion_system.get_axis_count("ROB_1")

for axis in range(1, axis_count + 1):
    axis_info = robot.rws.motion_system.get_axis("ROB_1", axis)
    axis_pose = robot.rws.motion_system.get_axis_pose("ROB_1", axis)

    print(f"axis {axis_info.number} : {axis_info.status}, at {axis_pose}")

# Where the base of the unit stands, in millimetres
base_frame = robot.rws.motion_system.get_base_frame("ROB_1")
print(f"base frame {base_frame}, type {base_frame.type}")
##

##
# Changing a property of a unit needs the mastership of the motion domain.
# Give only the properties you want to change, leave the others None.
robot.rws.mastership.request(MastershipDomain.Motion)

try:
    robot.rws.motion_system.set_mechanical_unit("ROB_1",
                                                tool="tGripper",
                                                jogMode=JogMode.Cartesian,
                                                coordinateSystem=CoordinateSystem.Base)
finally:
    robot.rws.mastership.release(MastershipDomain.Motion)

# The controller answers success even when it could not apply one of the properties.
# Read the unit back to see what it really did.
print(robot.rws.motion_system.get_mechanical_unit("ROB_1").tool_name)
##

# Declaring where a base or an axis sits changes the calibration of the cell.
# The user account needs the matching UAS grant.
new_base = Pose(0, 0, 0, 1, 0, 0, 0)
robot.rws.motion_system.set_base_frame("ROB_1", new_base)
robot.rws.motion_system.set_axis_pose("ROB_1", 1, new_base)

robot.disconnect()
```

`MechanicalUnitInfo.Status` says whether the unit can move at all:

| `MechanicalUnitStatus`                               | Meaning                                                              |
| ---------------------------------------------------- | -------------------------------------------------------------------- |
| `Synchronized`                                       | Calibrated and synchronized, the unit can be moved                   |
| `NotCommutated`                                      | One or several motors have not been commutated                       |
| `NotCalibrated`                                      | The unit has never been calibrated                                   |
| `NotAbsoluteSynchronized`, `NotRelativeSynchronized` | The measurement of one or several axes is not synchronized           |
| `Locked`, `LockedShow`                               | The unit is locked and refuses to move                               |
| `Initiated`, `Undefined`, `Unknown`                  | The unit is starting up, or the controller does not report its state |

`SetMechanicalUnit` needs the mastership of the `Motion` domain. Give only the properties you want to change and leave the others null. The controller answers with a success status even when it could not apply one of them, so read the unit back to see what it really did.

`SetBaseFrame` and `SetAxisPose` declare where a unit or an axis sits in the cell. They do not move anything, they change the calibration of the cell, and the user account needs the matching UAS grant. The controller takes the request and applies the frame afterwards, so a success means the request was accepted, not that the new frame is already in use.

**Methods of MotionSystemService**
```csharp
// Gets the state of one axis of a mechanical unit (synchronous)
AxisInfo GetAxis(string mechanicalUnit, int axis);

// Gets how many axes a mechanical unit has (synchronous)
int GetAxisCount(string mechanicalUnit);

// Gets where one axis of a mechanical unit sits (synchronous) The position is expressed in millimetres.
Pose GetAxisPose(string mechanicalUnit, int axis);

// Gets where the base of a mechanical unit sits (synchronous) The position is expressed in millimetres.
BaseFrame GetBaseFrame(string mechanicalUnit);

// Gets everything the controller knows about one mechanical unit (synchronous)
MechanicalUnitInfo GetMechanicalUnit(string mechanicalUnit);

// Lists the mechanical units of the motion system (synchronous)
MechanicalUnitItem[] GetMechanicalUnits();

// Declares where one axis of a mechanical unit sits (synchronous) The position is expressed in millimetres.
void SetAxisPose(string mechanicalUnit, int axis, Pose pose);

// Declares where the base of a mechanical unit sits (synchronous) The position is expressed in millimetres.
void SetBaseFrame(string mechanicalUnit, Pose baseFrame);

// Changes one or several properties of a mechanical unit (synchronous) Every argument but the unit name is optional; leave the ones you do not want to touch null. At least one of them has to be given.
void SetMechanicalUnit(string mechanicalUnit, string tool = null, string workObject = null, string payload = null, string totalPayload = null, MechanicalUnitMode? mode = null, JogMode? jogMode = null, CoordinateSystem? coordinateSystem = null);

// Places a mechanical unit at the given joint values without moving it there (synchronous) Only a virtual controller accepts this: it teleports the simulated robot, which a real one cannot do.
void SetMechanicalUnitPosition(string mechanicalUnit, JointTarget position);
```

Every method also exists in an asynchronous version, with the same name followed by `Async` and an optional `CancellationToken`.

**Members of Rws.Data.MechanicalUnitItem**
```csharp
public class MechanicalUnitItem {
    // Initializes a new instance of the <xref href="UnderAutomation.ABB.Rws.Data.MechanicalUnitItem" data-throw-if-not-resolved="false"></xref> class
    public MechanicalUnitItem()

    // Whether the unit can be activated, null when the controller did not report it
    public bool? ActivationAllowed { get; set; }

    // Number of the drive module the unit is connected to, null when the controller did not report it
    public int? DriveModule { get; set; }

    // Whether the unit is activated
    public MechanicalUnitMode Mode { get; set; }

    // Name of the mechanical unit, for example "ROB_1"
    public string Name { get; set; }

    // Returns a string representation of this mechanical unit
    public override string ToString()
}
```

**Members of Rws.Data.MechanicalUnitInfo**
```csharp
public class MechanicalUnitInfo {
    // Initializes a new instance of the <xref href="UnderAutomation.ABB.Rws.Data.MechanicalUnitInfo" data-throw-if-not-resolved="false"></xref> class
    public MechanicalUnitInfo()

    // Number of axes of the unit, null when the controller did not report it
    public int? Axes { get; set; }

    // Reference frame the cartesian positions of the unit are expressed in
    public CoordinateSystem CoordinateSystem { get; set; }

    // Name of the mechanical unit integrated into this one. A unit that integrates no other one is
    // reported with a placeholder name rather than an empty value.
    public string HasIntegratedUnit { get; set; }

    // Name of the mechanical unit this one is integrated into. A unit that is integrated into no other
    // one is reported with a placeholder name rather than an empty value.
    public string IsIntegratedUnit { get; set; }

    // How the jogging commands sent to the unit are interpreted
    public JogMode JogMode { get; set; }

    // Whether the unit is activated
    public MechanicalUnitMode Mode { get; set; }

    // Name of the mechanical unit, for example "ROB_1"
    public string Name { get; set; }

    // Name of the active payload
    public string PayloadName { get; set; }

    // Calibration and synchronization state of the unit
    public MechanicalUnitStatus Status { get; set; }

    // Name of the RAPID task that drives the unit
    public string TaskName { get; set; }

    // Returns a string representation of this mechanical unit
    public override string ToString()

    // Name of the active tool
    public string ToolName { get; set; }

    // Number of axes of the unit and of the units integrated with it,
    // null when the controller did not report it
    public int? TotalAxes { get; set; }

    // Name of the active total payload, which is the payload plus the load of the tool
    public string TotalPayloadName { get; set; }

    // Kind of mechanical unit
    public MechanicalUnitType Type { get; set; }

    // Name of the active work object
    public string WorkObjectName { get; set; }
}
```

**Members of Rws.Data.MechanicalUnitMode**
```csharp
public enum MechanicalUnitMode {
    // The mechanical unit is activated and takes part in the motion
    Activated = 1

    // The mechanical unit is deactivated and stays where it is
    Deactivated = 2

    // The controller reported a mode this library does not know
    Unknown = 0
}
```

**Members of Rws.Data.MechanicalUnitType**
```csharp
public enum MechanicalUnitType {
    // No mechanical unit
    None = 1

    // A robot arm without a tool center point, which can only be moved axis by axis
    Robot = 3

    // A single external axis, such as a track or a positioner
    Single = 4

    // A robot arm holding a tool center point, which can be moved in cartesian coordinates
    TcpRobot = 2

    // The controller knows the unit but does not report what it is
    Undefined = 5

    // The controller reported a type this library does not know
    Unknown = 0
}
```

**Members of Rws.Data.MechanicalUnitStatus**
```csharp
public enum MechanicalUnitStatus {
    // The unit is starting up
    Initiated = 1

    // The unit is locked and refuses to move
    Locked = 7

    // The unit is locked, and the controller shows it as such
    LockedShow = 8

    // One or several absolute measurement axes are not synchronized
    NotAbsoluteSynchronized = 4

    // The unit has never been calibrated
    NotCalibrated = 3

    // One or several motors have not been commutated
    NotCommutated = 2

    // One or several relative measurement axes are not synchronized
    NotRelativeSynchronized = 5

    // The unit is calibrated and synchronized, and can be moved
    Synchronized = 6

    // The controller knows the unit but does not report its state
    Undefined = 9

    // The controller reported a state this library does not know
    Unknown = 0
}
```

**Members of Rws.Data.AxisInfo**
```csharp
public class AxisInfo {
    // Initializes a new instance of the <xref href="UnderAutomation.ABB.Rws.Data.AxisInfo" data-throw-if-not-resolved="false"></xref> class
    public AxisInfo()

    // Logical joint number of the axis, null when the controller did not report it
    public int? LogicalAxis { get; set; }

    // Number of the axis inside its mechanical unit, starting at 1
    public int Number { get; set; }

    // Calibration and synchronization state of the axis
    public MechanicalUnitStatus Status { get; set; }

    // Returns a string representation of this axis
    public override string ToString()
}
```

**Members of Rws.Data.BaseFrame**
```csharp
public class BaseFrame : Pose {
    // Initializes a new base frame at the origin, with no rotation
    public BaseFrame()

    // Returns a string representation of this base frame
    public override string ToString()

    // Kind of base frame the controller reports, for example "IRBRobot"
    public string Type { get; set; }
}
```

**Members of Rws.Data.CoordinateSystem**
```csharp
public enum CoordinateSystem {
    // The base frame of the mechanical unit
    Base = 2

    // The frame of the active tool
    Tool = 3

    // The controller reported a frame this library does not know
    Unknown = 0

    // The frame of the active work object
    WorkObject = 4

    // The world frame, shared by every mechanical unit of the system
    World = 1
}
```

**Members of Rws.Data.JogMode**
```csharp
public enum JogMode {
    // The tool is aligned with the closest axis of the active coordinate system
    Align = 4

    // Each command moves one axis of the first axis group
    AxisGroup1 = 1

    // Each command moves one axis of the second axis group
    AxisGroup2 = 2

    // The tool is moved along the axes of the active coordinate system
    Cartesian = 3

    // The robot changes axis configuration without moving the tool center point
    ConfigurationJog = 6

    // The robot moves to a given position
    GoToPosition = 5

    // The controller reported a mode this library does not know
    Unknown = 0
}
```

## Read the robot position

> **Available on** RWS 1.0 (IRC5) : yes | RWS 2.0 (OmniCore) : yes.

Four readings answer the same question in four ways. All of them are read only and need no mastership.

| Method                       | Returns                                                                               |
| ---------------------------- | ------------------------------------------------------------------------------------- |
| `GetRobTarget(unit)`         | `RobTarget`, the Cartesian position with the axis configuration and the external axes |
| `GetCartesianPosition(unit)` | `RobTarget` without the external axes, `ExternalAxes` is then null                    |
| `GetJointTarget(unit)`       | `JointTarget`, the joint values                                                       |
| `GetPhysicalJoints(unit)`    | `RobotJoints`, the raw values of the measurement system of the unit                   |

**C# : PositionRead**
```csharp
using UnderAutomation.ABB;
using UnderAutomation.ABB.Common;
using UnderAutomation.ABB.Rws.Data;

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

        /**/
        // Where the tool is, in millimetres, with the axis configuration and the external axes
        RobTarget target = robot.Rws.MotionSystem.GetRobTarget("ROB_1");
        Console.WriteLine($"X={target.X} Y={target.Y} Z={target.Z}");
        Console.WriteLine($"orientation {target.Orientation}");
        Console.WriteLine($"configuration {target.Configuration}");
        Console.WriteLine($"external axes {target.ExternalAxes}");

        // The same reading in another frame, with a given tool and work object
        RobTarget inWorld = robot.Rws.MotionSystem.GetRobTarget("ROB_1", CoordinateSystem.World,
                                                                tool: "tGripper", workObject: "wobj0");
        Console.WriteLine(inWorld);
        /**/

        /**/
        // The joint values, robot axes in degrees
        JointTarget joints = robot.Rws.MotionSystem.GetJointTarget("ROB_1");
        Console.WriteLine($"axis 1 = {joints.RobotAxes.Axis1} deg");
        Console.WriteLine($"axis 2 = {joints.RobotAxes.Axis2} deg");

        // An external axis the system does not define comes back as ExternalJoints.NotInUse
        if (joints.ExternalAxes.AxisA != ExternalJoints.NotInUse)
        {
            Console.WriteLine($"external axis A = {joints.ExternalAxes.AxisA}");
        }

        // alwaysRead asks the controller to measure again instead of answering with the value it holds
        JointTarget measured = robot.Rws.MotionSystem.GetJointTarget("ROB_1", alwaysRead: true);
        Console.WriteLine(measured);
        /**/

        /**/
        // Cartesian position without the external axes. ExternalAxes is null here.
        RobTarget cartesian = robot.Rws.MotionSystem.GetCartesianPosition("ROB_1");
        Console.WriteLine(cartesian);

        // The raw values of the measurement system of the unit
        RobotJoints physical = robot.Rws.MotionSystem.GetPhysicalJoints("ROB_1");
        Console.WriteLine(physical);
        /**/

        /**/
        // The same two positions seen from a RAPID task instead of a mechanical unit
        RobTarget taskTarget = robot.Rws.Rapid.GetRobTarget("T_ROB1");
        JointTarget taskJoints = robot.Rws.Rapid.GetJointTarget("T_ROB1");

        // Which external joints of the task carry a real value
        RapidExternalJointStates states = robot.Rws.Rapid.GetExternalJointStates("T_ROB1");
        Console.WriteLine($"external joint 1 : {states.Joint1}");

        // The units the task can move
        foreach (RapidMechanicalUnitItem unit in robot.Rws.Rapid.GetMechanicalUnits("T_ROB1"))
        {
            Console.WriteLine($"{unit.Name} : {unit.Type}, {unit.Mode}");
        }
        /**/

        robot.Disconnect();
    }
}
```

**Python : PositionRead**
```python
from underautomation.abb.abb_controller import AbbController
from underautomation.abb.common.external_joints import ExternalJoints
from underautomation.abb.rws.data.coordinate_system import CoordinateSystem

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

##
# Where the tool is, in millimetres, with the axis configuration and the external axes
target = robot.rws.motion_system.get_rob_target("ROB_1")
print(f"X={target.x} Y={target.y} Z={target.z}")
print(f"orientation {target.orientation}")
print(f"configuration {target.configuration}")
print(f"external axes {target.external_axes}")

# The same reading in another frame, with a given tool and work object
in_world = robot.rws.motion_system.get_rob_target("ROB_1", CoordinateSystem.World,
                                                  tool="tGripper", workObject="wobj0")
print(in_world)
##

##
# The joint values, robot axes in degrees
joints = robot.rws.motion_system.get_joint_target("ROB_1")
print(f"axis 1 = {joints.robot_axes.axis1} deg")
print(f"axis 2 = {joints.robot_axes.axis2} deg")

# An external axis the system does not define comes back as ExternalJoints.NotInUse
if joints.external_axes.axis_a != ExternalJoints.NotInUse:
    print(f"external axis A = {joints.external_axes.axis_a}")

# alwaysRead asks the controller to measure again instead of answering with the value it holds
measured = robot.rws.motion_system.get_joint_target("ROB_1", alwaysRead=True)
print(measured)
##

##
# Cartesian position without the external axes. external_axes is None here.
cartesian = robot.rws.motion_system.get_cartesian_position("ROB_1")
print(cartesian)

# The raw values of the measurement system of the unit
physical = robot.rws.motion_system.get_physical_joints("ROB_1")
print(physical)
##

##
# The same two positions seen from a RAPID task instead of a mechanical unit
task_target = robot.rws.rapid.get_rob_target("T_ROB1")
task_joints = robot.rws.rapid.get_joint_target("T_ROB1")

# Which external joints of the task carry a real value
states = robot.rws.rapid.get_external_joint_states("T_ROB1")
print(f"external joint 1 : {states.joint1}")

# The units the task can move
for unit in robot.rws.rapid.get_mechanical_units("T_ROB1"):
    print(f"{unit.name} : {unit.type}, {unit.mode}")
##

robot.disconnect()
```

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

robot = AbbController();
robot.Connect('192.168.0.1');

%%
% Tool position in millimeters, in the base frame, with the active tool and work object.
% Pass every argument: [] passes null for the tool and the work object.
target = robot.Rws.MotionSystem.GetRobTarget('ROB_1', CoordinateSystem.Base, [], []);
fprintf('X=%.1f Y=%.1f Z=%.1f\n', target.X, target.Y, target.Z);

% Orientation as a quaternion
q = target.Orientation;
fprintf('q = [%.4f, %.4f, %.4f, %.4f]\n', q.Q1, q.Q2, q.Q3, q.Q4);

% Joint values of the robot axes, in degrees
joints = robot.Rws.MotionSystem.GetJointTarget('ROB_1', false);
robotAxes = joints.RobotAxes;
j = [robotAxes.Axis1, robotAxes.Axis2, robotAxes.Axis3, robotAxes.Axis4, robotAxes.Axis5, robotAxes.Axis6];
%%

robot.Disconnect();
```

`GetRobTarget` takes a `CoordinateSystem`, a tool and a work object. Without them it answers in the base frame of the unit, with the tool and the work object currently active on it. Passing a tool that does not exist is refused by the controller.

`GetJointTarget` has an `alwaysRead` parameter. By default the controller answers with the value it already holds. With `alwaysRead: true` it measures the position again, which costs more and is what you want when the robot has just moved.

**Methods of MotionSystemService**
```csharp
// Gets where the tool of a mechanical unit currently is, without the external axes (synchronous) The position is expressed in millimetres.
RobTarget GetCartesianPosition(string mechanicalUnit, CoordinateSystem coordinateSystem = CoordinateSystem.Base, string tool = null, string workObject = null, bool logErrors = false);

// Gets the joint values a mechanical unit currently stands at (synchronous) The robot axes are expressed in degrees.
JointTarget GetJointTarget(string mechanicalUnit, bool alwaysRead = false);

// Gets the physical joint values of a mechanical unit, as its measurement system reads them (synchronous)
RobotJoints GetPhysicalJoints(string mechanicalUnit);

// Gets where the tool of a mechanical unit currently is (synchronous) The position is expressed in millimetres.
RobTarget GetRobTarget(string mechanicalUnit, CoordinateSystem coordinateSystem = CoordinateSystem.Base, string tool = null, string workObject = null);
```

Every method also exists in an asynchronous version, with the same name followed by `Async` and an optional `CancellationToken`.

## Position of a RAPID task

> **Available on** RWS 1.0 (IRC5) : yes | RWS 2.0 (OmniCore) : yes.

The same two positions can also be read from a RAPID task instead of a mechanical unit. Those methods are on `robot.Rws.Rapid`, and the last block of the snippet above shows them.

| Method                                         | Returns                                              |
| ---------------------------------------------- | ---------------------------------------------------- |
| `robot.Rws.Rapid.GetRobTarget(task)`           | `RobTarget` of the robot of that task                |
| `robot.Rws.Rapid.GetJointTarget(task)`         | `JointTarget` of the robot of that task              |
| `robot.Rws.Rapid.GetExternalJointStates(task)` | What each external joint of the task is doing        |
| `robot.Rws.Rapid.GetMechanicalUnits(task)`     | The units the positions of the task are expressed in |

Which one to use:

- Read from the motion system when you work with a mechanical unit by name, when you need another coordinate system, another tool or another work object, or when you want the raw measurement.
- Read from the RAPID task when your code already works with tasks, or when you want the position exactly as the running program sees it.

`GetExternalJointStates` is what says how to read the external axis values of the task: a joint can be linear, rotating, inactive, or active without a position. A `RobTarget` read from a task reports an inactive external axis with the same large value as `ExternalJoints.NotInUse`.

**Methods of RapidService**
```csharp
// Gets what each of the six external joints of a task is doing (synchronous) This is what says how to read the corresponding value of GetJointTarget(System.String): a joint reported as not active carries no meaningful position.
RapidExternalJointStates GetExternalJointStates(string task);

// Gets the joint values of the robot of a task (synchronous)
JointTarget GetJointTarget(string task);

// Gets the mechanical units the positions of a task are expressed in (synchronous)
RapidMechanicalUnitItem[] GetMechanicalUnits(string task);

// Gets where the tool of a task currently stands, as a position and an orientation (synchronous)
RobTarget GetRobTarget(string task, string tool = null, string workObject = null);
```

Every method also exists in an asynchronous version, with the same name followed by `Async` and an optional `CancellationToken`.

**Members of Rws.Data.RapidExternalJointStates**
```csharp
public class RapidExternalJointStates {
    // Initializes a new instance of the <xref href="UnderAutomation.ABB.Rws.Data.RapidExternalJointStates" data-throw-if-not-resolved="false"></xref> class
    public RapidExternalJointStates()

    // State of the first external joint
    public RapidJointState Joint1 { get; set; }

    // State of the second external joint
    public RapidJointState Joint2 { get; set; }

    // State of the third external joint
    public RapidJointState Joint3 { get; set; }

    // State of the fourth external joint
    public RapidJointState Joint4 { get; set; }

    // State of the fifth external joint
    public RapidJointState Joint5 { get; set; }

    // State of the sixth external joint
    public RapidJointState Joint6 { get; set; }

    // Returns a string representation of these joint states
    public override string ToString()
}
```

**Members of Rws.Data.RapidJointState**
```csharp
public enum RapidJointState {
    // The joint moves along a line
    Linear = 1

    // The joint is active but has no position
    NoPosition = 4

    // The joint is not active
    NotActive = 3

    // The joint turns
    Rotating = 2

    // The controller reported a state this library does not know
    Unknown = 0
}
```

**Members of Rws.Data.RapidMechanicalUnitItem**
```csharp
public class RapidMechanicalUnitItem {
    // Initializes a new instance of the <xref href="UnderAutomation.ABB.Rws.Data.RapidMechanicalUnitItem" data-throw-if-not-resolved="false"></xref> class
    public RapidMechanicalUnitItem()

    // Whether the unit is activated
    public MechanicalUnitMode Mode { get; set; }

    // Name of the unit, for example "ROB_1"
    public string Name { get; set; }

    // Returns a string representation of this unit
    public override string ToString()

    // Kind of unit
    public MechanicalUnitType Type { get; set; }
}
```

## Move to a target

> **Available on** RWS 1.0 (IRC5) : yes | RWS 2.0 (OmniCore) : yes.

`SetPositionTarget` sends the robot to a Cartesian target. It really moves the arm. The preconditions are the same as for jogging: the controller in manual mode, the motors on, this client as the local client of the controller, and the mastership of the `Motion` domain.

`SetMechanicalUnitPosition` does not move anything. It places the unit at the given joint values. Only a virtual controller accepts it, a real one refuses the call.

**C# : PositionSet**
```csharp
using UnderAutomation.ABB;
using UnderAutomation.ABB.Common;
using UnderAutomation.ABB.Rws.Data;

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

        /**/
        // SetPositionTarget really moves the robot to a cartesian target.
        // Same preconditions as jogging: manual mode, motors on, local client, motion mastership.
        RobTarget target = robot.Rws.MotionSystem.GetRobTarget("ROB_1");
        target.Z += 10; // 10 mm up

        robot.Rws.Mastership.Request(MastershipDomain.Motion);

        try
        {
            robot.Rws.MotionSystem.SetPositionTarget(target);
        }
        finally
        {
            robot.Rws.Mastership.Release();
        }
        /**/

        /**/
        // SetMechanicalUnitPosition does not move anything: it places the unit at the given joint
        // values. Only a virtual controller accepts it, a real one refuses the call.
        JointTarget joints = new JointTarget(new RobotJoints(0, 0, 0, 0, 30, 0), new ExternalJoints());

        robot.Rws.Mastership.Request(MastershipDomain.Motion);

        try
        {
            robot.Rws.MotionSystem.SetMechanicalUnitPosition("ROB_1", joints);
        }
        finally
        {
            robot.Rws.Mastership.Release();
        }
        /**/

        robot.Disconnect();
    }
}
```

**Python : PositionSet**
```python
from underautomation.abb.abb_controller import AbbController
from underautomation.abb.common.external_joints import ExternalJoints
from underautomation.abb.common.joint_target import JointTarget
from underautomation.abb.common.robot_joints import RobotJoints
from underautomation.abb.rws.data.mastership_domain import MastershipDomain

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

##
# set_position_target really moves the robot to a cartesian target.
# Same preconditions as jogging: manual mode, motors on, local client, motion mastership.
target = robot.rws.motion_system.get_rob_target("ROB_1")
target.z += 10  # 10 mm up

robot.rws.mastership.request(MastershipDomain.Motion)

try:
    robot.rws.motion_system.set_position_target(target)
finally:
    robot.rws.mastership.release(MastershipDomain.Motion)
##

##
# set_mechanical_unit_position does not move anything: it places the unit at the given joint
# values. Only a virtual controller accepts it, a real one refuses the call.
joints = JointTarget(RobotJoints(0, 0, 0, 0, 30, 0), ExternalJoints(0, 0, 0, 0, 0, 0))

robot.rws.mastership.request(MastershipDomain.Motion)

try:
    robot.rws.motion_system.set_mechanical_unit_position("ROB_1", joints)
finally:
    robot.rws.mastership.release(MastershipDomain.Motion)
##

robot.disconnect()
```

**Methods of MotionSystemService**
```csharp
// Places a mechanical unit at the given joint values without moving it there (synchronous) Only a virtual controller accepts this: it teleports the simulated robot, which a real one cannot do.
void SetMechanicalUnitPosition(string mechanicalUnit, JointTarget position);

// Sends the robot to a cartesian target (synchronous) The position is expressed in millimetres, in the coordinate system currently active for the mechanical unit selected for jogging.
void SetPositionTarget(RobTarget target);
```

Every method also exists in an asynchronous version, with the same name followed by `Async` and an optional `CancellationToken`.

## Jog the robot

> **Available on** RWS 1.0 (IRC5) : yes | RWS 2.0 (OmniCore) : yes.

Jogging moves the robot step by step, the way the operator does from the FlexPendant. Four conditions have to be met, and none of them can be arranged by a request:

1. The controller is in manual mode. In automatic mode the request is refused with the HTTP status code 403.
2. The motors are on.
3. This client is the local client of the controller.
4. This connection holds the mastership of the `Motion` domain.

**C# : JogRobot**
```csharp
using UnderAutomation.ABB;
using UnderAutomation.ABB.Common;
using UnderAutomation.ABB.Rws.Data;

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

        /**/
        // Check the preconditions before asking the robot to move
        OperationMode mode = robot.Rws.Panel.GetOperationMode();
        ControllerState state = robot.Rws.Panel.GetControllerState();

        if (mode == OperationMode.Automatic || state != ControllerState.MotorsOn)
        {
            Console.WriteLine("Jogging is refused in this state");
            return;
        }
        /**/

        /**/
        robot.Rws.Mastership.Request(MastershipDomain.Motion);

        try
        {
            // Which unit the jogging commands apply to
            robot.Rws.MotionSystem.SetJoggingMechanicalUnit("ROB_1");

            // How the six values are read depends on the jog mode of that unit
            robot.Rws.MotionSystem.SetMechanicalUnit("ROB_1", jogMode: JogMode.AxisGroup1);

            // The change count of the last reading. The controller refuses a command
            // built on a state that has moved on since.
            MotionSystemInfo info = robot.Rws.MotionSystem.GetInfo();

            // One small step on axis 1, nothing on the other five
            RobotJoints step = new RobotJoints(100, 0, 0, 0, 0, 0);

            robot.Rws.MotionSystem.Jog(step, info.ChangeCount.Value, JogIncrementMode.Small);
        }
        finally
        {
            robot.Rws.Mastership.Release();
        }
        /**/

        /**/
        // With JogIncrementMode.None the robot moves for as long as the command is repeated,
        // so the loop itself is what stops the motion.
        MotionSystemInfo current = robot.Rws.MotionSystem.GetInfo();
        RobotJoints speed = new RobotJoints(50, 0, 0, 0, 0, 0);

        for (int i = 0; i < 20; i++)
        {
            robot.Rws.MotionSystem.Jog(speed, current.ChangeCount.Value, JogIncrementMode.None);
            System.Threading.Thread.Sleep(100);
        }

        // Some jogging requests are accepted by the controller and still not honoured.
        // The error state says what went wrong.
        MotionSystemErrorState error = robot.Rws.MotionSystem.GetErrorState();
        Console.WriteLine($"{error.State}, {error.Count} error(s)");
        /**/

        robot.Disconnect();
    }
}
```

**Python : JogRobot**
```python
import time

from underautomation.abb.abb_controller import AbbController
from underautomation.abb.common.robot_joints import RobotJoints
from underautomation.abb.rws.data.controller_state import ControllerState
from underautomation.abb.rws.data.jog_increment_mode import JogIncrementMode
from underautomation.abb.rws.data.jog_mode import JogMode
from underautomation.abb.rws.data.mastership_domain import MastershipDomain
from underautomation.abb.rws.data.operation_mode import OperationMode

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

##
# Check the preconditions before asking the robot to move
mode = robot.rws.panel.get_operation_mode()
state = robot.rws.panel.get_controller_state()

if mode == OperationMode.Automatic or state != ControllerState.MotorsOn:
    print("Jogging is refused in this state")
    raise SystemExit(0)
##

##
robot.rws.mastership.request(MastershipDomain.Motion)

try:
    # Which unit the jogging commands apply to
    robot.rws.motion_system.set_jogging_mechanical_unit("ROB_1")

    # How the six values are read depends on the jog mode of that unit
    robot.rws.motion_system.set_mechanical_unit("ROB_1", jogMode=JogMode.AxisGroup1)

    # The change count of the last reading. The controller refuses a command
    # built on a state that has moved on since.
    info = robot.rws.motion_system.get_info()

    # One small step on axis 1, nothing on the other five
    step = RobotJoints(100, 0, 0, 0, 0, 0)

    robot.rws.motion_system.jog(step, info.change_count, JogIncrementMode.Small)
finally:
    robot.rws.mastership.release(MastershipDomain.Motion)
##

##
# With JogIncrementMode.None_ the robot moves for as long as the command is repeated,
# so the loop itself is what stops the motion.
current = robot.rws.motion_system.get_info()
speed = RobotJoints(50, 0, 0, 0, 0, 0)

for i in range(20):
    robot.rws.motion_system.jog(speed, current.change_count, JogIncrementMode.None_)
    time.sleep(0.1)

# Some jogging requests are accepted by the controller and still not honoured.
# The error state says what went wrong.
error = robot.rws.motion_system.get_error_state()
print(f"{error.state}, {error.count} errors")
##

robot.disconnect()
```

`SetJoggingMechanicalUnit` chooses which unit the following jogging commands apply to. `Jog` then sends six values. How they are read depends on the jog mode of that unit, which `SetMechanicalUnit` sets:

| `JogMode`                  | The six values are                                                             |
| -------------------------- | ------------------------------------------------------------------------------ |
| `AxisGroup1`, `AxisGroup2` | One value per axis of the group                                                |
| `Cartesian`                | A motion of the tool along the axes of the active coordinate system            |
| `Align`                    | An alignment of the tool with the closest axis of the active coordinate system |
| `GoToPosition`             | A position to move to                                                          |
| `ConfigurationJog`         | A change of axis configuration that does not move the tool center point        |

`Jog` also takes the change count of the last reading of the motion system. The controller refuses a command built on a state that has moved on since, so read `GetInfo().ChangeCount` before jogging.

| `JogIncrementMode`         | Effect                                                                                     |
| -------------------------- | ------------------------------------------------------------------------------------------ |
| `None`                     | The robot moves for as long as the command is repeated. The loop is what stops the motion. |
| `User`                     | One step of the size configured in the system parameters                                   |
| `Small`, `Medium`, `Large` | One step of the corresponding size                                                         |

A jogging request can be accepted by the controller and still not honoured. `GetErrorState` then says why, see the last section of this page.

**Methods of MotionSystemService**
```csharp
// Moves the mechanical unit currently selected for jogging (synchronous) The unit is the one SetJoggingMechanicalUnit(System.String) chose, and how the six values are interpreted depends on its jog mode: axis by axis, along the axes of a coordinate system, and so on.
void Jog(RobotJoints axes, int changeCount, JogIncrementMode incrementMode = JogIncrementMode.None);

// Chooses which mechanical unit the jogging commands apply to (synchronous)
void SetJoggingMechanicalUnit(string mechanicalUnit);
```

Every method also exists in an asynchronous version, with the same name followed by `Async` and an optional `CancellationToken`.

**Members of Rws.Data.JogIncrementMode**
```csharp
public enum JogIncrementMode {
    // One large step
    Large = 4

    // One medium step
    Medium = 3

    // The robot moves for as long as the command is repeated, with no fixed step
    None = 0

    // One small step
    Small = 2

    // One step of the size configured in the system parameters
    User = 1
}
```

## Kinematics

> **Available on** RWS 1.0 (IRC5) : yes | RWS 2.0 (OmniCore) : yes.

The controller can compute where the tool would be for a set of joint values, and which joint values put the tool at a given pose. Nothing moves, and no mastership is needed.

**These four calculations work in metres and radians**, unlike every other reading of the service. The values are passed and returned in the same classes, only the unit changes.

**C# : Kinematics**
```csharp
using UnderAutomation.ABB;
using UnderAutomation.ABB.Common;
using UnderAutomation.ABB.Rws.Data;

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

        /**/
        // These four calculations work in metres and radians, not in millimetres and degrees.

        // Tool relative to the mounting flange. Here the flange itself, with no rotation.
        Pose toolFrame = new Pose(0, 0, 0, new Quaternion(1, 0, 0, 0));

        // Forward kinematics: where the tool would be for these joint values
        JointTarget joints = new JointTarget(new RobotJoints(0, 0, 0, 0, 0.5, 0), new ExternalJoints());

        RobTarget pose = robot.Rws.MotionSystem.GetPoseFromJoints("ROB_1", toolFrame, joints);
        Console.WriteLine($"tool at {pose.X} {pose.Y} {pose.Z} metres, configuration {pose.Configuration}");
        /**/

        /**/
        // Inverse kinematics: which joint values put the tool at that pose.
        // previousJoints decides between the solutions the pose admits.
        JointTarget solution = robot.Rws.MotionSystem.GetJointsFromPose("ROB_1", pose, new ExternalJoints(),
                                                                       toolFrame, joints, pose.Configuration);
        Console.WriteLine($"axis 5 = {solution.RobotAxes.Axis5} rad");

        // The controller has a second calculation for the same question. It does not always
        // pick the same solution.
        JointTarget other = robot.Rws.MotionSystem.GetJointsFromCartesian("ROB_1", pose, new ExternalJoints(),
                                                                         toolFrame, joints, pose.Configuration);
        Console.WriteLine(other);
        /**/

        /**/
        // Every way of reaching the pose. A six axis robot usually has eight.
        JointSolution[] solutions = robot.Rws.MotionSystem.GetAllJointSolutions("ROB_1", pose, new ExternalJoints(),
                                                                               toolFrame, pose.Configuration);

        foreach (JointSolution s in solutions)
        {
            Console.WriteLine($"{s.Configuration} : {s.RobotAxes}");
        }
        /**/

        // Set robotHoldsWorkObject to true when the tool is fixed in the cell and the robot
        // carries the work object. Set logErrors to true to have the controller write an
        // event log message when the calculation fails.
        RobTarget held = robot.Rws.MotionSystem.GetPoseFromJoints("ROB_1", toolFrame, joints,
                                                                 robotHoldsWorkObject: true, logErrors: true);
        Console.WriteLine(held);

        robot.Disconnect();
    }
}
```

**Python : Kinematics**
```python
from underautomation.abb.abb_controller import AbbController
from underautomation.abb.common.external_joints import ExternalJoints
from underautomation.abb.common.joint_target import JointTarget
from underautomation.abb.common.pose import Pose
from underautomation.abb.common.robot_joints import RobotJoints

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

##
# These four calculations work in metres and radians, not in millimetres and degrees.

# Tool relative to the mounting flange. Here the flange itself, with no rotation.
tool_frame = Pose(0, 0, 0, 1, 0, 0, 0)

# Forward kinematics: where the tool would be for these joint values
joints = JointTarget(RobotJoints(0, 0, 0, 0, 0.5, 0), ExternalJoints(0, 0, 0, 0, 0, 0))

pose = robot.rws.motion_system.get_pose_from_joints("ROB_1", tool_frame, joints)
print(f"tool at {pose.x} {pose.y} {pose.z} metres, configuration {pose.configuration}")
##

##
# Inverse kinematics: which joint values put the tool at that pose.
# previousJoints decides between the solutions the pose admits.
solution = robot.rws.motion_system.get_joints_from_pose("ROB_1", pose, ExternalJoints(0, 0, 0, 0, 0, 0),
                                                       tool_frame, joints, pose.configuration)
print(f"axis 5 = {solution.robot_axes.axis5} rad")

# The controller has a second calculation for the same question. It does not always
# pick the same solution.
other = robot.rws.motion_system.get_joints_from_cartesian("ROB_1", pose, ExternalJoints(0, 0, 0, 0, 0, 0),
                                                          tool_frame, joints, pose.configuration)
print(other)
##

##
# Every way of reaching the pose. A six axis robot usually has eight.
solutions = robot.rws.motion_system.get_all_joint_solutions("ROB_1", pose, ExternalJoints(0, 0, 0, 0, 0, 0),
                                                            tool_frame, pose.configuration)

for s in solutions:
    print(f"{s.configuration} : {s.robot_axes}")
##

# Set robotHoldsWorkObject to True when the tool is fixed in the cell and the robot
# carries the work object. Set logErrors to True to have the controller write an
# event log message when the calculation fails.
held = robot.rws.motion_system.get_pose_from_joints("ROB_1", tool_frame, joints,
                                                    robotHoldsWorkObject=True, logErrors=True)
print(held)

robot.disconnect()
```

| Method                   | Answers                                                                   |
| ------------------------ | ------------------------------------------------------------------------- |
| `GetPoseFromJoints`      | Forward kinematics: the pose for these joint values                       |
| `GetJointsFromPose`      | Inverse kinematics: the joint values for this pose                        |
| `GetJointsFromCartesian` | The same question, through a second calculation of the controller         |
| `GetAllJointSolutions`   | Every joint combination that reaches the pose, one per axis configuration |

`GetJointsFromPose` and `GetJointsFromCartesian` take the same arguments and do not always return the same solution. Both are exposed because a controller can accept one and refuse the other. Compare the pose they reach rather than the joint values themselves.

`previousJoints` is what decides between the solutions a pose admits. Pass the joint values the robot is currently in, so the answer is the closest one.

Set `robotHoldsWorkObject` to true when the tool is fixed in the cell and the robot carries the work object. Set `logErrors` to true to have the controller write an event log message when the calculation fails.

A pose that cannot be reached is refused by the controller and reported as an `RwsException`.

**Methods of MotionSystemService**
```csharp
// Asks the controller for every joint combination that puts the tool at the given pose (synchronous) A six axis robot usually reaches the same pose in eight different ways, each one in a different axis configuration.
JointSolution[] GetAllJointSolutions(string mechanicalUnit, Pose pose, ExternalJoints externalAxes, Pose toolFrame, RobotConfiguration configuration, bool robotHoldsWorkObject = false);

// Asks the controller which joint values put the tool at the given pose, staying close to the joint values the robot is already in (synchronous)
JointTarget GetJointsFromCartesian(string mechanicalUnit, Pose pose, ExternalJoints externalAxes, Pose toolFrame, JointTarget previousJoints, RobotConfiguration configuration, bool robotHoldsWorkObject = false, bool logErrors = false);

// Asks the controller which joint values put the tool at the given pose (synchronous)
JointTarget GetJointsFromPose(string mechanicalUnit, Pose pose, ExternalJoints externalAxes, Pose toolFrame, JointTarget previousJoints, RobotConfiguration configuration, bool robotHoldsWorkObject = false, bool logErrors = false);

// Asks the controller where the tool would be if the robot stood at the given joint values, without moving it there (synchronous)
RobTarget GetPoseFromJoints(string mechanicalUnit, Pose toolFrame, JointTarget joints, bool robotHoldsWorkObject = false, bool logErrors = false);
```

Every method also exists in an asynchronous version, with the same name followed by `Async` and an optional `CancellationToken`.

**Members of Rws.Data.JointSolution**
```csharp
public class JointSolution : JointTarget {
    // Initializes a new solution with every axis at zero
    public JointSolution()

    // Axis configuration this solution corresponds to. Never null.
    public RobotConfiguration Configuration { get; set; }

    // Returns a string representation of this solution
    public override string ToString()
}
```

## Collision supervision

> **Available on** RWS 1.0 (IRC5) : yes | RWS 2.0 (OmniCore) : yes.

The controller watches the torque of the axes and stops the robot when it meets an unexpected resistance. There is one setting for jogging and one for a programmed path, per mechanical unit.

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

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

        /**/
        // Collision detection while the unit is jogged
        MotionSupervision jogging = robot.Rws.MotionSystem.GetMotionSupervision("ROB_1");
        Console.WriteLine($"jogging supervision {jogging.Enabled}, sensitivity {jogging.Level} %");

        // Collision detection while the unit follows a programmed path
        PathSupervision path = robot.Rws.MotionSystem.GetPathSupervision("ROB_1");
        Console.WriteLine($"path supervision {path.Enabled}, sensitivity {path.Level} %");
        /**/

        /**/
        // Writing these four values needs the mastership of the motion domain.
        // The lower the percentage, the sooner the controller reports a collision.
        robot.Rws.Mastership.Request(MastershipDomain.Motion);

        try
        {
            robot.Rws.MotionSystem.SetMotionSupervisionMode("ROB_1", true);
            robot.Rws.MotionSystem.SetMotionSupervisionLevel("ROB_1", 80);

            robot.Rws.MotionSystem.SetPathSupervisionMode("ROB_1", true);
            robot.Rws.MotionSystem.SetPathSupervisionLevel("ROB_1", 80);
        }
        finally
        {
            robot.Rws.Mastership.Release();
        }
        /**/

        /**/
        // Collision prediction stops the robot before it hits something the controller
        // knows about. It is a separate setting, and it needs no mastership.
        bool predicting = robot.Rws.MotionSystem.GetCollisionPredictionMode();

        if (!predicting)
        {
            // Refused when the collision detection option is not installed.
            // The error message then names the missing option.
            robot.Rws.MotionSystem.SetCollisionPredictionMode(true);
        }
        /**/

        robot.Disconnect();
    }
}
```

**Python : MotionCollisionDetection**
```python
from underautomation.abb.abb_controller import AbbController
from underautomation.abb.rws.data.mastership_domain import MastershipDomain

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

##
# Collision detection while the unit is jogged
jogging = robot.rws.motion_system.get_motion_supervision("ROB_1")
print(f"jogging supervision {jogging.enabled}, sensitivity {jogging.level} %")

# Collision detection while the unit follows a programmed path
path = robot.rws.motion_system.get_path_supervision("ROB_1")
print(f"path supervision {path.enabled}, sensitivity {path.level} %")
##

##
# Writing these four values needs the mastership of the motion domain.
# The lower the percentage, the sooner the controller reports a collision.
robot.rws.mastership.request(MastershipDomain.Motion)

try:
    robot.rws.motion_system.set_motion_supervision_mode("ROB_1", True)
    robot.rws.motion_system.set_motion_supervision_level("ROB_1", 80)

    robot.rws.motion_system.set_path_supervision_mode("ROB_1", True)
    robot.rws.motion_system.set_path_supervision_level("ROB_1", 80)
finally:
    robot.rws.mastership.release(MastershipDomain.Motion)
##

##
# Collision prediction stops the robot before it hits something the controller
# knows about. It is a separate setting, and it needs no mastership.
predicting = robot.rws.motion_system.get_collision_prediction_mode()

if not predicting:
    # Refused when the collision detection option is not installed.
    # The error message then names the missing option.
    robot.rws.motion_system.set_collision_prediction_mode(True)
##

robot.disconnect()
```

| Setting             | Applies while                      |
| ------------------- | ---------------------------------- |
| `MotionSupervision` | The unit is jogged                 |
| `PathSupervision`   | The unit follows a programmed path |

The level is a percentage. The lower the value, the sooner the controller reports a collision. The four write methods need the mastership of the `Motion` domain.

Collision prediction is a different feature: it stops the robot before it hits something the controller already knows about, where the supervision only reacts once the arm meets a resistance. `GetCollisionPredictionMode` and `SetCollisionPredictionMode` need no mastership.

All of this belongs to the Collision Detection option. On a controller built without it, switching the feature on is refused with the HTTP status code 403 and the SDK reports an error naming the missing option. Writing back the value the controller already holds is accepted.

`SetNonMotionExecutionMode` is nearby but different: it runs the RAPID program while skipping every motion instruction, which is how a program is tested without the robot leaving its position. It belongs to the editing domain, not to the motion one, so take every domain before calling it.

**Methods of MotionSystemService**
```csharp
// Tells whether the controller predicts collisions before they happen (synchronous) Collision prediction stops the robot before it hits something it knows about, where the motion supervision only reacts once the arm meets an unexpected resistance.
bool GetCollisionPredictionMode();

// Gets the collision detection settings that apply while a mechanical unit is jogged (synchronous)
MotionSupervision GetMotionSupervision(string mechanicalUnit);

// Tells whether the controller runs RAPID programs without moving the robot (synchronous) In that mode the program executes normally but every motion instruction is skipped, which is how a program is tested without the robot leaving its position.
bool GetNonMotionExecutionMode();

// Gets the collision detection settings that apply while a mechanical unit follows a programmed path (synchronous)
PathSupervision GetPathSupervision(string mechanicalUnit);

// Switches collision prediction on or off (synchronous)
void SetCollisionPredictionMode(bool enabled);

// Sets how sensitive the jogging collision detection of a mechanical unit is (synchronous)
void SetMotionSupervisionLevel(string mechanicalUnit, int sensitivity);

// Switches the jogging collision detection of a mechanical unit on or off (synchronous)
void SetMotionSupervisionMode(string mechanicalUnit, bool enabled);

// Chooses whether the controller runs RAPID programs without moving the robot (synchronous)
void SetNonMotionExecutionMode(bool enabled);

// Sets how sensitive the path collision detection of a mechanical unit is (synchronous)
void SetPathSupervisionLevel(string mechanicalUnit, int level);

// Switches the path collision detection of a mechanical unit on or off (synchronous)
void SetPathSupervisionMode(string mechanicalUnit, bool enabled);
```

Every method also exists in an asynchronous version, with the same name followed by `Async` and an optional `CancellationToken`.

**Members of Rws.Data.MotionSupervision**
```csharp
public class MotionSupervision {
    // Initializes a new instance of the <xref href="UnderAutomation.ABB.Rws.Data.MotionSupervision" data-throw-if-not-resolved="false"></xref> class
    public MotionSupervision()

    // Whether the supervision is switched on, null when the controller did not report it
    public bool? Enabled { get; set; }

    // Sensitivity of the supervision, as a percentage: the lower the value, the sooner a collision is
    // reported. Null when the controller did not report it.
    public int? Level { get; set; }

    // Returns a string representation of these settings
    public override string ToString()
}
```

**Members of Rws.Data.PathSupervision**
```csharp
public class PathSupervision {
    // Initializes a new instance of the <xref href="UnderAutomation.ABB.Rws.Data.PathSupervision" data-throw-if-not-resolved="false"></xref> class
    public PathSupervision()

    // Whether the supervision is switched on, null when the controller did not report it
    public bool? Enabled { get; set; }

    // Sensitivity of the supervision, as a percentage: the lower the value, the sooner a collision is
    // reported. Null when the controller did not report it.
    public int? Level { get; set; }

    // Returns a string representation of these settings
    public override string ToString()
}
```

## Lead through

> **Available on** RWS 1.0 (IRC5) : yes | RWS 2.0 (OmniCore) : yes.

Lead through releases the arm so an operator can push it around by hand. The motors have to be on and the robot has to support the feature. This is one of the two writes of the service that need no mastership.

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

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

        /**/
        // Is the arm free to be pushed by hand right now
        LeadThroughStatus status = robot.Rws.MotionSystem.GetLeadThrough("ROB_1");
        Console.WriteLine(status);

        // Switching it on releases the arm: the motors have to be on, and the robot
        // has to support lead through. This call needs no mastership.
        robot.Rws.MotionSystem.SetLeadThrough("ROB_1", true);

        // Switching it off makes the arm hold its position again
        robot.Rws.MotionSystem.SetLeadThrough("ROB_1", false);
        /**/

        robot.Disconnect();
    }
}
```

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

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

##
# Is the arm free to be pushed by hand right now
status = robot.rws.motion_system.get_lead_through("ROB_1")
print(status)

# Switching it on releases the arm: the motors have to be on, and the robot
# has to support lead through. This call needs no mastership.
robot.rws.motion_system.set_lead_through("ROB_1", True)

# Switching it off makes the arm hold its position again
robot.rws.motion_system.set_lead_through("ROB_1", False)
##

robot.disconnect()
```

`GetLeadThrough` returns `Active` when the arm gives way when pushed, and `Inactive` when it holds its position.

**Methods of MotionSystemService**
```csharp
// Tells whether an operator can push the arm of a mechanical unit around by hand (synchronous)
LeadThroughStatus GetLeadThrough(string mechanicalUnit);

// Lets an operator push the arm of a mechanical unit around by hand, or stops letting them (synchronous)
void SetLeadThrough(string mechanicalUnit, bool active);
```

Every method also exists in an asynchronous version, with the same name followed by `Async` and an optional `CancellationToken`.

**Members of Rws.Data.LeadThroughStatus**
```csharp
public enum LeadThroughStatus {
    // The arm gives way when pushed
    Active = 1

    // The arm holds its position
    Inactive = 2

    // The controller reported a state this library does not know
    Unknown = 0
}
```

## Calibration

> **Available on** RWS 1.0 (IRC5) : yes | RWS 2.0 (OmniCore) : yes.

The calibration says where each axis really is. Reading it is safe and tells you why a unit reports something else than `Synchronized`.

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

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

        /**/
        // How each joint of the unit was calibrated
        CalibrationInfo calibration = robot.Rws.MotionSystem.GetCalibrationInfo("ROB_1");
        Console.WriteLine($"method {calibration.CalibrationMethodUsed}, {calibration.ExistingJointCount} joint(s)");

        // The controller always answers with more joint slots than the unit has.
        // The extra ones carry no name and are marked as not existing.
        foreach (CalibrationJointInfo joint in calibration.Joints)
        {
            if (!joint.Exists) continue;

            Console.WriteLine($"{joint.JointName} : factory {joint.FactoryCalibrationMethod}, now {joint.CurrentCalibrationMethod}");
        }

        // The name each joint carries, and the name of its calibration data
        foreach (MotorCalibrationName name in robot.Rws.MotionSystem.GetMotorCalibrationNames("ROB_1"))
        {
            Console.WriteLine($"joint {name.Number} : {name.JointName}, data {name.CalibrationName}");
        }
        /**/

        /**/
        // The calibration is stored twice: in the controller cabinet and in the robot itself.
        // The two copies are meant to agree.
        SmbData smb = robot.Rws.MotionSystem.GetSmbData("ROB_1");

        Console.WriteLine($"cabinet calibration {smb.CabinetCalibrationStatus}");
        Console.WriteLine($"robot calibration   {smb.RobotCalibrationStatus}");

        if (smb.CabinetCalibrationStatus == SmbDataStatus.ValidNotEqual)
        {
            Console.WriteLine("the two copies do not hold the same data");
        }
        /**/

        robot.Disconnect();
    }
}
```

**Python : CalibrationRead**
```python
from underautomation.abb.abb_controller import AbbController
from underautomation.abb.rws.data.smb_data_status import SmbDataStatus

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

##
# How each joint of the unit was calibrated
calibration = robot.rws.motion_system.get_calibration_info("ROB_1")
print(f"method {calibration.calibration_method_used}, {calibration.existing_joint_count} joints")

# The controller always answers with more joint slots than the unit has.
# The extra ones carry no name and are marked as not existing.
for joint in calibration.joints:
    if not joint.exists:
        continue

    print(f"{joint.joint_name} : factory {joint.factory_calibration_method}, now {joint.current_calibration_method}")

# The name each joint carries, and the name of its calibration data
for name in robot.rws.motion_system.get_motor_calibration_names("ROB_1"):
    print(f"joint {name.number} : {name.joint_name}, data {name.calibration_name}")
##

##
# The calibration is stored twice: in the controller cabinet and in the robot itself.
# The two copies are meant to agree.
smb = robot.rws.motion_system.get_smb_data("ROB_1")

print(f"cabinet calibration {smb.cabinet_calibration_status}")
print(f"robot calibration   {smb.robot_calibration_status}")

if smb.cabinet_calibration_status == SmbDataStatus.ValidNotEqual:
    print("the two copies do not hold the same data")
##

robot.disconnect()
```

`GetCalibrationInfo` always answers with a fixed number of joint slots, larger than the number of joints the unit really has. The extra ones carry no name and have `Exists` set to false. `ExistingJointCount` counts the real ones.

The calibration data is stored twice, once in the controller cabinet and once in the robot itself. `GetSmbData` returns both copies with a status per block of data. `ValidNotEqual` means the two copies are present but do not agree.

The write operations replace the calibration of an axis. RWS offers no way to put the previous one back, so a wrong calibration leaves the robot moving to the wrong place until somebody recalibrates it from the FlexPendant. They need the mastership of the `Motion` domain, and the measurement board operations also need the controller in manual mode with this client as its local client.

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

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

        /**/
        // These four operations replace the calibration of an axis. RWS has no way to
        // put the previous one back, so read the state first and be sure of the position
        // the axis is standing in.
        MechanicalUnitInfo unit = robot.Rws.MotionSystem.GetMechanicalUnit("ROB_1");

        if (unit.Status != MechanicalUnitStatus.Synchronized)
        {
            Console.WriteLine($"ROB_1 is {unit.Status}");
        }

        robot.Rws.Mastership.Request(MastershipDomain.Motion);

        try
        {
            // Teaches the controller how the rotor of the motor is oriented.
            // Needed once after a motor has been replaced.
            robot.Rws.MotionSystem.Commutate("ROB_1", 1);

            // Move the axis to its synchronization mark first: the controller stores
            // the position the axis is in right now.
            robot.Rws.MotionSystem.SynchronizeAxisRevolutionCounter("ROB_1", 1);
            robot.Rws.MotionSystem.UpdateRevolutionCounter("ROB_1", 1);

            // Takes the current position of the axis as its new calibration position
            robot.Rws.MotionSystem.FineCalibrate("ROB_1", 1);
        }
        finally
        {
            robot.Rws.Mastership.Release();
        }
        /**/

        /**/
        // Copy one of the two calibration data stores over the other. The overwritten
        // one is gone, so read both first and keep the good one.
        robot.Rws.MotionSystem.SetSmbData("ROB_1", SmbDataTransfer.RobotToController);

        // Erase one of them. The controller cannot recover it.
        robot.Rws.MotionSystem.ClearSmbData("ROB_1", SmbDataMemory.Controller);
        /**/

        robot.Disconnect();
    }
}
```

**Python : CalibrationAxis**
```python
from underautomation.abb.abb_controller import AbbController
from underautomation.abb.rws.data.mastership_domain import MastershipDomain
from underautomation.abb.rws.data.mechanical_unit_status import MechanicalUnitStatus
from underautomation.abb.rws.data.smb_data_memory import SmbDataMemory
from underautomation.abb.rws.data.smb_data_transfer import SmbDataTransfer

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

##
# These four operations replace the calibration of an axis. RWS has no way to
# put the previous one back, so read the state first and be sure of the position
# the axis is standing in.
unit = robot.rws.motion_system.get_mechanical_unit("ROB_1")

if unit.status != MechanicalUnitStatus.Synchronized:
    print(f"ROB_1 is {unit.status}")

robot.rws.mastership.request(MastershipDomain.Motion)

try:
    # Teaches the controller how the rotor of the motor is oriented.
    # Needed once after a motor has been replaced.
    robot.rws.motion_system.commutate("ROB_1", 1)

    # Move the axis to its synchronization mark first: the controller stores
    # the position the axis is in right now.
    robot.rws.motion_system.synchronize_axis_revolution_counter("ROB_1", 1)
    robot.rws.motion_system.update_revolution_counter("ROB_1", 1)

    # Takes the current position of the axis as its new calibration position
    robot.rws.motion_system.fine_calibrate("ROB_1", 1)
finally:
    robot.rws.mastership.release(MastershipDomain.Motion)
##

##
# Copy one of the two calibration data stores over the other. The overwritten
# one is gone, so read both first and keep the good one.
robot.rws.motion_system.set_smb_data("ROB_1", SmbDataTransfer.RobotToController)

# Erase one of them. The controller cannot recover it.
robot.rws.motion_system.clear_smb_data("ROB_1", SmbDataMemory.Controller)
##

robot.disconnect()
```

| Method                             | Effect                                                                                                      |
| ---------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `Commutate`                        | Teaches the controller how the rotor of the motor is oriented. Needed once after a motor has been replaced. |
| `SynchronizeAxisRevolutionCounter` | Tells the controller the axis stands at its synchronization mark                                            |
| `UpdateRevolutionCounter`          | Updates the revolution counter of the axis                                                                  |
| `FineCalibrate`                    | Takes the current position of the axis as its new calibration position                                      |
| `SetSmbData`                       | Copies one of the two data stores over the other                                                            |
| `ClearSmbData`                     | Erases one of the two data stores                                                                           |

For the three operations that store a position, move the axis to its synchronization mark first. The controller stores the position the axis is in at the moment of the call.

**Methods of MotionSystemService**
```csharp
// Erases one of the two serial measurement board data stores (synchronous)
void ClearSmbData(string mechanicalUnit, SmbDataMemory memory);

// Commutates the motor of one axis, which teaches the controller how the rotor of that motor is oriented (synchronous) Needed once after a motor has been replaced, before the axis can be calibrated.
void Commutate(string mechanicalUnit, int axis);

// Fine calibrates one axis of a mechanical unit (synchronous)
void FineCalibrate(string mechanicalUnit, int axis);

// Gets how each joint of a mechanical unit was calibrated (synchronous)
CalibrationInfo GetCalibrationInfo(string mechanicalUnit);

// Gets the name each joint of a mechanical unit carries, and the name of its calibration data (synchronous)
MotorCalibrationName[] GetMotorCalibrationNames(string mechanicalUnit);

// Gets the serial measurement board data of a mechanical unit, as held by the controller cabinet and by the robot itself (synchronous) The two copies are meant to agree. When they do not, one of them is written over the other with Data.SmbDataTransfer).
SmbData GetSmbData(string mechanicalUnit);

// Copies one of the two serial measurement board data stores over the other (synchronous)
void SetSmbData(string mechanicalUnit, SmbDataTransfer direction);

// Synchronizes the revolution counter of one axis, telling the controller that the axis stands at its synchronization mark (synchronous)
void SynchronizeAxisRevolutionCounter(string mechanicalUnit, int axis);

// Updates the revolution counter of one axis of a mechanical unit (synchronous)
void UpdateRevolutionCounter(string mechanicalUnit, int axis);
```

Every method also exists in an asynchronous version, with the same name followed by `Async` and an optional `CancellationToken`.

**Members of Rws.Data.CalibrationInfo**
```csharp
public class CalibrationInfo {
    // Initializes a new instance of the <xref href="UnderAutomation.ABB.Rws.Data.CalibrationInfo" data-throw-if-not-resolved="false"></xref> class
    public CalibrationInfo()

    // Number of joints of the unit that are in use, null when the controller did not report it
    public int? ActiveJointCount { get; set; }

    // Name of the calibration method the unit was last calibrated with, for example "AxisCalibration"
    public string CalibrationMethodUsed { get; set; }

    // Kind of calibration window the controller offers for this unit,
    // null when the controller did not report it
    public int? CalibrationWindowType { get; set; }

    // Number of joints that exist on the unit, counted from <xref href="UnderAutomation.ABB.Rws.Data.CalibrationInfo.Joints" data-throw-if-not-resolved="false"></xref>
    public int ExistingJointCount { get; }

    // Number of entries in <xref href="UnderAutomation.ABB.Rws.Data.CalibrationInfo.Joints" data-throw-if-not-resolved="false"></xref>, which is fixed and larger than
    // <xref href="UnderAutomation.ABB.Rws.Data.CalibrationInfo.ActiveJointCount" data-throw-if-not-resolved="false"></xref>. Null when the controller did not report it.
    public int? JointCount { get; set; }

    // One entry per joint slot of the unit, the unused ones marked as such. Never null.
    public CalibrationJointInfo[] Joints { get; set; }

    // Returns a string representation of this calibration
    public override string ToString()
}
```

**Members of Rws.Data.CalibrationJointInfo**
```csharp
public class CalibrationJointInfo {
    // Initializes a new instance of the <xref href="UnderAutomation.ABB.Rws.Data.CalibrationJointInfo" data-throw-if-not-resolved="false"></xref> class
    public CalibrationJointInfo()

    // Method the joint is currently calibrated with
    public string CurrentCalibrationMethod { get; set; }

    // Whether the joint exists on this mechanical unit. The controller always answers with a fixed
    // number of entries and marks the unused ones, which carry no name at all.
    public bool Exists { get; set; }

    // Method the joint was calibrated with in the factory
    public string FactoryCalibrationMethod { get; set; }

    // Name of the joint, for example "rob1_1", empty for an entry that does not exist
    public string JointName { get; set; }

    // Returns a string representation of this joint
    public override string ToString()
}
```

**Members of Rws.Data.MotorCalibrationName**
```csharp
public class MotorCalibrationName {
    // Initializes a new instance of the <xref href="UnderAutomation.ABB.Rws.Data.MotorCalibrationName" data-throw-if-not-resolved="false"></xref> class
    public MotorCalibrationName()

    // Name of the calibration data of the joint, usually the same as <xref href="UnderAutomation.ABB.Rws.Data.MotorCalibrationName.JointName" data-throw-if-not-resolved="false"></xref>
    public string CalibrationName { get; set; }

    // Name of the joint, for example "rob1_1"
    public string JointName { get; set; }

    // Number of the joint inside its mechanical unit, starting at 1
    public int Number { get; set; }

    // Returns a string representation of these names
    public override string ToString()
}
```

**Members of Rws.Data.SmbData**
```csharp
public class SmbData {
    // Initializes a new instance of the <xref href="UnderAutomation.ABB.Rws.Data.SmbData" data-throw-if-not-resolved="false"></xref> class
    public SmbData()

    // State of the absolute accuracy data stored in the cabinet
    public SmbDataStatus CabinetAbsoluteAccuracyStatus { get; set; }

    // State of the axis calibration data stored in the cabinet
    public SmbDataStatus CabinetAxisCalibrationStatus { get; set; }

    // State of the calibration data stored in the cabinet
    public SmbDataStatus CabinetCalibrationStatus { get; set; }

    // High part of the serial number stored in the cabinet
    public string CabinetSerialNumberHighPart { get; set; }

    // Low part of the serial number stored in the cabinet
    public string CabinetSerialNumberLowPart { get; set; }

    // Whether the serial number stored in the cabinet is usable, null when the controller did not report it
    public bool? CabinetSerialNumberValid { get; set; }

    // State of the service information data stored in the cabinet
    public SmbDataStatus CabinetServiceInformationStatus { get; set; }

    // Number of the drive module the data belongs to, null when the controller did not report it
    public int? DriveModule { get; set; }

    // Number of the measurement board the data belongs to, null when the controller did not report it
    public int? MeasurementBoard { get; set; }

    // Number of the measurement link the data belongs to, null when the controller did not report it
    public int? MeasurementLink { get; set; }

    // State of the absolute accuracy data stored in the robot
    public SmbDataStatus RobotAbsoluteAccuracyStatus { get; set; }

    // State of the axis calibration data stored in the robot
    public SmbDataStatus RobotAxisCalibrationStatus { get; set; }

    // State of the calibration data stored in the robot
    public SmbDataStatus RobotCalibrationStatus { get; set; }

    // High part of the serial number stored in the robot
    public string RobotSerialNumberHighPart { get; set; }

    // Low part of the serial number stored in the robot
    public string RobotSerialNumberLowPart { get; set; }

    // Whether the serial number stored in the robot is usable, null when the controller did not report it
    public bool? RobotSerialNumberValid { get; set; }

    // State of the service information data stored in the robot
    public SmbDataStatus RobotServiceInformationStatus { get; set; }

    // Returns a string representation of this data
    public override string ToString()
}
```

**Members of Rws.Data.SmbDataStatus**
```csharp
public enum SmbDataStatus {
    // The robot system does not use this block of data
    NotUsed = 4

    // The data is missing or unusable
    NotValid = 3

    // The controller reported a state this library does not know
    Unknown = 0

    // The data is present and the two copies agree
    Valid = 1

    // The data is present on both sides, but the two copies differ
    ValidNotEqual = 2
}
```

**Members of Rws.Data.SmbDataTransfer**
```csharp
public enum SmbDataTransfer {
    // The copy held by the controller cabinet is written into the robot
    ControllerToRobot = 1

    // The copy held by the robot is written into the controller cabinet
    RobotToController = 0
}
```

**Members of Rws.Data.SmbDataMemory**
```csharp
public enum SmbDataMemory {
    // The copy held by the controller cabinet
    Controller = 1

    // The copy held by the robot itself
    Robot = 0
}
```

## State of the motion system

> **Available on** RWS 1.0 (IRC5) : yes | RWS 2.0 (OmniCore) : yes.

`GetInfo` gives the overview: which unit the jogging commands apply to, whether absolute accuracy is on, and the change count.

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

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

        /**/
        // Overview of the motion system: which unit the jogging commands apply to,
        // and the counter the controller increments on every change
        MotionSystemInfo info = robot.Rws.MotionSystem.GetInfo();
        Console.WriteLine($"jogging applies to {info.MechanicalUnitName}");
        Console.WriteLine($"change count {info.ChangeCount}, absolute accuracy {info.AbsoluteAccuracyActive}");

        // Ask whether anything moved since that reading, instead of reading everything again
        if (info.ChangeCount.HasValue && robot.Rws.MotionSystem.HasChanged(info.ChangeCount.Value))
        {
            Console.WriteLine("the motion system changed");
        }
        /**/

        /**/
        // The last error the motion system ran into. Most of them come from a jogging
        // request the controller accepted and could not honour.
        MotionSystemErrorState error = robot.Rws.MotionSystem.GetErrorState();

        if (error.State != MotionErrorState.Ok)
        {
            Console.WriteLine($"{error.State} ({error.RawState}), {error.Count} error(s)");
        }
        /**/

        /**/
        // Run the RAPID program without moving the robot. The instructions execute,
        // the motion ones are skipped.
        bool skipped = robot.Rws.MotionSystem.GetNonMotionExecutionMode();

        robot.Rws.Mastership.Request();

        try
        {
            robot.Rws.MotionSystem.SetNonMotionExecutionMode(true);
        }
        finally
        {
            robot.Rws.Mastership.Release();
        }
        /**/

        robot.Disconnect();
    }
}
```

**Python : MotionState**
```python
from underautomation.abb.abb_controller import AbbController
from underautomation.abb.rws.data.motion_error_state import MotionErrorState

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

##
# Overview of the motion system: which unit the jogging commands apply to,
# and the counter the controller increments on every change
info = robot.rws.motion_system.get_info()
print(f"jogging applies to {info.mechanical_unit_name}")
print(f"change count {info.change_count}, absolute accuracy {info.absolute_accuracy_active}")

# Ask whether anything moved since that reading, instead of reading everything again
if info.change_count is not None and robot.rws.motion_system.has_changed(info.change_count):
    print("the motion system changed")
##

##
# The last error the motion system ran into. Most of them come from a jogging
# request the controller accepted and could not honour.
error = robot.rws.motion_system.get_error_state()

if error.state != MotionErrorState.Ok:
    print(f"{error.state} ({error.raw_state}), {error.count} errors")
##

##
# Run the RAPID program without moving the robot. The instructions execute,
# the motion ones are skipped.
skipped = robot.rws.motion_system.get_non_motion_execution_mode()

# The motion domain alone is not enough here, the controller answers 403.
# In Python the domains are taken one by one, so take the ones it exposes.
domains = robot.rws.mastership.get_domains()

for domain in domains:
    robot.rws.mastership.request(domain)

try:
    robot.rws.motion_system.set_non_motion_execution_mode(True)
finally:
    for domain in domains:
        robot.rws.mastership.release(domain)
##

robot.disconnect()
```

The change count is a counter the controller increments on every change of the motion system. Reading it once and asking `HasChanged` afterwards is cheaper than fetching the whole state again to find out that nothing moved. Only pass a count a previous reading gave: the controller does not track how two counts relate, so a count it never reported comes back as changed.

`GetErrorState` returns the last error the motion system ran into and how many it has counted. Most of them come from a jogging request the controller accepted and could not honour, and they stay reported until a new one replaces them.

| `MotionErrorState`                 | Meaning                                                                              |
| ---------------------------------- | ------------------------------------------------------------------------------------ |
| `Ok`                               | No error                                                                             |
| `MechanicalUnitNotActive`          | A mechanical unit was jogged whose activation failed                                 |
| `UncalibratedJogMotionType`        | An uncalibrated robot was jogged in a mode that needs its calibration                |
| `UnnormalizedQuaternion`           | A tool, a load or a work object carries an orientation that is not normalized        |
| `ErroneousToolMass`                | A load definition carries a negative mass                                            |
| `RobotHoldMismatch`                | The tool and the work object disagree on which one the robot holds                   |
| `WorkObjectMechanicalUnitNotFound` | A unit used in coordinated jogging was not found                                     |
| `InvalidJogMotionType`             | The requested jogging mode is not valid                                              |
| `Unknown`                          | The controller reported an error the library does not know, `RawState` then holds it |

**Methods of MotionSystemService**
```csharp
// Gets the last error the motion system ran into, and how many errors it has counted (synchronous) Most of these errors are raised by a jogging request the controller could not honour, and stay reported until a new one replaces them.
MotionSystemErrorState GetErrorState();

// Gets an overview of the motion system: the mechanical unit jogging applies to, the change counter and the payload and accuracy settings (synchronous)
MotionSystemInfo GetInfo();

// Tells whether the motion system changed since it reported the given change count (synchronous) Reading MotionSystemInfo.ChangeCount once and asking this afterwards is cheaper than fetching the whole state again to find out that nothing moved.
bool HasChanged(int changeCount);
```

Every method also exists in an asynchronous version, with the same name followed by `Async` and an optional `CancellationToken`.

**Members of Rws.Data.MotionSystemInfo**
```csharp
public class MotionSystemInfo {
    // Initializes a new instance of the <xref href="UnderAutomation.ABB.Rws.Data.MotionSystemInfo" data-throw-if-not-resolved="false"></xref> class
    public MotionSystemInfo()

    // Whether absolute accuracy is switched on, null when the controller did not report it
    public bool? AbsoluteAccuracyActive { get; set; }

    // Counter the controller increments on every change of the motion system.
    // 
    // <p>Pass it to <code>MotionSystemService.HasChanged()</code> to find out whether anything moved
    // since a previous reading, without fetching the whole state again.</p>
    public int? ChangeCount { get; set; }

    // Name of the mechanical unit the jogging commands currently apply to
    public string MechanicalUnitName { get; set; }

    // Whether the payload of the robot is set by the running program rather than by the mechanical unit,
    // null when the controller did not report it
    public bool? ModalPayloadMode { get; set; }

    // Rate at which the controller refreshes the motion system state, null when it did not report it
    public int? PollRate { get; set; }

    // Returns a string representation of this motion system
    public override string ToString()
}
```

**Members of Rws.Data.MotionSystemErrorState**
```csharp
public class MotionSystemErrorState {
    // Initializes a new instance of the <xref href="UnderAutomation.ABB.Rws.Data.MotionSystemErrorState" data-throw-if-not-resolved="false"></xref> class
    public MotionSystemErrorState()

    // Number of errors counted since the controller started, incremented on every new error,
    // null when the controller did not report it
    public int? Count { get; set; }

    // Error state exactly as the controller reported it, useful when <xref href="UnderAutomation.ABB.Rws.Data.MotionSystemErrorState.State" data-throw-if-not-resolved="false"></xref> is
    // <xref href="UnderAutomation.ABB.Rws.Data.MotionErrorState.Unknown" data-throw-if-not-resolved="false"></xref>
    public string RawState { get; set; }

    // Last error the motion system ran into
    public MotionErrorState State { get; set; }

    // Returns a string representation of this error state
    public override string ToString()
}
```

**Members of Rws.Data.MotionErrorState**
```csharp
public enum MotionErrorState {
    // A load definition carries a negative mass
    ErroneousToolMass = 5

    // The requested jogging mode is not valid
    InvalidJogMotionType = 8

    // A mechanical unit was jogged whose activation failed
    MechanicalUnitNotActive = 2

    // No error
    Ok = 1

    // The tool and the work object disagree on which one the robot holds
    RobotHoldMismatch = 6

    // An uncalibrated robot was jogged in a mode that needs its calibration
    UncalibratedJogMotionType = 3

    // The controller reported an error this library does not know
    Unknown = 0

    // A quaternion that is not normalized reached the jogging task, from a tool, a load or a work object
    UnnormalizedQuaternion = 4

    // A mechanical unit used in coordinated jogging was not found
    WorkObjectMechanicalUnitNotFound = 7
}
```

## Try it in the demo application

Everything on this page can be tried without writing code, in the **Motion System (RWS)** page of the [demo application](/abb/documentation/demo-app).

![Motion System (RWS) page of the ABB SDK demo application](/abb/WinformsScreenshots/RwsMotionSystem.jpg)

Its C# source is [RwsMotionSystemControl.cs](https://github.com/underautomation/ABB.NET/blob/main/UnderAutomation.ABB.Showcase.Forms/Components/RwsMotionSystemControl.cs).