`robot.Rws.Controller` gives access to the controller itself, not to the robot program: identity, clock, network, installed options and systems, restart, backups, safety controller and virtual time. Most of these calls work on an IRC5 and on an OmniCore without changing anything in your code.

Some resources only exist on a real controller. When you call them on a RobotStudio virtual controller, the SDK throws an `RwsException` saying that the resource is not implemented, instead of a raw 404.

## Identity and information

`GetInfo` returns a summary of the controller: system time, name, type and level. `GetIdentity` returns the same name plus the controller id and the MAC address of the main network interface.

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

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

        /**/
        // Overview of the controller: system time, name, type and level
        ControllerInfo info = robot.Rws.Controller.GetInfo();
        Console.WriteLine($"{info.Name} ({info.Type}), level {info.Level}");
        Console.WriteLine($"Controller time (UTC) : {info.SystemTime}");

        // Identity of the controller, with its id and its MAC address
        ControllerIdentity identity = robot.Rws.Controller.GetIdentity();
        Console.WriteLine($"Id : {identity.Id}");
        Console.WriteLine($"MAC address : {identity.MacAddress}");

        // Type tells a real controller from a RobotStudio virtual controller
        bool isVirtual = identity.Type == ControllerType.VirtualController;
        Console.WriteLine($"Virtual controller : {isVirtual}");
        /**/

        /**/
        // Rename the controller. Only a real controller accepts it.
        robot.Rws.Controller.SetIdentity("CELL_01");
        /**/

        robot.Disconnect();
    }
}
```

`Type` tells a real controller from a virtual one, which is useful before calling a method that needs real hardware.

| `ControllerType`    | Meaning                                                                                                                      |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `RealController`    | A physical IRC5 or OmniCore cabinet                                                                                          |
| `VirtualController` | A controller running in RobotStudio, see [Test with a RobotStudio virtual controller](/abb/documentation/virtual-controller) |
| `Unknown`           | The controller reported a value the SDK does not know                                                                        |

`SetIdentity` renames the controller. It works only on a real controller. The `id` argument is accepted by RWS 1.0 only, an OmniCore may ignore or refuse it.

`GetEnvironmentVariable` reads a controller environment variable such as `$TEMP` or `$HOME`, with or without the leading dollar sign. It gives the real path behind these names, which is handy before writing a file with the [file system service](/abb/documentation/rws-files).











## Date, time and time server

> **Available on** RWS 1.0 (IRC5) : yes | RWS 2.0 (OmniCore) : yes. Reading a specific time server needs an OmniCore controller

The controller clock is always UTC. `GetClock` returns a UTC `DateTime`, and `SetClock` expects one. The time zone is read and written apart, with the name used by the tz database, for example `Europe/Stockholm`.

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

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

        /**/
        // The controller clock is always UTC
        DateTime clock = robot.Rws.Controller.GetClock();
        Console.WriteLine($"Controller time (UTC) : {clock}");

        // Set the clock from the PC time
        robot.Rws.Controller.SetClock(DateTime.UtcNow);

        // Time zone, named as in the tz database
        Console.WriteLine($"Time zone : {robot.Rws.Controller.GetTimeZone()}");
        robot.Rws.Controller.SetTimeZone("Europe/Stockholm");
        /**/

        /**/
        // Time server the controller synchronizes its clock with
        robot.Rws.Controller.SetTimeServer("132.163.4.101");

        TimeServerInfo timeServer = robot.Rws.Controller.GetTimeServer();

        // null when no time server is configured
        if (timeServer != null)
        {
            Console.WriteLine($"{timeServer.Address} answers {timeServer.Time}");
        }
        /**/

        robot.Disconnect();
    }
}
```

Instead of setting the clock from your application, you can give the controller a time server with `SetTimeServer`. `GetTimeServer` returns `null` when no time server is configured. Passing an IP address to `GetTimeServer` queries one specific server, this needs a connection opened as RWS 2.0. On an RWS 1.0 connection the SDK throws instead of quietly returning the default server.

The clock, the time zone and the time server are not settable on a virtual controller.





## Network

`GetNetworkInterfaces` lists the IP configuration of every network interface of the controller.

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

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

        /**/
        // IP configuration of every network interface of the controller
        NetworkInterfaceItem[] interfaces = robot.Rws.Controller.GetNetworkInterfaces();

        foreach (NetworkInterfaceItem item in interfaces)
        {
            Console.WriteLine($"{item.Port} {item.LogicalName} : {item.Address} / {item.Mask}");
            Console.WriteLine($"   gateway {item.Gateway}, DHCP {item.DhcpEnabled}");
        }
        /**/

        /**/
        // Fixed address on the LAN adapter
        robot.Rws.Controller.SetNetworkConfiguration(NetworkConfigurationMethod.FixIp,
                                                     "192.168.0.10",
                                                     "255.255.255.0",
                                                     "192.168.0.254");

        // Or let a DHCP server give the address
        robot.Rws.Controller.SetNetworkConfiguration(NetworkConfigurationMethod.Dhcp);

        // The new configuration is used after the next restart
        robot.Rws.Controller.Restart(ControllerRestartMode.Restart);
        /**/

        robot.Disconnect();
    }
}
```

`SetNetworkConfiguration` changes the address of the LAN adapter. **This call can cut you off from the robot.** The controller keeps its current address until the next restart, then answers on the new one. If you set a wrong address or a wrong mask, the only way back is the FlexPendant. The connected user needs the UAS grant to write the controller properties.

| `NetworkConfigurationMethod` | Meaning                                                                  |
| ---------------------------- | ------------------------------------------------------------------------ |
| `FixIp`                      | Fixed address. `address` and `mask` are required, `gateway` is optional. |
| `Dhcp`                       | The address is given by a DHCP server                                    |
| `NoIp`                       | The interface gets no address                                            |

Both methods are refused by a virtual controller.







## Options and installed systems

`HasOption` returns `true` or `false` instead of throwing when the option is missing. The option name is case sensitive, `SAFEMOVEPRO` and not `SafeMovePro`.

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

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

        /**/
        // Is an option installed? The name is case sensitive.
        bool hasSafeMove = robot.Rws.Controller.HasOption("SAFEMOVEPRO");
        Console.WriteLine($"SafeMove Pro : {hasSafeMove}");

        // Systems installed on the controller
        string[] systems = robot.Rws.Controller.GetInstalledSystems();
        Console.WriteLine($"Installed systems : {string.Join(", ", systems)}");
        /**/

        /**/
        // Value of a controller environment variable
        string temp = robot.Rws.Controller.GetEnvironmentVariable("$TEMP");
        Console.WriteLine($"$TEMP is {temp}");

        // Would this RobotWare version run on this hardware?
        bool compatible = robot.Rws.Controller.IsRobotWareVersionCompatible("6.03.0101");
        Console.WriteLine($"Compatible : {compatible}");
        /**/

        robot.Disconnect();
    }
}
```

`GetInstalledSystems` returns the names of the systems installed on the controller, and `IsRobotWareVersionCompatible` says whether a given RobotWare version would run on this hardware. Both need a real controller.

`SetLanguage` changes the language the controller writes its messages in, with a code such as `en`, `de` or `sv`. The language must be installed, otherwise the controller answers 400. The same setting is also reachable from the [control panel service](/abb/documentation/rws-panel).

The RobotWare version and the full list of installed options and products are read from the [system service](/abb/documentation/rws-system).



## Restart

**`Restart` stops the robot.** A running RAPID program is interrupted, the motors go off and the controller reboots. Depending on the mode, the RAPID programs or the system settings can also be lost. Do not call it on a production cell without knowing what the mode does.

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

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

        /**/
        // Warm restart of the controller
        robot.Rws.Controller.Restart(ControllerRestartMode.Restart);

        // The controller closes the connection while it reboots
        robot.Disconnect();
        /**/

        /**/
        // Try to reconnect until the controller answers again
        while (true)
        {
            try
            {
                robot.Connect("192.168.0.1");
                break;
            }
            catch (Exception)
            {
                Thread.Sleep(5000);
            }
        }

        Console.WriteLine(robot.Rws.Panel.GetControllerState());
        /**/

        robot.Disconnect();
    }
}
```

| `ControllerRestartMode` | What the controller does                                                              |
| ----------------------- | ------------------------------------------------------------------------------------- |
| `Restart`               | Warm restart. The system and the RAPID programs are kept.                             |
| `Shutdown`              | The controller stops and stays off. Someone has to power it on again.                 |
| `IStart`                | The system restarts with its default settings                                         |
| `PStart`                | The system restarts and the RAPID programs are removed                                |
| `BStart`                | The system restarts from the state stored at the last shutdown                        |
| `XStart`                | The controller restarts to the boot application, where another system can be selected |

The request returns as soon as the controller accepts it. The connection is then lost, and every following request fails until the controller is up again. Call `Disconnect`, wait, and connect again.

On an OmniCore, the restart needs the mastership on all domains. The SDK takes it for you, this is what the `useImplicitMastership` argument does. Set it to `false` when you already hold the [mastership](/abb/documentation/rws-mastership). An IRC5 needs no mastership here and ignores the argument.

The [control panel service](/abb/documentation/rws-panel) also has a `Restart` method, with the same modes.





## Backup and restore

A backup is a folder written by the controller on its own file system. Creating one is asynchronous: `CreateBackup` returns as soon as the controller accepts the request, and you follow the progress with `GetBackupState`.

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

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

        /**/
        // The controller creates the backup in the background, this call returns immediately
        robot.Rws.Controller.CreateBackup("$temp/mybackup");

        // Poll the state until the controller is done
        BackupState state = robot.Rws.Controller.GetBackupState();

        while (state == BackupState.BackupInProgress)
        {
            Thread.Sleep(1000);
            state = robot.Rws.Controller.GetBackupState();
        }

        if (state != BackupState.BackupReady)
        {
            Console.WriteLine($"The backup failed : {state}");
            return;
        }
        /**/

        /**/
        // What the backup contains
        BackupSystemInfo backup = robot.Rws.Controller.GetBackupInfo("$temp/mybackup");
        Console.WriteLine($"{backup.SystemName}, RobotWare {backup.RobotWareVersion}");
        Console.WriteLine($"{backup.OptionCount} option(s) : {string.Join(", ", backup.Options)}");
        /**/

        robot.Disconnect();
    }
}
```

The destination path must be on the controller file system. Environment variables are allowed, `$temp/mybackup` or `~temp/mybackup` both work. The folder must not exist yet, and it cannot be created under `$HOME`. Creating a backup can stop the RAPID execution, so do not do it in the middle of a production cycle. The connected user needs the backup grant.

| `BackupState`                             | Meaning                              |
| ----------------------------------------- | ------------------------------------ |
| `BackupInProgress`                        | The controller is writing the backup |
| `BackupReady`                             | The last backup finished correctly   |
| `ErrorDuringBackup`                       | The last backup failed               |
| `None`, `InitState`, `Invalid`, `Unknown` | No usable backup state is reported   |

`GetBackupInfo` reads the content of a backup folder without restoring it: system name, RobotWare version and the options the backed up system was built with.

To copy the backup on your PC, download the files with the [file system service](/abb/documentation/rws-files). A complete example is given in [Backup & restore a controller](/abb/documentation/backup-restore-controller).

### Restore

**`RestoreBackup` replaces the current system and restarts the controller.** The RAPID programs, the configuration and, when asked, the safety settings of the running system are overwritten. Check the backup first with `CheckRestore`, which reports the mismatches without touching anything.

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

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

        /**/
        // Check the backup before restoring it
        CheckRestoreResult check = robot.Rws.Controller.CheckRestore("$temp/mybackup");

        if (!check.IsAccepted)
        {
            Console.WriteLine($"The backup cannot be restored : {check.Status} {check.Path}");
            return;
        }
        /**/

        /**/
        // The controller restarts as soon as the restore is accepted
        robot.Rws.Controller.RestoreBackup("$temp/mybackup");

        // A backup taken on another controller has a different system id.
        // Ignore the mismatch to restore it anyway, and keep the backup folder.
        robot.Rws.Controller.RestoreBackup("$temp/mybackup",
                                           BackupRestoreIgnore.SystemId,
                                           false);

        // Restore only the RAPID modules, not the configuration
        robot.Rws.Controller.RestoreBackup("$temp/mybackup",
                                           BackupRestoreIgnore.All,
                                           true,
                                           true,
                                           true,
                                           BackupRestoreInclude.Modules);
        /**/

        robot.Disconnect();
    }
}
```

| `CheckRestoreStatus`         | Meaning                                                  |
| ---------------------------- | -------------------------------------------------------- |
| `Accepted`                   | The backup can be restored as it is                      |
| `RestoreMismatchSystemId`    | The backup comes from another controller                 |
| `RestoreMismatchTemplateId`  | The backup was made from another system template         |
| `DirectoryNotComplete`       | The backup folder misses files, `Path` names one of them |
| `ConfigurationDataIncorrect` | A configuration file of the backup cannot be read        |

`BackupRestoreIgnore` says which mismatches are accepted anyway: `None`, `SystemId`, `TemplateId` or `All`. `BackupRestoreInclude` limits what is restored: `All`, `Cfg` for the configuration only, or `Modules` for the RAPID modules only.

`includeControllerSettings` is used by RobotWare 6. RobotWare 7 does not restore the controller settings and ignores the flag.

`GetBackupResources` returns the names of the backup sub resources the controller exposes. It is mainly useful to know what this particular controller supports.















## Safety

These methods talk to the safety controller. They need the Safety Module option (SafeMove) on the controller and the safety grants on the user account. Without them the controller answers 403, and the SDK throws an `RwsException` that says which of the two is probably missing.

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

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

        /**/
        // Current safety mode of the controller
        SafetyModeStatus mode = robot.Rws.Controller.GetSafetyMode();
        Console.WriteLine($"Safety mode : {mode.Mode}, user data {mode.UserData}");

        // Versions and checksum of the loaded safety configuration
        SafetyConfiguration configuration = robot.Rws.Controller.GetSafetyConfiguration();
        Console.WriteLine($"{configuration.Name} created on {configuration.CreationDate} by {configuration.CreatedBy}");
        Console.WriteLine($"Checksum : {configuration.Checksum}");
        /**/

        /**/
        // What the safety controller reports about the last violation
        SafetyViolationInfo violation = robot.Rws.Controller.GetSafetyViolationInfo();
        Console.WriteLine($"{violation.ViolationNumber} violation(s), type {violation.ViolationType}");

        // Cyclic brake check of the drive number 1
        CyclicBrakeCheckStatus brakeCheck = robot.Rws.Controller.GetCyclicBrakeCheckStatus(1);
        Console.WriteLine($"Brake check : {brakeCheck.Status}, last result {brakeCheck.LastBrakeCheckStatus}");
        /**/

        robot.Disconnect();
    }
}
```

`GetSafetyMode` returns the current mode and the user data that goes with it. `SetSafetyMode` accepts `Active`, `Commissioning` and `Service`. The other values, `ModeError` and `Unknown`, are reported by the controller and cannot be requested. The controller must be in manual mode.

Loading a safety configuration is a two step operation. `GetSafetyLoadOperationStatus` says whether the controller accepts it right now, then `LoadSafetyConfiguration` reads a file that already exists on the controller file system.

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

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

        /**/
        // A safety configuration can only be loaded in some controller states
        SafetyLoadOperationStatus status = robot.Rws.Controller.GetSafetyLoadOperationStatus();

        if (status == SafetyLoadOperationStatus.Ok)
        {
            // The file is already on the controller file system
            robot.Rws.Controller.LoadSafetyConfiguration("$home/safety.xml");
        }
        else
        {
            Console.WriteLine($"A safety configuration cannot be loaded now : {status}");
        }
        /**/

        /**/
        // The controller must be in manual mode to change the safety mode
        robot.Rws.Controller.SetSafetyMode(SafetyMode.Commissioning);

        // Removes the validation information of the current safety configuration
        robot.Rws.Controller.InvalidateSafetyConfiguration();
        /**/

        robot.Disconnect();
    }
}
```

| `SafetyLoadOperationStatus`  | Why loading is refused                 |
| ---------------------------- | -------------------------------------- |
| `Ok`                         | A configuration can be loaded          |
| `OptionNotPresent`           | The safety option is not installed     |
| `NotInManualMode`            | The controller is not in manual mode   |
| `NotInMotorsOff`             | The motors are on                      |
| `CurrentConfigurationLocked` | The configuration in use is locked     |
| `UserGrantMissing`           | The connected user has no safety grant |

`InvalidateSafetyConfiguration` removes the validation information of the configuration file. **After that the safety configuration has to be validated again before the robot can run.**

`GetCyclicBrakeCheckStatus` takes the drive number of a mechanical unit and returns when the next brake check is due and how the last one ended. `GetSafetyViolationInfo` gives the details of the last violation seen by the safety controller.





















## Virtual time

A RobotStudio virtual controller does not run in real time. It runs a simulation clock, the virtual time, that you can slow down, speed up or advance step by step. These methods make sense only on a virtual controller, a real one has no such clock.

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

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

        /**/
        // Milliseconds elapsed since the virtual controller started
        long virtualTime = robot.Rws.Controller.GetVirtualTime();
        Console.WriteLine($"Virtual time : {virtualTime} ms");

        // 100 is about real time, -1 runs the simulation as fast as possible
        robot.Rws.Controller.SetVirtualTimeSpeed(100);
        Console.WriteLine($"Speed : {robot.Rws.Controller.GetVirtualTimeSpeed()} %");

        // Duration of one step, 10 ms minimum
        robot.Rws.Controller.SetVirtualTimeSlice(50);
        Console.WriteLine($"Time slice : {robot.Rws.Controller.GetVirtualTimeSlice()} ms");
        /**/

        /**/
        // Run the virtual time one step at a time
        robot.Rws.Controller.SetVirtualTimeState(VirtualTimeState.RunSlice);
        robot.Rws.Controller.RunVirtualTime();

        VirtualTimeState state = robot.Rws.Controller.GetVirtualTimeState();
        Console.WriteLine($"State : {state}");

        // Let the simulation run freely again
        robot.Rws.Controller.SetVirtualTimeState(VirtualTimeState.FreeRun);
        /**/

        robot.Disconnect();
    }
}
```

`GetVirtualTime` returns the milliseconds elapsed since the virtual controller started. `GetVirtualTimeSpeed` and `SetVirtualTimeSpeed` work in percent of the real time: `100` is about real time, `-1` runs the simulation as fast as the PC can. The time slice is the duration of one step, 10 ms minimum.

| `VirtualTimeState` | Meaning                                                                       |
| ------------------ | ----------------------------------------------------------------------------- |
| `Stop`             | The virtual time does not advance                                             |
| `FreeRun`          | The virtual time runs continuously                                            |
| `RunSlice`         | Each call to `RunVirtualTime` advances the clock by one time slice            |
| `NextEvent`        | Each call to `RunVirtualTime` advances the clock to the next controller event |
| `Unknown`          | The controller reported a value the SDK does not know                         |

`Unknown` is only returned by `GetVirtualTimeState`, it cannot be set. `RunVirtualTime` executes the virtual time according to the current state, so it is used with `RunSlice` and `NextEvent`.

Running a simulation faster than real time makes tests shorter, but the robot then reacts faster than your application. Read [Test with a RobotStudio virtual controller](/abb/documentation/virtual-controller) before using it in automated tests.