Reading an I/O signal of an ABB controller is `robot.Rws.Io.GetSignal(network, device, signal)`, and writing one is `robot.Rws.Io.SetSignalValue(network, device, signal, value)`. Digital, analog and group signals all go through the same two methods, only the value changes. I/O needs no mastership, it needs a user account with the write grant.

> **Available on** RWS 1.0 (IRC5) : yes | RWS 2.0 (OmniCore) : yes.

## How a signal is identified

A signal has three parts: the network it belongs to, the device it is connected to, and its name. `Local` is the internal network of the controller, and the signals of the robot itself are usually on it.

`IoSignalItem.Path` gives the three parts joined, for example `Local/Board10/DO_Gripper`. If you only know the name of a signal, find its network and its device with `GetSignals()` or with a search.

## Read a signal

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

A signal has two values. `LogicalValue` is what the program and the network see. `PhysicalValue` is what the device really carries. They differ when the signal is simulated, or inverted in the configuration. `LogicalState` says whether the signal is simulated, `PhysicalState` whether the value can be trusted.

`GetSignals()` returns every signal of the controller in one request. It is the fast way to build a table, but it carries only the name, the type, the category, the logical value and the logical state. Call `GetSignal` on one signal to get everything.

## Digital, analog and group

| `IoSignalType`                  | Value                                     | Written with                 |
| ------------------------------- | ----------------------------------------- | ---------------------------- |
| `DigitalInput`, `DigitalOutput` | 0 or 1                                    | `SetSignalValue(..., 1)`     |
| `AnalogInput`, `AnalogOutput`   | any value inside the configured range     | `SetSignalValue(..., 12.5f)` |
| `GroupInput`, `GroupOutput`     | the integer made of the bits of the group | `SetSignalValue(..., 5)`     |

`LogicalValue` is a `float?`, and `SetSignalValue` takes a `float`. A group of 4 bits written with the value 5 sets the bits 1 and 3. `IoSignalConfiguration.SignalBits` gives the width of a group.

## Write a signal

Only an output can be written, unless the input is simulated. Writing a read only signal fails with an `RwsException`.

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

## Pulse, toggle and invert

A pulse is done by the controller, which is more precise than two writes separated by a `Thread.Sleep` in your application.

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

## Wait for a signal

There is no subscription in this SDK, a value is read by asking for it. To wait for an input, poll it with a period and a timeout. 200 ms is a reasonable period, a shorter one loads the controller for nothing.

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

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

        /**/
        // Ask the robot to work, then wait for its answer
        robot.Rws.Io.SetSignalValue("Local", "Board10", "DO_Start", 1);

        if (!WaitValue(robot, "Local", "Board10", "DI_Done", 1, 10000))
            throw new Exception("The robot did not answer in 10 seconds");

        robot.Rws.Io.SetSignalValue("Local", "Board10", "DO_Start", 0);
        /**/

        robot.Disconnect();
    }

    /**/
    // Polls one signal until it reaches the expected value, or the timeout expires.
    // 200 ms is a reasonable period : a shorter one loads the controller for nothing.
    static bool WaitValue(AbbController robot, string network, string device, string signal,
                          float expected, int timeoutMs)
    {
        DateTime limit = DateTime.UtcNow.AddMilliseconds(timeoutMs);

        while (DateTime.UtcNow < limit)
        {
            IoSignalItem item = robot.Rws.Io.GetSignal(network, device, signal);

            if (item.LogicalValue == expected)
                return true;

            Thread.Sleep(200);
        }

        return false;
    }
    /**/
}
```

When the reaction has to be faster than that, do the waiting in RAPID with a `WaitDI`, and use the SDK to give the program the order to start.

## Simulate a signal during a test

A simulated signal keeps the value written by the client and stops following its device. This is how an input is forced from a test bench, on a real controller as well as on a virtual one.

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

Do not leave a signal simulated at the end of a test. `UnblockSignals()` stops the simulation of every simulated signal of the controller at once.

## Who is allowed to write a signal

A signal can be write protected depending on who writes it and in which operation mode. When a write is refused and the mastership is not the reason, read the configuration of the signal.

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

## Find the signals you need

`SearchSignals` filters on the name, the device, the network, the type and the category. A second criteria can be inverted to exclude what it matches, which is how the safety signals are left out of a list.

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

## Going further

- [I/O signals, devices & networks](/abb/documentation/rws-io), the complete reference
- [Start & stop a RAPID program](/abb/documentation/start-stop-rapid-program), to trigger a program that reacts to your signals
- [Read & write RAPID variables](/abb/documentation/read-write-rapid-variables), the other way of exchanging data with a program