`robot.Rws.Panel` is the control panel of the controller. It reads and changes what an operator sees on the teach pendant: the operating mode, the state of the motors, the speed the programs run at, and the collision detection.

## Operating mode

`GetOperationMode` returns the mode currently selected on the controller. The mode decides what a remote client is allowed to do. A program is normally started from the network in automatic mode only.

| `OperationMode`                | Meaning                                                           |
| ------------------------------ | ----------------------------------------------------------------- |
| `Automatic`                    | Automatic mode, the program runs without an operator in the cell  |
| `ManualReducedSpeed`           | Manual mode, the operator holds the enabling device               |
| `ManualFullSpeed`              | Manual mode at full speed                                         |
| `AutomaticChangeRequest`       | The change to automatic mode is waiting for an acknowledgement    |
| `ManualFullSpeedChangeRequest` | The change to manual full speed is waiting for an acknowledgement |
| `Init`                         | The controller is starting                                        |
| `Undefined`, `Unknown`         | The controller reports no mode, or one this library does not know |

The mode itself is selected with the key on the cabinet, an application cannot change it. What the SDK can do is confirm a change the operator has already started, with `AcknowledgeOperationMode`.

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

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

        /**/
        OperationMode mode = robot.Rws.Panel.GetOperationMode();

        switch (mode)
        {
            case OperationMode.Automatic:
                Console.WriteLine("Automatic mode, a program can be started remotely");
                break;

            case OperationMode.ManualReducedSpeed:
            case OperationMode.ManualFullSpeed:
                Console.WriteLine("Manual mode, the operator holds the enabling device");
                break;

            case OperationMode.AutomaticChangeRequest:
                // The key was turned to automatic, the change waits for a confirmation
                robot.Rws.Panel.AcknowledgeOperationMode(OperationModeAcknowledgement.Automatic);
                break;

            case OperationMode.ManualFullSpeedChangeRequest:
                robot.Rws.Panel.AcknowledgeOperationMode(OperationModeAcknowledgement.ManualFullSpeed);
                break;
        }
        /**/

        robot.Disconnect();
    }
}
```

**Python : PanelOperationMode**
```python
from underautomation.abb.abb_controller import AbbController
from underautomation.abb.rws.data.operation_mode import OperationMode
from underautomation.abb.rws.data.operation_mode_acknowledgement import OperationModeAcknowledgement

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

##
mode = robot.rws.panel.get_operation_mode()

if mode == OperationMode.Automatic:
    print("Automatic mode, a program can be started remotely")
elif mode == OperationMode.ManualReducedSpeed or mode == OperationMode.ManualFullSpeed:
    print("Manual mode, the operator holds the enabling device")
elif mode == OperationMode.AutomaticChangeRequest:
    # The key was turned to automatic, the change waits for a confirmation
    robot.rws.panel.acknowledge_operation_mode(OperationModeAcknowledgement.Automatic)
elif mode == OperationMode.ManualFullSpeedChangeRequest:
    robot.rws.panel.acknowledge_operation_mode(OperationModeAcknowledgement.ManualFullSpeed)
##

robot.disconnect()
```

The controller refuses the acknowledgement when no change is waiting for one, and when the connected client is not the local client of the controller. Both cases give an `RwsException`.

`OperationModeAcknowledgement.CollisionDetection` uses the same method to confirm a collision, see below.

## Mode selector lock

> **Available on** RWS 1.0 (IRC5) : yes | RWS 2.0 (OmniCore) : yes. The controller needs the key-less mode selector option

The mode selector can be locked with a pin code, so that the mode cannot be changed until the same code is given back.

| `OperationModeLockState` | Meaning                                                                            |
| ------------------------ | ---------------------------------------------------------------------------------- |
| `Unlocked`               | The operating mode can be changed freely                                           |
| `Locked`                 | Locked, `UnlockOperationMode` releases it with the pin code it was locked with     |
| `PermanentlyLocked`      | Locked for good, the pin code no longer releases it                                |
| `PendingPermanentLock`   | A permanent lock was asked for and is not effective yet                            |
| `Error`, `Unknown`       | The controller reports an error on the lock, or a state this library does not know |

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

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

        /**/
        OperationModeLockState lockState = robot.Rws.Panel.GetOperationModeLockState();
        Console.WriteLine($"Mode selector : {lockState}");

        if (lockState == OperationModeLockState.Unlocked)
        {
            // Four digits, needed again to unlock the selector
            robot.Rws.Panel.LockOperationMode("1234");
        }
        else if (lockState == OperationModeLockState.Locked)
        {
            robot.Rws.Panel.UnlockOperationMode("1234");
        }
        /**/

        /**/
        // A permanent lock cannot be released with UnlockOperationMode
        robot.Rws.Panel.LockOperationMode("1234", true);
        /**/

        robot.Disconnect();
    }
}
```

**Python : PanelModeLock**
```python
from underautomation.abb.abb_controller import AbbController
from underautomation.abb.rws.data.operation_mode_lock_state import OperationModeLockState

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

##
lock_state = robot.rws.panel.get_operation_mode_lock_state()
print(f"Mode selector : {lock_state}")

if lock_state == OperationModeLockState.Unlocked:
    # Four digits, needed again to unlock the selector
    robot.rws.panel.lock_operation_mode("1234")
elif lock_state == OperationModeLockState.Locked:
    robot.rws.panel.unlock_operation_mode("1234")
##

##
# A permanent lock cannot be released with unlock_operation_mode
robot.rws.panel.lock_operation_mode("1234", True)
##

robot.disconnect()
```

The pin code is exactly four digits, the SDK throws an `ArgumentException` for anything else. The permanent lock needs the grant to lock the safety controller configuration on the user account.

A controller built without the key-less mode selector option answers 403 on the three lock methods, including the read. The SDK catches that case and explains it in the message of the `RwsException`, because the controller error only says that an option is missing.

## Motors on and off

`GetControllerState` reports whether the robot can move. `SetControllerState` accepts `MotorsOn` and `MotorsOff` only, the other values are read only states of the controller and passing one throws an `ArgumentException`.

| `ControllerState`    | Meaning                                                              |
| -------------------- | -------------------------------------------------------------------- |
| `MotorsOn`           | The robot is ready to move, by jogging or by running a program       |
| `MotorsOff`          | Standby, there is no power on the motors                             |
| `Init`               | The controller is starting, it goes to `MotorsOff` when it is up     |
| `GuardStop`          | Stopped because the safety runchain is open, for example a cell door |
| `EmergencyStop`      | Stopped by the emergency stop                                        |
| `EmergencyStopReset` | The emergency stop is released, the transition is not confirmed yet  |
| `SystemFailure`      | The controller needs a restart                                       |
| `Unknown`            | The state could not be determined                                    |

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

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

        /**/
        ControllerState state = robot.Rws.Panel.GetControllerState();
        Console.WriteLine($"Controller state : {state}");

        if (state == ControllerState.MotorsOff)
        {
            // The robot can move once the motors are on
            robot.Rws.Panel.SetControllerState(ControllerState.MotorsOn);
        }

        // The state change is not immediate, wait for the controller to report it
        while (robot.Rws.Panel.GetControllerState() != ControllerState.MotorsOn)
        {
            Thread.Sleep(200);
        }
        /**/

        /**/
        // Motors off puts the robot back in standby, it cannot move any more
        robot.Rws.Panel.SetControllerState(ControllerState.MotorsOff);
        /**/

        robot.Disconnect();
    }
}
```

**Python : PanelMotors**
```python
import time

from underautomation.abb.abb_controller import AbbController
from underautomation.abb.rws.data.controller_state import ControllerState

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

##
state = robot.rws.panel.get_controller_state()
print(f"Controller state : {state}")

if state == ControllerState.MotorsOff:
    # The robot can move once the motors are on
    robot.rws.panel.set_controller_state(ControllerState.MotorsOn)

# The state change is not immediate, wait for the controller to report it
while robot.rws.panel.get_controller_state() != ControllerState.MotorsOn:
    time.sleep(0.2)
##

##
# Motors off puts the robot back in standby, it cannot move any more
robot.rws.panel.set_controller_state(ControllerState.MotorsOff)
##

robot.disconnect()
```

The controller answers before the state has really changed. Read the state again until it reports `MotorsOn`, as in the example above, instead of assuming the robot is ready right after the call.

## Speed ratio

The speed ratio is the percentage of their programmed speed the programs run at. `GetSpeedRatio` returns it, `SetSpeedRatio` changes it.

**C# : PanelSpeedRatio**
```csharp
using UnderAutomation.ABB;

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

        /**/
        int speedRatio = robot.Rws.Panel.GetSpeedRatio();
        Console.WriteLine($"The programs run at {speedRatio} % of their programmed speed");

        // Percentage between 0 and 100, accepted in automatic mode only
        robot.Rws.Panel.SetSpeedRatio(25);
        /**/

        /**/
        // Take the mastership yourself when several writes follow each other
        robot.Rws.Mastership.Request();
        robot.Rws.Panel.SetSpeedRatio(100, false);
        robot.Rws.Mastership.Release();
        /**/

        robot.Disconnect();
    }
}
```

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

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

##
speed_ratio = robot.rws.panel.get_speed_ratio()
print(f"The programs run at {speed_ratio} % of their programmed speed")

# Percentage between 0 and 100, accepted in automatic mode only
robot.rws.panel.set_speed_ratio(25)
##

##
# Take the mastership yourself when several writes follow each other.
# The .NET API takes every domain in one call, in Python they are taken one by one.
domains = robot.rws.mastership.get_domains()

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

try:
    robot.rws.panel.set_speed_ratio(100, False)
finally:
    for domain in domains:
        robot.rws.mastership.release(domain)
##

robot.disconnect()
```

The value is between 0 and 100, the controller answers 400 for anything else. The controller accepts the change in automatic mode.

On an OmniCore, writing the speed ratio needs the mastership. The SDK takes it for the time of the request, this is what the `useImplicitMastership` argument does. Set it to `false` when your own code already holds the [mastership](/abb/documentation/rws-mastership), otherwise the controller refuses a second request on a domain you already hold. An IRC5 needs no mastership here and ignores the argument.

## Collision detection

`GetCollisionDetectionState` tells whether the robot stopped because it touched something.

| `CollisionDetectionState` | Meaning                                                 |
| ------------------------- | ------------------------------------------------------- |
| `Init`                    | No collision since the controller started               |
| `Triggered`               | A collision was detected and is waiting to be confirmed |
| `Confirmed`               | A detected collision was confirmed                      |
| `TriggeredAcknowledged`   | A detected collision was acknowledged by an operator    |
| `Unknown`                 | The state could not be determined                       |

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

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

        /**/
        CollisionDetectionState state = robot.Rws.Panel.GetCollisionDetectionState();

        if (state == CollisionDetectionState.Triggered || state == CollisionDetectionState.Confirmed)
        {
            Console.WriteLine("A collision was detected, the robot is stopped");

            // Acknowledge the collision from the application
            robot.Rws.Panel.AcknowledgeOperationMode(OperationModeAcknowledgement.CollisionDetection);
        }
        /**/

        robot.Disconnect();
    }
}
```

**Python : PanelCollisionDetection**
```python
from underautomation.abb.abb_controller import AbbController
from underautomation.abb.rws.data.collision_detection_state import CollisionDetectionState
from underautomation.abb.rws.data.operation_mode_acknowledgement import OperationModeAcknowledgement

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

##
state = robot.rws.panel.get_collision_detection_state()

if state == CollisionDetectionState.Triggered or state == CollisionDetectionState.Confirmed:
    print("A collision was detected, the robot is stopped")

    # Acknowledge the collision from the application
    robot.rws.panel.acknowledge_operation_mode(OperationModeAcknowledgement.CollisionDetection)
##

robot.disconnect()
```

A collision is acknowledged with `AcknowledgeOperationMode`, the same method as an operating mode change, with `OperationModeAcknowledgement.CollisionDetection`.

## Language and restart

`SetLanguage` changes the language the controller writes its messages in, with a two letter code such as `en`, `de` or `sv`. The language must be installed on the controller, otherwise the request fails. The [controller service](/abb/documentation/rws-controller) changes the same setting with its own `SetLanguage`, either one can be used.

`Restart` restarts the controller. The control panel accepts `Restart`, `IStart`, `PStart` and `BStart`. Use `robot.Rws.Controller.Restart` for the other modes, they are described on the [controller page](/abb/documentation/rws-controller).

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

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

        /**/
        // Language the controller reports its messages in.
        // The language must be installed on the controller.
        robot.Rws.Panel.SetLanguage("en");
        /**/

        /**/
        // Warm restart. The connection is lost as soon as the controller shuts down.
        robot.Rws.Panel.Restart(ControllerRestartMode.Restart);
        robot.Disconnect();
        /**/
    }
}
```

**Python : PanelLanguageRestart**
```python
from underautomation.abb.abb_controller import AbbController
from underautomation.abb.rws.data.controller_restart_mode import ControllerRestartMode

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

##
# Language the controller reports its messages in.
# The language must be installed on the controller.
robot.rws.panel.set_language("en")
##

##
# Warm restart. The connection is lost as soon as the controller shuts down.
robot.rws.panel.restart(ControllerRestartMode.Restart)
robot.disconnect()
##
```

The connection is lost as soon as the controller starts shutting down. A call that succeeded is normally followed by failing requests until the controller is up again, so disconnect right after it and connect again later. On an OmniCore the restart needs the mastership on every domain, taken implicitly by default like for the speed ratio.

## Try it in the demo application

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

![Panel (RWS) page of the ABB SDK demo application](/abb/WinformsScreenshots/RwsPanel.jpg)

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

## API reference

**Methods of PanelService**
```csharp
// Confirms a pending operating mode change (synchronous) The controller waits for this confirmation whenever the mode selector is turned, unless it is configured to acknowledge the change on its own.
void AcknowledgeOperationMode(OperationModeAcknowledgement acknowledgement);

// Gets the collision detection state of the controller (synchronous)
CollisionDetectionState GetCollisionDetectionState();

// Gets the state of the controller (synchronous)
ControllerState GetControllerState();

// Gets the operating mode the controller runs in (synchronous)
OperationMode GetOperationMode();

// Gets the lock state of the operating mode selector (synchronous)
OperationModeLockState GetOperationModeLockState();

// Gets the speed ratio the controller runs the programs at (synchronous)
int GetSpeedRatio();

// Locks the operating mode selector with a pin code (synchronous)
void LockOperationMode(string pin, bool permanent = false);

// Restarts the controller (synchronous)
void Restart(ControllerRestartMode mode, bool useImplicitMastership = true);

// Turns the motors of the robot on or off (synchronous)
void SetControllerState(ControllerState state);

// Sets the language the controller reports its messages in (synchronous)
void SetLanguage(string languageCode);

// Sets the speed ratio the controller runs the programs at (synchronous) Only accepted while the controller runs in automatic mode.
void SetSpeedRatio(int speedRatio, bool useImplicitMastership = true);

// Releases the lock of the operating mode selector (synchronous)
void UnlockOperationMode(string pin);
```

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

**Members of Rws.Data.OperationMode**
```csharp
public enum OperationMode {
    // Automatic mode
    Automatic = 6

    // A change to the automatic mode has been requested and is waiting to be acknowledged
    AutomaticChangeRequest = 2

    // The controller is initializing
    Init = 1

    // Manual mode at full speed
    ManualFullSpeed = 5

    // A change to the manual full speed mode has been requested and is waiting to be acknowledged
    ManualFullSpeedChangeRequest = 3

    // Manual mode at reduced speed
    ManualReducedSpeed = 4

    // The controller reports an undefined operating mode
    Undefined = 7

    // The operating mode could not be determined
    Unknown = 0
}
```

**Members of Rws.Data.OperationModeAcknowledgement**
```csharp
public enum OperationModeAcknowledgement {
    // Confirms the switch to the automatic mode
    Automatic = 0

    // Confirms a collision detection
    CollisionDetection = 2

    // Confirms the switch to the manual full speed mode
    ManualFullSpeed = 1
}
```

**Members of Rws.Data.OperationModeLockState**
```csharp
public enum OperationModeLockState {
    // The controller reports an error on the mode selector lock
    Error = 1

    // The operating mode is locked and can be unlocked again with the pin code it was locked with
    Locked = 3

    // A permanent lock has been requested and is not effective yet
    PendingPermanentLock = 5

    // The operating mode is permanently locked
    PermanentlyLocked = 4

    // The lock state could not be determined
    Unknown = 0

    // The operating mode can be changed freely
    Unlocked = 2
}
```

**Members of Rws.Data.ControllerState**
```csharp
public enum ControllerState {
    // The robot is stopped because the emergency stop was activated
    EmergencyStop = 5

    // The robot is ready to leave the emergency stop state: the emergency stop is no longer activated,
    // but the state transition is not confirmed yet.
    EmergencyStopReset = 6

    // The robot is stopped because the safety runchain is opened, for instance because a door of its cell is open
    GuardStop = 4

    // The robot is starting up. It will shift to <xref href="UnderAutomation.ABB.Rws.Data.ControllerState.MotorsOff" data-throw-if-not-resolved="false"></xref> once it has started.
    Init = 1

    // The robot is in a standby state where there is no power to its motors.
    // The state has to be shifted to <xref href="UnderAutomation.ABB.Rws.Data.ControllerState.MotorsOn" data-throw-if-not-resolved="false"></xref> before the robot can move.
    MotorsOff = 2

    // The robot is ready to move, either by jogging or by running programs
    MotorsOn = 3

    // The robot is in a system failure state and requires a restart
    SystemFailure = 7

    // The state could not be determined
    Unknown = 0
}
```

**Members of Rws.Data.CollisionDetectionState**
```csharp
public enum CollisionDetectionState {
    // A detected collision has been confirmed
    Confirmed = 3

    // No collision has been detected since the controller started
    Init = 1

    // A collision has been detected and is waiting to be confirmed
    Triggered = 2

    // A detected collision has been acknowledged by an operator
    TriggeredAcknowledged = 4

    // The collision detection state could not be determined
    Unknown = 0
}
```

**Members of Rws.Data.ControllerRestartMode**
```csharp
public enum ControllerRestartMode {
    // The controller will be restarted. The last automatically saved system state will be loaded.
    // Should be used to recover from a system crash.
    BStart = 5

    // The controller will be restarted. The current system parameter settings and RAPID programs will be discarded,
    // and the original system installation settings will be used.
    IStart = 3

    // The controller will be restarted. The current RAPID programs and data will be discarded, but not the system parameter settings.
    PStart = 4

    // The controller will be restarted. The state is saved and any changed system parameter settings will be activated after the restart.
    Restart = 0

    // The main computer will be shut down. Should be used if the controller UPS is broken.
    Shutdown = 1

    // The controller will be restarted and the Boot Application will be started. The current system is saved and deactivated
    // (the controller is non-functional, for advanced maintenance only).
    XStart = 2
}
```