`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                      |

















## 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();
    }
}
```

`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.





















## 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();
    }
}
```

`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.



## 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`.









## 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();
    }
}
```



## 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();
    }
}
```

`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.





## 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();
    }
}
```

| 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`.





## 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();
    }
}
```

| 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.







## 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();
    }
}
```

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





## 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();
    }
}
```

`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();
    }
}
```

| 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.

















## 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();
    }
}
```

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 |