Read & write I/O signals
Read and write digital, analog and group I/O signals of an ABB controller, pulse a signal and simulate one during tests.
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.
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
// A signal is identified by its network, its device and its nameIoSignalItem signal = robot.Rws.Io.GetSignal("Local", "Board10", "DO_Gripper");Console.WriteLine(signal.Path); // Local/Board10/DO_GripperConsole.WriteLine(signal.Type); // DigitalOutputConsole.WriteLine(signal.LogicalValue); // 1Console.WriteLine(signal.LogicalState); // NotSimulatedConsole.WriteLine(signal.PhysicalValue); // 1Console.WriteLine(signal.PhysicalState); // Valid// Every signal of the controller, in one callIoSignalItem[] signals = robot.Rws.Io.GetSignals();foreach (IoSignalItem item in signals){Console.WriteLine($"{item.Path} = {item.LogicalValue} ({item.Type})");}
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.
// A digital signal takes 0 or 1robot.Rws.Io.SetSignalValue("Local", "Board10", "DO_Gripper", 1);// An analog or a group signal takes any value inside its rangerobot.Rws.Io.SetSignalValue("Local", "Board10", "AO_Speed", 12.5f);// The last argument writes the change in the event log of the controllerrobot.Rws.Io.SetSignalValue("Local", "Board10", "DO_Gripper", 0, true);// The controller applies the value 500 ms later, and answers immediatelyrobot.Rws.Io.SetSignalValueDelayed("Local", "Board10", "DO_Gripper", 1, 500);
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.
// Three pulses to 1, 200 ms active and 200 ms passiverobot.Rws.Io.PulseSignal("Local", "Board10", "DO_Gripper", 1, 3, 200, 200);// Same, with the pulse lengths configured on the controllerrobot.Rws.Io.PulseSignal("Local", "Board10", "DO_Gripper", 1, 1);// Toggle pulses the signal by starting from the opposite of its current valuerobot.Rws.Io.ToggleSignal("Local", "Board10", "DO_Gripper", 1, 2, 200, 200);// Invert writes the opposite of the current value, oncerobot.Rws.Io.InvertSignal("Local", "Board10", "DO_Gripper", 1);
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.
// Ask the robot to work, then wait for its answerrobot.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);// 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.
// Simulate an input: it keeps the value written by the clientrobot.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 devicerobot.Rws.Io.SetSignalState("Local", "Board10", "DI_PartPresent", false);// Or stop simulating every simulated signal of the controller at oncerobot.Rws.Io.UnblockSignals();
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.
IoSignalConfiguration config = robot.Rws.Io.GetSignalConfiguration("Local", "Board10", "DO_Gripper");Console.WriteLine(config.SignalName); // DO_GripperConsole.WriteLine(config.SignalBits); // 1// Who is allowed to write the signal, and in which operation modeConsole.WriteLine(config.Rapid); // a RAPID programConsole.WriteLine(config.LocalManual); // the teach pendant, in manual modeConsole.WriteLine(config.LocalAuto); // the teach pendant, in auto modeConsole.WriteLine(config.RemoteManual); // a remote client, in manual modeConsole.WriteLine(config.RemoteAuto); // a remote client, in auto mode
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.
// Every digital output of one deviceIoSignalSearchCriteria 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 levelIoSignalItem[] 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 signalsIoSignalSearchCriteria exclude = new IoSignalSearchCriteria();exclude.Category = "safety";exclude.Invert = true;IoSignalItem[] withoutSafety = robot.Rws.Io.SearchSignals(criteria, exclude);
Going further
- I/O signals, devices & networks, the complete reference
- Start & stop a RAPID program, to trigger a program that reacts to your signals
- Read & write RAPID variables, the other way of exchanging data with a program
// 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.
public class IoSignalItem {// Initializes a new instance of the <xref href="UnderAutomation.ABB.Rws.Data.IoSignalItem" data-throw-if-not-resolved="false"></xref> classpublic 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 reportedpublic long? LogicalTimeMicroseconds { get; set; }// Seconds part of the global time at which the logical value was updated, null when not reportedpublic long? LogicalTimeSeconds { get; set; }// Logical value of the signal, null when the controller did not report itpublic 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 reportedpublic long? PhysicalTimeMicroseconds { get; set; }// Seconds part of the global time at which the physical value was updated, null when not reportedpublic 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 signalpublic override string ToString()// Type of the signalpublic 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; }}
public enum IoSignalType {// Analog inputAnalogInput = 4// Analog outputAnalogOutput = 3// Digital inputDigitalInput = 2// Digital outputDigitalOutput = 1// Group inputGroupInput = 5// Group outputGroupOutput = 6// The signal type could not be determinedUnknown = 0}
public class IoSignalConfiguration {// Initializes a new instance of the <xref href="UnderAutomation.ABB.Rws.Data.IoSignalConfiguration" data-throw-if-not-resolved="false"></xref> classpublic IoSignalConfiguration()// Whether a local client can write the signal in auto modepublic bool? LocalAuto { get; set; }// Whether a local client can write the signal in manual modepublic bool? LocalManual { get; set; }// Whether a RAPID client can write the signal in both manual and auto modepublic bool? Rapid { get; set; }// Whether a remote client can write the signal in auto modepublic bool? RemoteAuto { get; set; }// Whether a remote client can write the signal in manual modepublic 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 reportedpublic int? SignalBits { get; set; }// Name of the signal, for example "DRV1CHAIN2"public string SignalName { get; set; }// Returns a string representation of this signal configurationpublic override string ToString()}
public class IoSignalSearchCriteria {// Initializes a new instance of the <xref href="UnderAutomation.ABB.Rws.Data.IoSignalSearchCriteria" data-throw-if-not-resolved="false"></xref> classpublic IoSignalSearchCriteria()// Whether only the blocked (simulated) signals are searchedpublic bool? Blocked { get; set; }// Category of the searched signals, for example "safety"public string Category { get; set; }// Category prefix of the searched signalspublic string CategoryPrefix { get; set; }// Name of the device the searched signals are connected topublic string DeviceName { get; set; }// Whether the criteria is inverted: the signals matching it are excluded from the resultpublic bool? Invert { get; set; }// Name of the searched signalspublic string Name { get; set; }// Name of the network the searched signals belong topublic string NetworkName { get; set; }// Returns a string representation of this search criteriapublic override string ToString()// Type of the searched signals, null to search every typepublic IoSignalType? Type { get; set; }}