`robot.Rws.System` describes the system installed on the controller: its name and software version, the options and the products it was built with, the type of robot it drives, its license, and the energy it consumes. Everything here is read only, except the reset of the energy counter.

Do not confuse this service with [Controller](/abb/documentation/rws-controller), which reports the identity of the controller hardware. `System` reports the software running on it.

## System information

`GetInfo` returns a `SystemInfo` with the name of the system, the robot software version and the installed options.

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

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

        /**/
        SystemInfo info = robot.Rws.System.GetInfo();

        Console.WriteLine(info.Name);          // name of the system
        Console.WriteLine(info.VersionName);   // readable robot software version
        Console.WriteLine(info.Version);
        Console.WriteLine(info.SystemId);      // unique identifier of the system
        Console.WriteLine(info.StartTime);     // last start, null on some controllers
        Console.WriteLine(info.OptionCount);

        // The options come with the description, no second call needed
        foreach (string option in info.Options)
            Console.WriteLine(option);
        /**/

        robot.Disconnect();
    }
}
```

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

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

##
info = robot.rws.system.get_info()

print(info.name)          # name of the system
print(info.version_name)  # readable robot software version
print(info.version)
print(info.system_id)     # unique identifier of the system
print(info.start_time)    # last start, None on some controllers
print(info.option_count)

# The options come with the description, no second call needed
for option in info.options:
    print(option)
##

robot.disconnect()
```

A virtual controller does not fill every field. The detailed version numbers, the build tag and the timestamps are usually empty on a simulated system, which is why they are nullable. The name, the version and the system identifier are always there.

The options are returned with the description, so `GetOptions` is not needed when you call `GetInfo`.

## Options, products, robot types and license

| Method              | Returns                                                                |
| ------------------- | ---------------------------------------------------------------------- |
| `GetOptions()`      | `string[]`, the names of the installed options                         |
| `GetProducts(name)` | `SystemProduct[]`, the installed software products with their versions |
| `GetRobotTypes()`   | `string[]`, for example `IRB 120-3/0.6`                                |
| `GetLicense()`      | `string`, the license the robot software runs under                    |

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

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

        /**/
        // Installed options
        foreach (string option in robot.Rws.System.GetOptions())
            Console.WriteLine(option);

        // Installed products and their versions
        foreach (SystemProduct product in robot.Rws.System.GetProducts())
            Console.WriteLine($"{product.Name} {product.VersionName}");

        // One product only. The name must match exactly
        SystemProduct[] robotWare = robot.Rws.System.GetProducts("RobotWare");

        // Type of every robot the controller drives
        foreach (string type in robot.Rws.System.GetRobotTypes())
            Console.WriteLine(type);

        // License the robot software runs under
        Console.WriteLine(robot.Rws.System.GetLicense());
        /**/

        Console.WriteLine(robotWare.Length);
        robot.Disconnect();
    }
}
```

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

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

##
# Installed options
for option in robot.rws.system.get_options():
    print(option)

# Installed products and their versions
for product in robot.rws.system.get_products():
    print(f"{product.name} {product.version_name}")

# One product only. The name must match exactly
robot_ware = robot.rws.system.get_products("RobotWare")

# Type of every robot the controller drives
for robot_type in robot.rws.system.get_robot_types():
    print(robot_type)

# License the robot software runs under
print(robot.rws.system.get_license())
##

robot.disconnect()
```

`GetProducts` takes an optional name to report a single product. The name has to match an installed product exactly, the controller rejects an unknown one with an error instead of returning an empty list. Call it without argument to get every product.

`GetRobotTypes` only reports standard ABB robots. Positioners, track motions and other mechanical units are left out. A controller that drives none of them returns an empty array, not an error. Use `robot.Rws.MotionSystem.GetMechanicalUnits()` when you need the complete list.

`GetLicense` returns `VIRTUAL_USE` on a RobotStudio virtual controller.

## Energy consumption

`GetEnergy` returns the energy the controller consumed, for the current measurement interval and since the last reset, broken down per mechanical unit and per axis. Values are in joules.

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

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

        /**/
        SystemEnergy energy = robot.Rws.System.GetEnergy();

        // Always check this first, the other values mean nothing when it is false
        if (!energy.IsMeasurementValid)
        {
            Console.WriteLine($"No measurement available, state is {energy.State}");
            return;
        }

        Console.WriteLine($"Interval : {energy.IntervalEnergy} J over {energy.IntervalLength} s");
        Console.WriteLine($"Average power : {energy.AveragePower} W");
        Console.WriteLine($"Accumulated : {energy.AccumulatedEnergy} J since {energy.ResetTime}");

        // Breakdown per mechanical unit and per axis
        foreach (SystemEnergyMechanicalUnit unit in energy.MechanicalUnits)
        {
            Console.WriteLine(unit.Name);

            foreach (SystemEnergyAxis axis in unit.Axes)
                Console.WriteLine($"  axis {axis.Number} : {axis.IntervalEnergy} J");
        }
        /**/

        robot.Disconnect();
    }
}
```

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

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

##
energy = robot.rws.system.get_energy()

# Always check this first, the other values mean nothing when it is false
if not energy.is_measurement_valid:
    print(f"No measurement available, state is {energy.state}")
    raise SystemExit(0)

print(f"Interval : {energy.interval_energy} J over {energy.interval_length} s")
print(f"Average power : {energy.average_power} W")
print(f"Accumulated : {energy.accumulated_energy} J since {energy.reset_time}")

# Breakdown per mechanical unit and per axis
for unit in energy.mechanical_units:
    print(unit.name)

    for axis in unit.axes:
        print(f"  axis {axis.number} : {axis.interval_energy} J")
##

robot.disconnect()
```

Check `IsMeasurementValid` first. The controller answers with a complete but meaningless measurement while it has nothing to report, and `State` then tells why. `AveragePower` is computed by the SDK from the interval energy and the interval length, in watts, and is null when one of the two is missing.

| `SystemEnergyState`             | Meaning                                                          |
| ------------------------------- | ---------------------------------------------------------------- |
| `NotPaused`                     | Measurement is running                                           |
| `Paused`, `Pausing`, `Resuming` | Measurement is stopped or changing state                         |
| `Blocked`                       | Measurement is blocked, no new value is produced                 |
| `GoingToSleep`, `Sleep`         | The controller is entering or in its low energy consumption mode |
| `Unknown`                       | The state could not be determined                                |

## Polling the energy

Reading the whole measurement is not cheap. `GetEnergyChangeCount` returns a single number the controller increments each time a new measurement is available. Poll that number, and read the measurement only when it moved.

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

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

        /**/
        int? lastCount = robot.Rws.System.GetEnergyChangeCount();

        while (true)
        {
            Thread.Sleep(1000);

            // One small request. Read the whole measurement only when the counter moved
            int? count = robot.Rws.System.GetEnergyChangeCount();

            if (count == lastCount) continue;

            lastCount = count;

            SystemEnergy energy = robot.Rws.System.GetEnergy();
            Console.WriteLine($"{energy.TimeStamp} : {energy.AccumulatedEnergy} J");
        }
        /**/
    }
}
```

**Python : SystemEnergyPolling**
```python
import time

from underautomation.abb.abb_controller import AbbController

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

##
last_count = robot.rws.system.get_energy_change_count()

while True:
    time.sleep(1)

    # One small request. Read the whole measurement only when the counter moved
    count = robot.rws.system.get_energy_change_count()

    if count == last_count:
        continue

    last_count = count

    energy = robot.rws.system.get_energy()
    print(f"{energy.time_stamp} : {energy.accumulated_energy} J")
##
```

The same counter is also in `SystemEnergy.ChangeCount`. Both return null when the controller does not report it.

## Reset the accumulated energy

`ResetAccumulatedEnergy` sets the accumulated counter back to zero. The energy counted before is lost, the controller keeps no history. The reset moment becomes the new reference reported by `ResetTime`. The energy of the current interval is not affected.

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

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

        /**/
        // The accumulated counter goes back to zero, the energy counted before is lost
        robot.Rws.System.ResetAccumulatedEnergy();

        SystemEnergy energy = robot.Rws.System.GetEnergy();

        // ResetTime is now the reference of the accumulated energy
        Console.WriteLine($"{energy.AccumulatedEnergy} J since {energy.ResetTime}");
        /**/

        robot.Disconnect();
    }
}
```

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

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

##
# The accumulated counter goes back to zero, the energy counted before is lost
robot.rws.system.reset_accumulated_energy()

energy = robot.rws.system.get_energy()

# reset_time is now the reference of the accumulated energy
print(f"{energy.accumulated_energy} J since {energy.reset_time}")
##

robot.disconnect()
```

## Try it in the demo application

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

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

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

## API reference

**Methods of SystemService**
```csharp
// Gets the energy the controller consumed, for the current interval and since the last reset (synchronous)
SystemEnergy GetEnergy();

// Gets the counter the controller increments each time a new energy measurement is available (synchronous)
int? GetEnergyChangeCount();

// Gets the name, the software version and the installed options of the system (synchronous)
SystemInfo GetInfo();

// Gets the license the robot software runs under (synchronous)
string GetLicense();

// Gets the options installed on the system (synchronous)
string[] GetOptions();

// Gets the software products installed on the controller, with their versions (synchronous)
SystemProduct[] GetProducts(string name = null);

// Gets the type of every robot the controller drives (synchronous)
string[] GetRobotTypes();

// Sets the accumulated energy counter of the controller back to zero (synchronous)
void ResetAccumulatedEnergy();
```

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

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

    // Revision of the programming interface the system is compatible with,
    // null when the controller did not report it
    public int? ApiCompatibilityRevision { get; set; }

    // Build number of the version, null when the controller did not report it
    public int? Build { get; set; }

    // Free text describing the build the system was produced by, null when the controller did not report it
    public string BuildTag { get; set; }

    // Timestamp of the configuration the system was built with, as the controller spells it.
    // 
    // <p>Null when the controller did not report it, which is the usual case on a virtual controller.</p>
    public string ConfigurationTimestamp { get; set; }

    // Date the system was produced, null when the controller did not report it
    public DateTime? Date { get; set; }

    // Description of the system, null when the controller did not report it
    public string Description { get; set; }

    // Version of the software distribution the system was installed from.
    // 
    // <p>Null when the controller does not report it.</p>
    public string DistributionVersion { get; set; }

    // Major number of the version, null when the controller did not report it
    public int? Major { get; set; }

    // Minor number of the version, null when the controller did not report it
    public int? Minor { get; set; }

    // Name of the system installed on the controller
    public string Name { get; set; }

    // Number of options installed on the system
    public int OptionCount { get; }

    // Options installed on the system, in the order the controller reports them
    public string[] Options { get; set; }

    // Revision number of the version, null when the controller did not report it
    public int? Revision { get; set; }

    // Moment the system was last started, null when the controller did not report it
    public DateTime? StartTime { get; set; }

    // Sub revision number of the version, null when the controller did not report it
    public int? SubRevision { get; set; }

    // Unique identifier of the system
    public string SystemId { get; set; }

    // Title of the system, null when the controller did not report it
    public string Title { get; set; }

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

    // Type of the system, null when the controller did not report it
    public string Type { get; set; }

    // Version of the robot software the system runs
    public string Version { get; set; }

    // Human readable version of the robot software the system runs
    public string VersionName { get; set; }
}
```

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

    // Name of the product, for example "RobotWare" or "RobotControl"
    public string Name { get; set; }

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

    // Full version of the product, build information included.
    // 
    // <p>Null when the controller only reports the version name.</p>
    public string Version { get; set; }

    // Human readable version of the product
    public string VersionName { get; set; }
}
```

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

    // Total energy consumed since the last reset, in joules, null when the controller did not report it
    public double? AccumulatedEnergy { get; set; }

    // Average power consumed during the current measurement interval, in watts.
    // 
    // <p>Null when the interval energy or the interval length is missing, or when the interval is empty.</p>
    public double? AveragePower { get; }

    // Counter the controller increments every time a new measurement is available.
    // 
    // <p>Comparing it with the previous one tells whether the values changed without reading them all.</p>
    public int? ChangeCount { get; set; }

    // Total energy consumed during the current measurement interval, in joules,
    // null when the controller did not report it
    public double? IntervalEnergy { get; set; }

    // Length of the measurement interval in seconds, which the average power is computed from,
    // null when the controller did not report it
    public int? IntervalLength { get; set; }

    // Whether the reported measurement is valid. When false, every energy value of this instance is
    // meaningless and the measurement has to be read again later.
    public bool IsMeasurementValid { get; set; }

    // Number of mechanical units the controller reported
    public int MechanicalUnitCount { get; }

    // Energy consumed by each mechanical unit during the current measurement interval
    public SystemEnergyMechanicalUnit[] MechanicalUnits { get; set; }

    // Moment the accumulated energy was last reset, null when the controller did not report it
    public DateTime? ResetTime { get; set; }

    // State of the energy measurement
    public SystemEnergyState State { get; set; }

    // Moment the measurement was taken, null when the controller did not report it
    public DateTime? TimeStamp { get; set; }

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

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

    // Energy consumed by each axis of the mechanical unit during the current measurement interval
    public SystemEnergyAxis[] Axes { get; set; }

    // Number of axes the controller reported for this mechanical unit
    public int AxisCount { get; }

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

    // Energy the axis consumed during the current measurement interval, in joules,
    // null when the controller did not report it
    public double? IntervalEnergy { get; set; }

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

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

**Members of Rws.Data.SystemEnergyState**
```csharp
public enum SystemEnergyState {
    // Energy measurement is blocked and no new value is produced
    Blocked = 1

    // The controller is entering its low energy consumption mode
    GoingToSleep = 6

    // Energy measurement is running
    NotPaused = 3

    // Energy measurement is paused
    Paused = 2

    // Energy measurement is being paused
    Pausing = 5

    // Energy measurement is being resumed
    Resuming = 4

    // The controller is in its low energy consumption mode
    Sleep = 7

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