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

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

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

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

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

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

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















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

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



















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

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