`robot.Rws.Io` reads and writes the I/O of the controller. The I/O system has three levels: a network carries devices, a device carries signals. A signal is identified by the three names together, for example `Local`, `Board10` and `DO_Gripper`.

A shorter introduction with a complete example is given in [Read & write I/O signals](/abb/documentation/read-write-io-signals).

## Signals

`GetSignal` reads one signal, `GetSignals` reads every signal of the controller in one call.

| `IoSignalType`                  | Values                                                     |
| ------------------------------- | ---------------------------------------------------------- |
| `DigitalInput`, `DigitalOutput` | 0 or 1                                                     |
| `AnalogInput`, `AnalogOutput`   | A real value inside the range configured on the controller |
| `GroupInput`, `GroupOutput`     | An integer coded on several bits                           |
| `Unknown`                       | The controller reports a type this library does not know   |

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

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

        /**/
        // A signal is identified by its network, its device and its name
        IoSignalItem signal = robot.Rws.Io.GetSignal("Local", "Board10", "DO_Gripper");

        Console.WriteLine(signal.Path);          // Local/Board10/DO_Gripper
        Console.WriteLine(signal.Type);          // DigitalOutput
        Console.WriteLine(signal.LogicalValue);  // 1
        Console.WriteLine(signal.LogicalState);  // NotSimulated
        Console.WriteLine(signal.PhysicalValue); // 1
        Console.WriteLine(signal.PhysicalState); // Valid
        /**/

        /**/
        // Every signal of the controller, in one call
        IoSignalItem[] signals = robot.Rws.Io.GetSignals();

        foreach (IoSignalItem item in signals)
        {
            Console.WriteLine($"{item.Path} = {item.LogicalValue} ({item.Type})");
        }
        /**/

        robot.Disconnect();
    }
}
```

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

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

##
# A signal is identified by its network, its device and its name
signal = robot.rws.io.get_signal("Local", "Board10", "DO_Gripper")

print(signal.path)            # Local/Board10/DO_Gripper
print(signal.type)            # DigitalOutput
print(signal.logical_value)   # 1
print(signal.logical_state)   # NotSimulated
print(signal.physical_value)  # 1
print(signal.physical_state)  # Valid
##

##
# Every signal of the controller, in one call
signals = robot.rws.io.get_signals()

for item in signals:
    print(f"{item.path} = {item.logical_value} ({item.type})")
##

robot.disconnect()
```

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

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

%%
% A signal is identified by its network, its device and its name
signal = robot.Rws.Io.GetSignal('Local', 'Board10', 'DO_Gripper');

% LogicalValue is a .NET Nullable: HasValue is false when the controller gave no value
if signal.LogicalValue.HasValue
    fprintf('%s = %g (%s)\n', char(signal.Path), signal.LogicalValue.Value, char(signal.Type));
end

% Every signal of the controller, in one call
signals = robot.Rws.Io.GetSignals();
for i = 1:signals.Length
    fprintf('%s = %g\n', char(signals(i).Path), signals(i).LogicalValue.GetValueOrDefault());
end
%%

robot.Disconnect();
```

`LogicalValue` is the value seen by the RAPID programs, `PhysicalValue` the one on the hardware. They differ when the signal is simulated. `LogicalState` says whether it is simulated, `PhysicalState` whether the physical value is valid.

A controller usually declares several hundreds of signals, so `GetSignals` returns a large answer. Prefer `SearchSignals` when you only need part of them.

### Write a signal

`SetSignalValue` writes the logical value of a signal. The value is a `float`, which covers the digital, the analog and the group signals with one method.

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

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

        /**/
        // A digital signal takes 0 or 1
        robot.Rws.Io.SetSignalValue("Local", "Board10", "DO_Gripper", 1);

        // An analog or a group signal takes any value inside its range
        robot.Rws.Io.SetSignalValue("Local", "Board10", "AO_Speed", 12.5f);

        // The last argument writes the change in the event log of the controller
        robot.Rws.Io.SetSignalValue("Local", "Board10", "DO_Gripper", 0, true);

        // The controller applies the value 500 ms later, and answers immediately
        robot.Rws.Io.SetSignalValueDelayed("Local", "Board10", "DO_Gripper", 1, 500);
        /**/

        robot.Disconnect();
    }
}
```

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

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

##
# A digital signal takes 0 or 1
robot.rws.io.set_signal_value("Local", "Board10", "DO_Gripper", 1)

# An analog or a group signal takes any value inside its range
robot.rws.io.set_signal_value("Local", "Board10", "AO_Speed", 12.5)

# The last argument writes the change in the event log of the controller
robot.rws.io.set_signal_value("Local", "Board10", "DO_Gripper", 0, True)

# The controller applies the value 500 ms later, and answers immediately
robot.rws.io.set_signal_value_delayed("Local", "Board10", "DO_Gripper", 1, 500)
##

robot.disconnect()
```

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

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

%%
% The value is a .NET float: convert it with single().
% The last argument writes the change in the event log of the controller.

% A digital signal takes 0 or 1
robot.Rws.Io.SetSignalValue('Local', 'Board10', 'DO_Gripper', single(1), false);

% An analog or a group signal takes any value inside its range
robot.Rws.Io.SetSignalValue('Local', 'Board10', 'AO_Speed', single(12.5), false);
%%

robot.Disconnect();
```

Writing a signal needs no mastership, but the user account needs the write access on it, and the signal must accept a write from a remote client in the current operating mode. That is what `GetSignalConfiguration` reports, see below. The controller refuses the write with an `RwsException` when the signal is read only or when the value is outside its range.

`SetSignalValueDelayed` asks the controller to apply the value after a delay in milliseconds. The call returns immediately, the controller does the waiting.

### Pulse, toggle and invert

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

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

        /**/
        // Three pulses to 1, 200 ms active and 200 ms passive
        robot.Rws.Io.PulseSignal("Local", "Board10", "DO_Gripper", 1, 3, 200, 200);

        // Same, with the pulse lengths configured on the controller
        robot.Rws.Io.PulseSignal("Local", "Board10", "DO_Gripper", 1, 1);

        // Toggle pulses the signal by starting from the opposite of its current value
        robot.Rws.Io.ToggleSignal("Local", "Board10", "DO_Gripper", 1, 2, 200, 200);

        // Invert writes the opposite of the current value, once
        robot.Rws.Io.InvertSignal("Local", "Board10", "DO_Gripper", 1);
        /**/

        robot.Disconnect();
    }
}
```

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

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

##
# Three pulses to 1, 200 ms active and 200 ms passive
robot.rws.io.pulse_signal("Local", "Board10", "DO_Gripper", 1, 3, 200, 200)

# Same, with the pulse lengths configured on the controller
robot.rws.io.pulse_signal("Local", "Board10", "DO_Gripper", 1, 1)

# toggle_signal pulses the signal by starting from the opposite of its current value
robot.rws.io.toggle_signal("Local", "Board10", "DO_Gripper", 1, 2, 200, 200)

# invert_signal writes the opposite of the current value, once
robot.rws.io.invert_signal("Local", "Board10", "DO_Gripper", 1)
##

robot.disconnect()
```

Only the digital and the group signals can be pulsed, toggled or inverted, an analog signal is refused. The three methods take a value even though they compute the written value themselves, because the controller rejects a write that carries none. Leave the two pulse lengths null to use the ones configured on the controller.

### Simulate a signal

A simulated signal keeps the value written by the client and stops following its device. This is how an input is forced during a test, without any wiring.

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

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

        /**/
        // Simulate an input: it keeps the value written by the client
        robot.Rws.Io.SetSignalState("Local", "Board10", "DI_PartPresent", true);
        robot.Rws.Io.SetSignalValue("Local", "Board10", "DI_PartPresent", 1);

        IoSignalItem signal = robot.Rws.Io.GetSignal("Local", "Board10", "DI_PartPresent");
        Console.WriteLine(signal.LogicalState); // Simulated

        // Give the signal back to its device
        robot.Rws.Io.SetSignalState("Local", "Board10", "DI_PartPresent", false);

        // Or stop simulating every simulated signal of the controller at once
        robot.Rws.Io.UnblockSignals();
        /**/

        robot.Disconnect();
    }
}
```

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

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

##
# Simulate an input: it keeps the value written by the client
robot.rws.io.set_signal_state("Local", "Board10", "DI_PartPresent", True)
robot.rws.io.set_signal_value("Local", "Board10", "DI_PartPresent", 1)

signal = robot.rws.io.get_signal("Local", "Board10", "DI_PartPresent")
print(signal.logical_state)  # Simulated

# Give the signal back to its device
robot.rws.io.set_signal_state("Local", "Board10", "DI_PartPresent", False)

# Or stop simulating every simulated signal of the controller at once
robot.rws.io.unblock_signals()
##

robot.disconnect()
```

`SetSignalState` only turns the simulation on and off, the value is still written with `SetSignalValue`. `UnblockSignals` stops the simulation of every simulated signal of the controller at once, which is a good thing to call at the end of a test run.

### Search signals

`SearchSignals` narrows the result down with an `IoSignalSearchCriteria`. Every property of the criteria is optional, and an empty criteria matches every signal.

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

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

        /**/
        // Every digital output of one device
        IoSignalSearchCriteria criteria = new IoSignalSearchCriteria();
        criteria.DeviceName = "Board10";
        criteria.Type = IoSignalType.DigitalOutput;

        IoSignalItem[] outputs = robot.Rws.Io.SearchSignals(criteria);

        foreach (IoSignalItem signal in outputs)
        {
            Console.WriteLine($"{signal.Name} = {signal.LogicalValue}");
        }
        /**/

        /**/
        // The extended search also reports the physical value and the write access level
        IoSignalItem[] extended = robot.Rws.Io.SearchSignalsExtended(criteria, null, 0, 50);

        Console.WriteLine(extended[0].PhysicalValue);
        Console.WriteLine(extended[0].Quality);
        Console.WriteLine(extended[0].WriteAccessLevel);

        // A second inverted criteria excludes what it matches, here the safety signals
        IoSignalSearchCriteria exclude = new IoSignalSearchCriteria();
        exclude.Category = "safety";
        exclude.Invert = true;

        IoSignalItem[] withoutSafety = robot.Rws.Io.SearchSignals(criteria, exclude);
        /**/

        robot.Disconnect();
    }
}
```

**Python : IoSearchSignals**
```python
from underautomation.abb.abb_controller import AbbController
from underautomation.abb.rws.data.io_signal_search_criteria import IoSignalSearchCriteria
from underautomation.abb.rws.data.io_signal_type import IoSignalType

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

##
# Every digital output of one device
criteria = IoSignalSearchCriteria()
criteria.device_name = "Board10"
criteria.type = IoSignalType.DigitalOutput

outputs = robot.rws.io.search_signals(criteria)

for signal in outputs:
    print(f"{signal.name} = {signal.logical_value}")
##

##
# The extended search also reports the physical value and the write access level
extended = robot.rws.io.search_signals_extended(criteria, None, 0, 50)

print(extended[0].physical_value)
print(extended[0].quality)
print(extended[0].write_access_level)

# A second inverted criteria excludes what it matches, here the safety signals
exclude = IoSignalSearchCriteria()
exclude.category = "safety"
exclude.invert = True

without_safety = robot.rws.io.search_signals(criteria, exclude)
##

robot.disconnect()
```

`SearchSignalsExtended` returns the same signals with their physical value, their time stamps, their quality and their write access level. It costs more on the controller, so use the simple search when the logical value is enough.

A second criteria can be passed. A signal is returned only when it matches both. Set `Invert` on one of the two to exclude what it matches, otherwise the result is the same as with a single criteria. `start` and `limit` page through a long result.

### Signal configuration

`GetSignalConfiguration` reports how the signal was declared in the I/O configuration of the controller: its width in bits, and who is allowed to write it.

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

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

        /**/
        IoSignalConfiguration config = robot.Rws.Io.GetSignalConfiguration("Local", "Board10", "DO_Gripper");

        Console.WriteLine(config.SignalName);   // DO_Gripper
        Console.WriteLine(config.SignalBits);   // 1

        // Who is allowed to write the signal, and in which operation mode
        Console.WriteLine(config.Rapid);        // a RAPID program
        Console.WriteLine(config.LocalManual);  // the teach pendant, in manual mode
        Console.WriteLine(config.LocalAuto);    // the teach pendant, in auto mode
        Console.WriteLine(config.RemoteManual); // a remote client, in manual mode
        Console.WriteLine(config.RemoteAuto);   // a remote client, in auto mode
        /**/

        robot.Disconnect();
    }
}
```

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

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

##
config = robot.rws.io.get_signal_configuration("Local", "Board10", "DO_Gripper")

print(config.signal_name)   # DO_Gripper
print(config.signal_bits)   # 1

# Who is allowed to write the signal, and in which operation mode
print(config.rapid)         # a RAPID program
print(config.local_manual)  # the teach pendant, in manual mode
print(config.local_auto)    # the teach pendant, in auto mode
print(config.remote_manual) # a remote client, in manual mode
print(config.remote_auto)   # a remote client, in auto mode
##

robot.disconnect()
```

`Rapid`, `LocalManual`, `LocalAuto`, `RemoteManual` and `RemoteAuto` are the write rights. Your application is a remote client, so `RemoteAuto` and `RemoteManual` are the two to check before a write. A write refused by the configuration gives an `RwsException`, not a silent failure.

**Methods of IoService**
```csharp
// Gets a single I/O signal, including its physical value and time stamps (synchronous)
IoSignalItem GetSignal(string network, string device, string signal);

// Gets the runtime configuration properties of an I/O signal (synchronous)
IoSignalConfiguration GetSignalConfiguration(string network, string device, string signal);

// Gets every I/O signal defined in the controller (synchronous) A controller usually exposes several hundreds of signals. Use Nullable%7bSystem.Int32%7d) to narrow the result down to a network, a device, a category or a signal type.
IoSignalItem[] GetSignals();

// Inverts the value of an I/O signal (synchronous) Only digital and group signals can be inverted.
void InvertSignal(string network, string device, string signal, float value, bool logToEventLog = false);

// Pulses the value of an I/O signal (synchronous) Only digital and group signals can be pulsed.
void PulseSignal(string network, string device, string signal, float value, int pulses, int? activePulseLength = null, int? passivePulseLength = null, bool logToEventLog = false);

// Searches the I/O signals matching the given criteria (synchronous) The returned signals carry their name, type, category, logical value and logical state. Use Nullable%7bSystem.Int32%7d) to also get their physical value, time stamps and write access level.
IoSignalItem[] SearchSignals(IoSignalSearchCriteria criteria = null, IoSignalSearchCriteria secondCriteria = null, int? start = null, int? limit = null);

// Searches the I/O signals matching the given criteria and returns their extended properties (synchronous) In addition to Nullable%7bSystem.Int32%7d), the returned signals carry their physical value, quality, time stamps and write access level.
IoSignalItem[] SearchSignalsExtended(IoSignalSearchCriteria criteria = null, IoSignalSearchCriteria secondCriteria = null, int? start = null, int? limit = null);

// Simulates or stops simulating an I/O signal (synchronous) A simulated signal keeps the logical value written by the client and no longer follows its physical value.
void SetSignalState(string network, string device, string signal, bool simulated);

// Writes the value of an I/O signal (synchronous)
void SetSignalValue(string network, string device, string signal, float value, bool logToEventLog = false);

// Writes the value of an I/O signal in "queued delayed" mode (synchronous) The controller queues the write and applies it once the delay has elapsed.
void SetSignalValueDelayed(string network, string device, string signal, float value, int delay, bool logToEventLog = false);

// Pulses an I/O signal by toggling its current value (synchronous) Only digital and group signals can be toggled.
void ToggleSignal(string network, string device, string signal, float value, int pulses, int? activePulseLength = null, int? passivePulseLength = null, bool logToEventLog = false);

// Removes the simulation of every simulated I/O signal of the controller (synchronous)
void UnblockSignals();
```

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

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

    // Category the signal belongs to, for example "safety"
    public string Category { get; set; }

    // Name of the device the signal is connected to, for example "DRV_1"
    public string DeviceName { get; set; }

    // Logical state of the signal (simulated or not)
    public IoSignalLogicalState LogicalState { get; set; }

    // Microseconds part of the global time at which the logical value was updated, null when not reported
    public long? LogicalTimeMicroseconds { get; set; }

    // Seconds part of the global time at which the logical value was updated, null when not reported
    public long? LogicalTimeSeconds { get; set; }

    // Logical value of the signal, null when the controller did not report it
    public float? LogicalValue { get; set; }

    // Name of the signal, for example "DRV1BRAKE"
    public string Name { get; set; }

    // Name of the network the signal belongs to, for example "Local"
    public string NetworkName { get; set; }

    // Full path of the signal, "{network}/{device}/{signal}" (for example "Local/DRV_1/DRV1BRAKE")
    public string Path { get; set; }

    // Physical state of the signal.
    // 
    // <p>Only reported when reading a single signal with <code>IoService.GetSignal()</code>.</p>
    public IoSignalPhysicalState PhysicalState { get; set; }

    // Microseconds part of the global time at which the physical value was updated, null when not reported
    public long? PhysicalTimeMicroseconds { get; set; }

    // Seconds part of the global time at which the physical value was updated, null when not reported
    public long? PhysicalTimeSeconds { get; set; }

    // Physical value of the signal, null when the controller did not report it.
    // 
    // <p>Only reported by <code>IoService.GetSignal()</code> and <code>IoService.SearchSignalsExtended()</code>.</p>
    public float? PhysicalValue { get; set; }

    // Quality of the signal, reported as a numeric code by <code>IoService.GetSignal()</code> and as a
    // textual value (for example "good") by <code>IoService.SearchSignalsExtended()</code>
    public string Quality { get; set; }

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

    // Type of the signal
    public IoSignalType Type { get; set; }

    // Access level required to write the signal, for example "None".
    // 
    // <p>Only reported by <code>IoService.SearchSignalsExtended()</code>.</p>
    public string WriteAccessLevel { get; set; }
}
```

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

    // Whether only the blocked (simulated) signals are searched
    public bool? Blocked { get; set; }

    // Category of the searched signals, for example "safety"
    public string Category { get; set; }

    // Category prefix of the searched signals
    public string CategoryPrefix { get; set; }

    // Name of the device the searched signals are connected to
    public string DeviceName { get; set; }

    // Whether the criteria is inverted: the signals matching it are excluded from the result
    public bool? Invert { get; set; }

    // Name of the searched signals
    public string Name { get; set; }

    // Name of the network the searched signals belong to
    public string NetworkName { get; set; }

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

    // Type of the searched signals, null to search every type
    public IoSignalType? Type { get; set; }
}
```

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

    // Whether a local client can write the signal in auto mode
    public bool? LocalAuto { get; set; }

    // Whether a local client can write the signal in manual mode
    public bool? LocalManual { get; set; }

    // Whether a RAPID client can write the signal in both manual and auto mode
    public bool? Rapid { get; set; }

    // Whether a remote client can write the signal in auto mode
    public bool? RemoteAuto { get; set; }

    // Whether a remote client can write the signal in manual mode
    public bool? RemoteManual { get; set; }

    // Whether the bits of this signal are set by a device transfer operation.
    // 
    // <p>Not reported by every controller, null when absent from the response.</p>
    public bool? SetByDeviceTransfer { get; set; }

    // Number of bits of the signal, null when not reported
    public int? SignalBits { get; set; }

    // Name of the signal, for example "DRV1CHAIN2"
    public string SignalName { get; set; }

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

**Members of Rws.Data.IoSignalType**
```csharp
public enum IoSignalType {
    // Analog input
    AnalogInput = 4

    // Analog output
    AnalogOutput = 3

    // Digital input
    DigitalInput = 2

    // Digital output
    DigitalOutput = 1

    // Group input
    GroupInput = 5

    // Group output
    GroupOutput = 6

    // The signal type could not be determined
    Unknown = 0
}
```

**Members of Rws.Data.IoSignalLogicalState**
```csharp
public enum IoSignalLogicalState {
    // The signal is not simulated
    NotSimulated = 2

    // The signal is simulated: its logical value is forced and no longer follows the physical value
    Simulated = 1

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

**Members of Rws.Data.IoSignalPhysicalState**
```csharp
public enum IoSignalPhysicalState {
    // The physical value of the signal is not valid
    Invalid = 2

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

    // The physical value of the signal is valid
    Valid = 1
}
```

## I/O devices

A device is a physical or a virtual I/O board. `GetDevices` lists them all, `GetDevice` reads one with its input and output data.

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

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

        /**/
        // Every device of every network
        IoDeviceItem[] devices = robot.Rws.Io.GetDevices();

        foreach (IoDeviceItem item in devices)
        {
            Console.WriteLine($"{item.Path} ({item.Type}) {item.PhysicalState} {item.LogicalState}");
        }

        // One device, with its input and output data
        IoDeviceItem device = robot.Rws.Io.GetDevice("Local", "Board10");
        Console.WriteLine(device.Address);
        Console.WriteLine(device.InputData);
        Console.WriteLine(device.OutputData);

        // Number of bits and write rights of the device
        IoDeviceConfiguration config = robot.Rws.Io.GetDeviceConfiguration("Local", "Board10");
        Console.WriteLine($"{config.InputBits} input bits, {config.OutputBits} output bits");

        // Disable a device, then enable it again
        robot.Rws.Io.SetDeviceState("Local", "Board10", IoDeviceLogicalState.Disabled);
        robot.Rws.Io.SetDeviceState("Local", "Board10", IoDeviceLogicalState.Enabled);

        // Search by name, by logical state, or by both, optionally inside one network
        IoDeviceItem[] enabled = robot.Rws.Io.SearchDevices(null, IoDeviceLogicalState.Enabled, "Local");
        /**/

        /**/
        // Force the first input byte of a device, on a virtual controller only.
        // The mask selects the written bits, here the two lowest ones.
        robot.Rws.Io.SetDeviceInputData("Local", "Board10", 0, 0x03, 0x03);
        robot.Rws.Io.SetDeviceOutputData("Local", "Board10", 0, 0x01, 0x01);

        // Firmware state of a device and of its modules, on a real controller only
        IoDeviceUpgradeInfo upgrade = robot.Rws.Io.GetDeviceUpgradeInfo("EtherNetIP", "Local_IO");
        Console.WriteLine($"{upgrade.State} {upgrade.Status} {upgrade.ModuleCount} modules");

        foreach (IoFirmwareModuleInfo module in upgrade.Modules)
        {
            Console.WriteLine($"{module.Index} {module.ProgramName} {module.SerialNumber}");
        }

        // Send a command to a device, on a real controller only.
        // The last two arguments are the length of the value and the timeout in milliseconds.
        robot.Rws.Io.SendDeviceCommand("EtherNetIP", "Local_IO", "FIRMWARE_INFO", "", 0, 5000);
        /**/

        robot.Disconnect();
    }
}
```

**Python : IoDevices**
```python
from underautomation.abb.abb_controller import AbbController
from underautomation.abb.rws.data.io_device_logical_state import IoDeviceLogicalState

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

##
# Every device of every network
devices = robot.rws.io.get_devices()

for item in devices:
    print(f"{item.path} ({item.type}) {item.physical_state} {item.logical_state}")

# One device, with its input and output data
device = robot.rws.io.get_device("Local", "Board10")
print(device.address)
print(device.input_data)
print(device.output_data)

# Number of bits and write rights of the device
config = robot.rws.io.get_device_configuration("Local", "Board10")
print(f"{config.input_bits} input bits, {config.output_bits} output bits")

# Disable a device, then enable it again
robot.rws.io.set_device_state("Local", "Board10", IoDeviceLogicalState.Disabled)
robot.rws.io.set_device_state("Local", "Board10", IoDeviceLogicalState.Enabled)

# Search by name, by logical state, or by both, optionally inside one network
enabled = robot.rws.io.search_devices(None, IoDeviceLogicalState.Enabled, "Local")
##

##
# Force the first input byte of a device, on a virtual controller only.
# The mask selects the written bits, here the two lowest ones.
robot.rws.io.set_device_input_data("Local", "Board10", 0, 0x03, 0x03)
robot.rws.io.set_device_output_data("Local", "Board10", 0, 0x01, 0x01)

# Firmware state of a device and of its modules, on a real controller only
upgrade = robot.rws.io.get_device_upgrade_info("EtherNetIP", "Local_IO")
print(f"{upgrade.state} {upgrade.status} {upgrade.module_count} modules")

for module in upgrade.modules:
    print(f"{module.index} {module.program_name} {module.serial_number}")

# Send a command to a device, on a real controller only.
# The last two arguments are the length of the value and the timeout in milliseconds.
robot.rws.io.send_device_command("EtherNetIP", "Local_IO", "FIRMWARE_INFO", "", 0, 5000)
##

robot.disconnect()
```

`PhysicalState` is the state of the hardware: `Running`, `Error`, `Unconnected`, `Unconfigured`, `Deactivated`, `Startup`, `Init` or `Halted`. `LogicalState` is what the controller was asked to do with the device, `Enabled` or `Disabled`, and `SetDeviceState` changes it. Disabling a device stops its signals from being updated.

`GetDeviceConfiguration` reports the number of input and output bits of the device and the same write rights as for a signal.

Three methods depend on the kind of controller:

- `SetDeviceInputData` and `SetDeviceOutputData` force one byte of the data of a device, on a virtual controller only. The mask selects the written bits, a bit at zero is left unchanged. A real controller refuses the request.
- `GetDeviceUpgradeInfo` reports the firmware state of a device and of its modules, on a real controller only.
- `SendDeviceCommand` sends a command to a device, on a real controller only. `valueLength` is used on an IRC5, an OmniCore computes it from the value itself and ignores the argument.

**Methods of IoService**
```csharp
// Gets a single I/O device, including its input and output data (synchronous)
IoDeviceItem GetDevice(string network, string device);

// Gets the runtime configuration properties of an I/O device (synchronous)
IoDeviceConfiguration GetDeviceConfiguration(string network, string device);

// Gets the firmware upgrade status of an I/O device and of each of its modules (synchronous) Only available on a real controller.
IoDeviceUpgradeInfo GetDeviceUpgradeInfo(string network, string device);

// Gets every I/O device defined in the controller (synchronous)
IoDeviceItem[] GetDevices();

// Searches the I/O devices matching a name and/or a logical state (synchronous)
IoDeviceItem[] SearchDevices(string name = null, IoDeviceLogicalState? logicalState = null, string network = null);

// Sends a command to an I/O device (synchronous) Only available on a real controller.
void SendDeviceCommand(string network, string device, string commandName, string value, int valueLength, int timeout);

// Writes one byte of the input data of an I/O device (synchronous) Only supported on a virtual controller.
void SetDeviceInputData(string network, string device, int startByte, int signalData, int dataMask);

// Writes one byte of the output data of an I/O device (synchronous) Only supported on a virtual controller.
void SetDeviceOutputData(string network, string device, int startByte, int signalData, int dataMask);

// Enables or disables an I/O device (synchronous)
void SetDeviceState(string network, string device, IoDeviceLogicalState logicalState);
```

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

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

    // Address of the device on its network, "-" when the network has no addressing
    public string Address { get; set; }

    // Input data of the device, as an hexadecimal string (for example "1FFFE063").
    // 
    // <p>Only reported when reading a single device with <code>IoService.GetDevice()</code>.</p>
    public string InputData { get; set; }

    // Input mask of the device, as an hexadecimal string. A bit set to zero is an input bit that is not written.
    // 
    // <p>Only reported when reading a single device with <code>IoService.GetDevice()</code>.</p>
    public string InputMask { get; set; }

    // Logical state of the device
    public IoDeviceLogicalState LogicalState { get; set; }

    // Name of the device, for example "DRV_1" or "PANEL"
    public string Name { get; set; }

    // Name of the network the device is connected to, for example "Local"
    public string NetworkName { get; set; }

    // Output data of the device, as an hexadecimal string (for example "0000000E").
    // 
    // <p>Only reported when reading a single device with <code>IoService.GetDevice()</code>.</p>
    public string OutputData { get; set; }

    // Output mask of the device, as an hexadecimal string. A bit set to zero is an output bit that is not written.
    // 
    // <p>Only reported when reading a single device with <code>IoService.GetDevice()</code>.</p>
    public string OutputMask { get; set; }

    // Full path of the device, "{network}/{device}" (for example "Local/DRV_1")
    public string Path { get; set; }

    // Physical state of the device
    public IoDevicePhysicalState PhysicalState { get; set; }

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

    // Type of the device, for example "DRV_1_TYPE".
    // 
    // <p>Not reported by every controller, null when absent. A virtual controller leaves it out.</p>
    public string Type { get; set; }
}
```

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

    // Whether deactivating the device is denied
    public bool? DenyDeactivate { get; set; }

    // Address of the device on its network, "-" when the network has no addressing
    public string DeviceAddress { get; set; }

    // Name of the device, for example "DN_Internal_Device"
    public string DeviceName { get; set; }

    // Number of input bits of the device, null when not reported
    public int? InputBits { get; set; }

    // Whether a local client can access the device in auto mode
    public bool? LocalAuto { get; set; }

    // Whether a local client can access the device in manual mode
    public bool? LocalManual { get; set; }

    // Name of the industrial network the device belongs to, for example "DeviceNet"
    public string NetworkName { get; set; }

    // Number of output bits of the device, null when not reported
    public int? OutputBits { get; set; }

    // Whether a RAPID client can access the device in both manual and auto mode
    public bool? Rapid { get; set; }

    // Whether a remote client can access the device in auto mode
    public bool? RemoteAuto { get; set; }

    // Whether a remote client can access the device in manual mode
    public bool? RemoteManual { get; set; }

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

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

    // Number of modules reported by the controller
    public int ModuleCount { get; }

    // Firmware status of each module of the device, empty when the controller reported none
    public IoFirmwareModuleInfo[] Modules { get; set; }

    // Overall progress of the firmware upgrade of the device
    public IoFirmwareUpgradeState State { get; set; }

    // Overall result of the firmware upgrade of the device
    public IoFirmwareUpgradeStatus Status { get; set; }

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

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

    // Hardware revision of the module, for example "C.1"
    public string HardwareRevision { get; set; }

    // Index of the module inside the device ("0", "1", ...)
    public string Index { get; set; }

    // Name of the latest program available for the module
    public string LatestProgramNameAvailable { get; set; }

    // Name of the program installed on the module, for example "A_HYPIOM_B_3_8"
    public string ProgramName { get; set; }

    // Serial number of the module
    public string SerialNumber { get; set; }

    // Progress of the firmware upgrade of this module
    public IoFirmwareUpgradeState State { get; set; }

    // Result of the firmware upgrade of this module
    public IoFirmwareUpgradeStatus Status { get; set; }

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

**Members of Rws.Data.IoDeviceLogicalState**
```csharp
public enum IoDeviceLogicalState {
    // The device is disabled
    Disabled = 2

    // The device is enabled
    Enabled = 1

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

**Members of Rws.Data.IoDevicePhysicalState**
```csharp
public enum IoDevicePhysicalState {
    // The device is deactivated
    Deactivated = 1

    // The device reports an error
    Error = 3

    // The device is halted
    Halted = 8

    // The device is initializing
    Init = 7

    // The device is running
    Running = 2

    // The device is starting up
    Startup = 6

    // The device is not configured
    Unconfigured = 5

    // The device is not connected
    Unconnected = 4

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

**Members of Rws.Data.IoFirmwareUpgradeState**
```csharp
public enum IoFirmwareUpgradeState {
    // The upgrade resources are being allocated
    Allocate = 4

    // The upgrade is performed automatically
    Automatic = 1

    // The upgraded firmware is being verified
    Check = 12

    // The upgrade resources are being released
    Deallocate = 13

    // The upgrade is finished
    Finished = 14

    // The firmware information is being collected
    Info = 3

    // The upgrade has to be started manually
    Manual = 2

    // The upgrade is running
    Running = 6

    // The firmware is being written to the device
    RunningBurnInProgress = 10

    // The firmware is being checked
    RunningCheckInProgress = 8

    // The device acknowledged the end of the upgrade
    RunningEndReceived = 11

    // The device memory is being erased
    RunningEraseInProgress = 9

    // The device acknowledged the start of the upgrade
    RunningStartReceived = 7

    // The upgrade is starting
    Start = 5

    // The state is unknown, or could not be parsed
    Unknown = 0
}
```

**Members of Rws.Data.IoFirmwareUpgradeStatus**
```csharp
public enum IoFirmwareUpgradeStatus {
    // The upgrade failed
    Error = 1

    // The upgrade finished, the firmware was already up to date
    Ok = 2

    // The upgrade is pending
    Pending = 4

    // The controller did not report a status, or it could not be parsed
    Unknown = 0

    // The upgrade finished, the firmware was updated
    Upgraded = 3
}
```

## I/O networks

A network groups the devices connected the same way. `Local` is the internal network of the controller, a fieldbus such as EtherNet/IP or PROFINET is another one.

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

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

        /**/
        // Every network of the controller
        IoNetworkItem[] networks = robot.Rws.Io.GetNetworks();

        foreach (IoNetworkItem item in networks)
        {
            Console.WriteLine($"{item.Name}: {item.PhysicalState}, {item.LogicalState}");
        }

        IoNetworkItem network = robot.Rws.Io.GetNetwork("Local");

        IoNetworkConfiguration config = robot.Rws.Io.GetNetworkConfiguration("Local");
        Console.WriteLine($"{config.NetworkName} {config.NetworkType} {config.NetworkAddress}");

        // Stop a network, then start it again. Every device of the network follows.
        robot.Rws.Io.SetNetworkState("Local", IoNetworkLogicalState.Stopped);
        robot.Rws.Io.SetNetworkState("Local", IoNetworkLogicalState.Started);

        // Search by name, by physical state, or by both
        IoNetworkItem[] running = robot.Rws.Io.SearchNetworks(null, IoNetworkPhysicalState.Running);
        /**/

        /**/
        // Run the auto configuration of a fieldbus network.
        // This rewrites the I/O configuration and cannot be undone.
        IoClientAction action = robot.Rws.Io.SetNetworkConfigurationType("DeviceNet", IoNetworkConfigurationType.Scan);

        if (action == IoClientAction.Restart)
        {
            Console.WriteLine("Restart the controller to apply the new configuration");
        }

        // Names of the I/O resources the controller exposes
        string[] resources = robot.Rws.Io.GetResources();
        /**/

        robot.Disconnect();
    }
}
```

**Python : IoNetworks**
```python
from underautomation.abb.abb_controller import AbbController
from underautomation.abb.rws.data.io_client_action import IoClientAction
from underautomation.abb.rws.data.io_network_configuration_type import IoNetworkConfigurationType
from underautomation.abb.rws.data.io_network_logical_state import IoNetworkLogicalState
from underautomation.abb.rws.data.io_network_physical_state import IoNetworkPhysicalState

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

##
# Every network of the controller
networks = robot.rws.io.get_networks()

for item in networks:
    print(f"{item.name}: {item.physical_state}, {item.logical_state}")

network = robot.rws.io.get_network("Local")

config = robot.rws.io.get_network_configuration("Local")
print(f"{config.network_name} {config.network_type} {config.network_address}")

# Stop a network, then start it again. Every device of the network follows.
robot.rws.io.set_network_state("Local", IoNetworkLogicalState.Stopped)
robot.rws.io.set_network_state("Local", IoNetworkLogicalState.Started)

# Search by name, by physical state, or by both
running = robot.rws.io.search_networks(None, IoNetworkPhysicalState.Running)
##

##
# Run the auto configuration of a fieldbus network.
# This rewrites the I/O configuration and cannot be undone.
action = robot.rws.io.set_network_configuration_type("DeviceNet", IoNetworkConfigurationType.Scan)

if action == IoClientAction.Restart:
    print("Restart the controller to apply the new configuration")

# Names of the I/O resources the controller exposes
resources = robot.rws.io.get_resources()
##

robot.disconnect()
```

`SetNetworkState` starts and stops a network. Every device of the network follows, so stopping a network stops the signals of all its devices at once.

`GetNetworkConfiguration` returns the type and the address of the network. `GetResources` returns the names of the I/O resources the controller exposes, which is mostly useful to check what a given controller supports.

### Auto configuration

> **Available on** RWS 1.0 (IRC5) : yes | RWS 2.0 (OmniCore) : yes. Only an OmniCore reports the action the client should take

`SetNetworkConfigurationType` runs the auto configuration of a fieldbus network. It rewrites part of the I/O configuration of the controller and cannot be undone, so keep it out of a production application.

| `IoNetworkConfigurationType` | Configures                                  |
| ---------------------------- | ------------------------------------------- |
| `Scan`                       | Scans the network for the connected devices |
| `Units`                      | The devices of the network                  |
| `Bits`                       | The signals of the network                  |
| `Groups`                     | The signal groups of the network            |
| `Both`                       | The signals and the signal groups           |

The returned `IoClientAction` says what to do next: `None` when nothing is needed, `Info` when the result should be shown to the user, `Restart` when the controller has to be restarted for the new configuration to take effect. An IRC5 does not report it, `Unknown` is then always returned.

**Methods of IoService**
```csharp
// Gets a single I/O network (synchronous)
IoNetworkItem GetNetwork(string network);

// Gets the runtime configuration properties of an I/O network (synchronous)
IoNetworkConfiguration GetNetworkConfiguration(string network);

// Gets every I/O network defined in the controller (synchronous)
IoNetworkItem[] GetNetworks();

// Gets the names of the I/O sub resources exposed by the controller (synchronous)
string[] GetResources();

// Searches the I/O networks matching a name and/or a physical state (synchronous)
IoNetworkItem[] SearchNetworks(string name = null, IoNetworkPhysicalState? physicalState = null);

// Runs the auto configuration of an I/O network (synchronous)
IoClientAction SetNetworkConfigurationType(string network, IoNetworkConfigurationType configurationType);

// Starts or stops an I/O network (synchronous)
void SetNetworkState(string network, IoNetworkLogicalState logicalState);
```

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

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

    // Logical state of the network
    public IoNetworkLogicalState LogicalState { get; set; }

    // Name of the network, for example "Local", "Virtual" or "EtherNetIP"
    public string Name { get; set; }

    // Full path of the network, which is its name for a network (for example "Local")
    public string Path { get; set; }

    // Physical state of the network
    public IoNetworkPhysicalState PhysicalState { get; set; }

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

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

    // Industrial network address, "-" when the network has no addressing
    public string NetworkAddress { get; set; }

    // Name of the network, for example "Local"
    public string NetworkName { get; set; }

    // Type of the network, for example "Local" or "LOC"
    public string NetworkType { get; set; }

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

**Members of Rws.Data.IoNetworkLogicalState**
```csharp
public enum IoNetworkLogicalState {
    // The network is started
    Started = 1

    // The network is stopped
    Stopped = 2

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

**Members of Rws.Data.IoNetworkPhysicalState**
```csharp
public enum IoNetworkPhysicalState {
    // The network reports an error
    Error = 3

    // The network is halted
    Halted = 1

    // The network is initializing
    Init = 5

    // The network is running
    Running = 2

    // The network is starting up
    Startup = 4

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

**Members of Rws.Data.IoNetworkConfigurationType**
```csharp
public enum IoNetworkConfigurationType {
    // Configure the signals of the network
    Bits = 0

    // Configure both the signals and the signal groups
    Both = 2

    // Configure the signal groups of the network
    Groups = 1

    // Scan the network for connected devices
    Scan = 3

    // Configure the devices of the network
    Units = 4
}
```

**Members of Rws.Data.IoClientAction**
```csharp
public enum IoClientAction {
    // The user should be informed of the configuration result
    Info = 2

    // Nothing to do
    None = 1

    // The controller has to be restarted for the configuration to take effect
    Restart = 3

    // The controller did not report any client action.
    // 
    // <p>Always returned when connected with version 1, which does not report this information.</p>
    Unknown = 0
}
```

## Try it in the demo application

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

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

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