Introduction
When I first explored TwinCAT ADS for C#/.NET, I quickly realised that the official Infosys documentation lacked coherence and omitted essential use cases. Key topics, like reading complex structs, invoking RPC methods, and handling dynamic symbol values, were either incomplete or presented in ways that made practical application challenging.
This book aims to bridge those gaps through clear, example-driven explanations on:
-
Connecting to TwinCAT ADS in .NET – Establish a reliable client for interfacing with TwinCAT PLCs.
-
Dynamic Symbol Management – Accessing and manipulating PLC symbols with flexibility and ease, leveraging dynamic types to avoid rigid, predefined structures.
-
Event Handling – Setting up event-driven reads to monitor symbol changes, connection states, and ADS states, enabling responsive and efficient data interaction.
-
RPC Invocation – Interact directly with function blocks and interfaces on the PLC through remote procedure calls (RPCs), simplifying PLC automation.
I hope by the end of this book, you'll be able to access, control, and manipulate your PLC data in a straightforward and accessible way, so you can focus on building robust, adaptable, high-performance applications.
Prerequisites
To make full use of this guide, ensure you have the following installed and configured:
-
The Beckhoff TwinCAT XAE (eXtended Automation Engineering) or XAR (eXtended Automation Runtime) – Essential for configuring and deploying PLC applications. Both can be downloaded from the Beckhoff download finder webpage.
-
Microsoft .NET SDK 8.0 – Provides the tools and libraries for developing and running .NET applications. To install it via
winget
, run:winget install Microsoft.DotNet.SDK.8
-
An IDE for C# Development – For this book, I used Visual Studio Code with the C# extension and the .NET CLI. If you’re new to this setup, please refer to the Visual Studio Code .NET documentation for additional guidance.
To get started with the .NET CLI in your chosen directory, you can create a new console application with:
dotnet new console -n <ProjectName>
Use the following command to explore additional project templates:
dotnet new list
To build and run your project, use:
dotnet run
A comprehensive guide to the .NET CLI can be found here.
-
Beckhoff TwinCAT ADS NuGet Package – This package allows you to establish an ADS connection to your device.
Install it by running this command in your project’s root directory:
dotnet add package Beckhoff.TwinCAT.Ads
-
Newtonsoft.Json NuGet Package – This package is used for JSON serialisation of dynamic objects, which is particularly helpful for displaying complex symbol values in a structured format.
Install it by running this command in your project’s root directory:
dotnet add package Newtonsoft.Json
Setting Up the ADS Connection
To begin, let’s establish a connection to the PLC. The code below assumes you have an active TwinCAT project ready to connect.For simplicity, I recommend creating a new .NET project, then pasting the code below into your Program.cs
file (or whichever file contains your entry point).
using TwinCAT.Ads;
using (AdsClient client = new())
{
client.Connect(AmsNetId.Local, 851);
Console.WriteLine
(
"Hello there!\n" +
$"You're connected to {client.Address} from {client.ClientAddress}.\n" +
$"The current state of the PLC is: {client.ReadState().AdsState}.\n" +
"\nPress any key to exit...\n"
);
Console.ReadKey(true);
}
This code should produce an output similar to the following when run:
Hello there!
You're connected to 192.168.137.1.1.1:851 from 192.168.137.1.1.1:XXXXX.
The current state of the PLC is: Run.
Press any key to exit...
This code connects to the PLC on the local AMS Net ID with port 851
. It displays the connection details, including the PLC state, which should confirm a successful connection if everything is set up correctly. Pressing any key will then terminate the application.
This setup gives you a basic foundation for communicating with your PLC through ADS in .NET.
Defining the PLC Symbols
Now that we’ve established a connection to the PLC, let's examine the symbols we’ll be working with in this book.
The MAIN
program in the TwinCAT project is defined as follows:
PROGRAM MAIN
VAR
nValue : DINT := 42;
fValue : LREAL := 3.14;
eValue : E_Value := E_Value.Winter;
arValue : ARRAY[0..2] OF LREAL := [273.15, 2.71, 9.80665];
stValue : ST_Value := (bValue := TRUE, sValue := 'Hello there!');
fbValue : FB_Value;
ipValue : I_Value := fbValue;
END_VAR
In this book, we’ll be interacting with a variety of symbols, each representing different data types and complexities, including:
nValue
: A basic integer type.fValue
: A floating-point value (LREAL).eValue
: An enumeration.arValue
: An array containing floating-point numbers (LREAL).stValue
: A structured type that we’ll define below.fbValue
: A function block that includes an RPC method and a property.ipValue
: An interface that includes an RPC method.
Enum Definition
The eValue
variable is an instance of type E_Value
, an enumeration that represents seasons. Here’s its definition:
TYPE E_Value :
(
_ := 0,
Summer,
Autumn,
Winter,
Spring
)UINT;
END_TYPE
Struct Definition
The stValue
variable is an instance of the structured data type ST_Value
. Here’s its definition:
TYPE ST_Value :
STRUCT
bValue : BOOL;
sValue : STRING;
END_STRUCT
END_TYPE
The struct ST_Value
contains a boolean (bValue
) and a string (sValue
). These members allow us to test interactions with complex data types.
Interface Definition
The I_Value
interface defines a method that will enable Remote Procedure Call (RPC) access. Here’s the definition:
INTERFACE I_Value
{attribute 'TcRpcEnable'}
METHOD Sum : LREAL
VAR_INPUT
fA, fB : LREAL;
END_VAR
VAR_OUTPUT
sMessage : STRING;
END_VAR
END_METHOD
END_INTERFACE
The Sum
method is enabled for RPC through the {attribute 'TcRpcEnable'}
pragma, allowing it to be called remotely via ADS. This method takes two LREAL
inputs and returns a sum along with a descriptive message in sMessage
.
Function Block Definition
The fbValue
variable is an instance of the function block FB_Value
which implements the I_Value
interface. This function block will be used to demonstrate remote procedure calls (RPC) and property access through ADS.
The function block FB_Value
includes:
-
An RPC method called
Sum
that calculates the sum of twoLREAL
inputs and returns both the sum and a descriptive string message. -
A property called
Value
that can be accessed and modified remotely. This property uses the{attribute 'monitoring' := 'call'}
pragma to allow read and write operations over ADS.Note: This feature isn’t supported on Windows CE-based devices.
Below is the full implementation of FB_Value
:
FUNCTION_BLOCK FB_Value IMPLEMENTS I_Value
VAR
_fValue : LREAL;
END_VAR
{attribute 'TcRpcEnable'}
METHOD Sum : LREAL
VAR_INPUT
fA, fB : LREAL;
END_VAR
VAR_OUTPUT
sMessage : STRING;
END_VAR
Sum := fA + fB;
sMessage :=
CONCAT('The sum of ',
CONCAT(TO_STRING(fA),
CONCAT(' and ',
CONCAT(TO_STRING(fB),
CONCAT(' is ', TO_STRING(Sum)
)))));
END_METHOD
{attribute 'monitoring' := 'call'}
PROPERTY Value : LREAL
GET
Value := THIS^._fValue;
SET
THIS^._fValue := Value * 2;
END_PROPERTY
END_FUNCTION_BLOCK
- Method:
Sum
: This RPC-enabled method takes twoLREAL
parameters (fA
andfB
) as inputs and returns their sum. The outputsMessage
provides a summary of the calculation in text format, giving both the input values and the result. - Property:
Value
: This property provides controlled access to the internal_fValue
variable. In theGET
accessor, it simply returns the current_fValue
. In theSET
accessor, it doubles the input value before storing it back into_fValue
.
Interacting with PLC Symbols
Now that we’ve defined the symbols, we can begin interacting with them in C# .NET.
In this section, we’ll use the .NET Dynamic Language Runtime (DLR) to create dynamic objects representing each PLC symbol. These objects mirror the structure and data of the symbols on the PLC, including standard IEC 61131 types. For example, the PLC symbol "MAIN.nValue"
can be accessed as MAIN.nValue
in C#, allowing straightforward interaction with your PLC’s variables.
Importing Required Libraries
Before we begin we’ll need to import several essential libraries. These libraries provide tools for establishing ADS communication, loading symbols dynamically, managing the PLC’s symbol system, and handling JSON data if required.
Below are the required imports:
using TwinCAT;
using TwinCAT.Ads;
using TwinCAT.Ads.TypeSystem;
using TwinCAT.TypeSystem;
using Newtonsoft.Json;
Loading Symbols with the Symbol Loader
To interact with PLC variables, we first need to load the symbols using a dynamic symbol loader.
The following code uses the SymbolLoaderFactory
to create and return an instance of an object that implements IDynamicSymbolLoader
internally. This object which provides access to the DynamicSymbolsCollection
. A collection holds all the symbols available in the PLC, allowing us to browse and interact with them dynamically:
var symbolLoader = (IDynamicSymbolLoader)SymbolLoaderFactory.Create
(
client,
new SymbolLoaderSettings(SymbolsLoadMode.DynamicTree)
);
var symbols = (DynamicSymbolsCollection)symbolLoader.SymbolsDynamic;
To list the top-level PLC symbols, use the following snippet:
foreach (var symbol in symbols)
{
Console.WriteLine(symbol.InstancePath);
}
Assuming a fresh TwinCAT project is active, this should output the following symbols in the console:
Constants
Global_Version
MAIN
TwinCAT_SystemInfoVarList
These top-level symbols represent either global variable lists (GVLs) or programs (PROGRAMs) in the PLC. From here, you can drill down further into specific PROGRAMs or GVLs to explore their contained variables, properties and methods.
Accessing the MAIN Program Symbol
The MAIN
program symbol usually represents the primary program block in our TwinCAT PLC setup.
To retrieve the MAIN
program symbol from the DynamicSymbolsCollection
, we can use either of these commands:
dynamic MAIN = symbols["MAIN"];
or more succinctly:
dynamic MAIN = symbols.MAIN;
Here, we're creating a DynamicSymbol
object representing MAIN
using the Dynamic Language Runtime (DLR). This approach allows us to interact with symbols flexibly. However, since it’s a dynamic type, IntelliSense won't provide auto-suggestions for properties or methods. Please, familiarising yourself with the DynamicSymbol
documentation. It will be useful as you work with these objects.
To inspect the symbols under MAIN
, we can iterate over its SubSymbols
property. This provides a list of all variables and data structures contained within MAIN
, which will vary depending on your PLC configuration:
foreach (var symbol in MAIN.SubSymbols)
{
Console.WriteLine(symbol.InstancePath);
}
If you've setup your TwinCAT project using the symbols we defined earlier, the above code snippet should output:
MAIN.arValue
MAIN.eValue
MAIN.fbValue
MAIN.fValue
MAIN.ipValue
MAIN.nValue
MAIN.stValue
Complete Code Example
Now that we've explored accessing the MAIN
program symbol and listing its sub-symbols, let’s put it all together in a full example. This complete code snippet covers each step we've discussed: connecting to the PLC, loading symbols dynamically, and accessing the MAIN
program symbol and its contents.
The code example below demonstrates how to import the necessary libraries, establish a connection to your device, load the available symbols using the SymbolLoaderFactory
, and list both the top-level symbols and the sub-symbols within MAIN
.
using TwinCAT;
using TwinCAT.Ads;
using TwinCAT.Ads.TypeSystem;
using TwinCAT.TypeSystem;
using (AdsClient client = new())
{
client.Connect(AmsNetId.Local, 851);
var symbolLoader = (IDynamicSymbolLoader)SymbolLoaderFactory.Create
(
client,
new SymbolLoaderSettings(SymbolsLoadMode.DynamicTree)
);
var symbols = (DynamicSymbolsCollection)symbolLoader.SymbolsDynamic;
foreach (var symbol in symbols) Console.WriteLine(symbol.InstancePath);
Console.WriteLine();
dynamic MAIN = symbols["MAIN"];
foreach (var symbol in MAIN.SubSymbols) Console.WriteLine(symbol.InstancePath);
Console.WriteLine("\nPress any key to exit...\n");
Console.ReadKey(true);
}
Running this code will display the following:
Constants
Global_Version
MAIN
TwinCAT_SystemInfoVarList
MAIN.arValue
MAIN.eValue
MAIN.fbValue
MAIN.fValue
MAIN.ipValue
MAIN.nValue
MAIN.stValue
Press any key to exit...
This is a list of the top-level symbols, followed by all sub-symbols within MAIN
, demonstrating how to examine the structure of your PLC program and begin working with its data. This setup provides a solid foundation for the dynamic handling of symbols that will be used the following sections of this book.
Reading and Writing Values from PLC Symbols
In this section, we’ll explore how to retrieve and set values for various PLC symbols, focusing on those defined in the MAIN
program from earlier sections using the dynamic symbol loader. Remember, it’s essential that the names of dynamic symbols in C# .NET precisely match those in the PLC program. For instance, the symbol "MAIN.nValue"
in the PLC is accessed as MAIN.nValue
in .NET.
We’ll begin by working with primitive types, then move on to more complex data structures, demonstrating how to interact with each type through dynamic symbols.
Primitives
Primitive types, such as booleans, integers, and floating-point numbers, are handled in TwinCAT ADS .NET library by instances of the DynamicSymbol
class. To retrieve values from these symbols, call the ReadValue()
method. Assigning the output to a typed variable ensures type safety, enhances readability, and minimises round-trips to the PLC. For updating values, the WriteValue(Object)
method allows you to set new values by passing the desired data as a parameter.
Reading Primitive Values
Here’s an example of reading primitive values from the MAIN
program:
dynamic MAIN = symbols["MAIN"];
int plcIntValue = MAIN.nValue.ReadValue();
double plcDblValue = MAIN.fValue.ReadValue();
Writing Primitive Values
To modify these values, use WriteValue()
as shown below:
MAIN.nValue.WriteValue(888);
MAIN.fValue.WriteValue(6.626);
Enums
Enums, similar to primitive types, are represented by instances of the DynamicSymbol
class. Reading an enum’s value involves calling the ReadValue()
method. Since PLC enums are represented by their underlying numeric type (e.g., byte
, ushort
, int
, etc.), it’s best to specify the numeric type directly in the PLC code to maintain consistency between the PLC and .NET.
Here’s an example of an enum type in Structured Text with UINT
as its underlying type:
TYPE E_Value :
(
_ := 0,
Summer,
Autumn,
Winter,
Spring
) UINT;
END_TYPE
This setup allows for consistent casting to the correct numeric type in .NET when reading or writing values.
Reading Enum Values
To read an enum value, use the ReadValue()
method and cast it to the specified type:
dynamic MAIN = symbols["MAIN"];
ushort plcEnumValue = (ushort)MAIN.eValue.ReadValue();
Writing Enum Values
To set an enum value, use the WriteValue(Object)
method. You can either pass the integer representation directly or use .NET features to translate the enum member to its numeric value.
Here’s how to set eValue
to Autumn
, which corresponds to 2
:
MAIN.eValue.WriteValue(2);
Alternatively, if you prefer converting an enum name to its numeric equivalent, the IDataTypeCollection
and IEnumType
interfaces allow for parsing:
var enumType = (IEnumType)symbolLoader.DataTypes["E_Value"];
var enumValue = (ushort)enumType.Parse("Autumn");
MAIN.eValue.WriteValue(enumValue);
Accessing Enum Names and Values
The IEnumType
interface also provides convenient access to the enum members’ names and values:
Names: Retrieve an array of member names:
string[] enumNames = enumType.GetNames();
Values: Retrieve an array of the corresponding numeric values as IConvertible[]
:
IConvertible[] enumValues = enumType.GetValues();
Arrays
Arrays are represented as instances of the DynamicArrayInstance
class, supporting read and write operations for both individual elements and entire arrays.
Reading Arrays
To read an entire array, use the ReadValue()
method:
dynamic MAIN = symbols["MAIN"];
double[] plcDblArray = MAIN.arValue.ReadValue();
To retrieve a single element in an array, there are multiple approaches. Each of these reads the first element:
MAIN.arValue[0].ReadValue();
MAIN.arValue.Elements[0].ReadValue();
MAIN.arValue.SubSymbols[0].ReadValue();
Using MAIN.arValue[0]
is recommended, particularly for multidimensional arrays, as it simplifies syntax.
Writing to Array Elements
To modify a single element, use the WriteValue(Object)
method:
MAIN.arValue[0].WriteValue(6.626);
Attempting to update an entire array in a single call will raise an ArgumentOutOfRangeException
due to current limitations in WriteValue(Object)
for array symbols:
// This will cause an error
MAIN.arValue.WriteValue(new double[] { 1.602, 6.022, 1.380 });
Instead, each element must be updated individually:
double[] newValues = { 1.602, 6.022, 1.380 };
for (int i = 0; i < Math.Min(newValues.Length, MAIN.arValue.Elements.Count); i++)
MAIN.arValue[i].WriteValue(newValues[i]);
While functional, this approach involves multiple write operations, which may be less efficient. If this behaviour appears to be a limitation, consider reaching out to Beckhoff Technical Support for clarification.
Tip: For multidimensional arrays, extend this approach by adding nested loops for each dimension.
Handling Non-Zero-Based Arrays
The IEC-61131-3 standard permits arrays with custom, non-zero-based bounds. To interact with these arrays, use their specific indices:
double plcArrayValue = MAIN.arNewValues[-1].ReadValue();
MAIN.arNewValues[-1].WriteValue(10_973_731.568160);
Array Metadata with Dimensions
The Dimensions
property offers valuable metadata about an array, such as bounds, dimensions, and whether it’s non-zero-based:
int[] lowerBounds = MAIN.arValue.Dimensions.LowerBounds;
int[] upperBounds = MAIN.arValue.Dimensions.UpperBounds;
int numOfDimensions = MAIN.arValue.Dimensions.Count;
int[] dimensionLengths = MAIN.arValue.Dimensions.GetDimensionLengths();
bool isNonZeroBased = MAIN.arValue.Dimensions.IsNonZeroBased;
foreach (var dim in MAIN.arValue.Dimensions) Console.WriteLine(dim.ElementCount);
The Dimensions
property is especially useful when working with complex arrays of various sizes and bounds, providing flexibility in managing diverse array structures.
Structs
Structs are represented by the DynamicStructInstance
class, which enables both reading and writing operations for individual members or the entire structure.
Reading Structs
To retrieve all members of a struct at once, use the ReadValue()
method:
dynamic MAIN = symbols["MAIN"];
dynamic plcStructValue = MAIN.stValue.ReadValue();
Individual struct members can be accessed directly by name:
bool plcBoolValue = MAIN.stValue.bValue.ReadValue();
string plcStrValue = MAIN.stValue.sValue.ReadValue();
Writing to Struct Members
To modify specific struct members, use the WriteValue(Object)
method:
MAIN.stValue.bValue.WriteValue(false);
MAIN.stValue.sValue.WriteValue("General Kenobi!");
Limitations of Writing Entire Structs
Attempting to write an entire struct at once using an anonymous object will raise an error, as shown below:
// This produces an error: "Struct member 'bValue' (of ValueType: <>f__AnonymousType0`2) not found!"
MAIN.stValue.WriteValue(new
{
bValue = true,
sValue = "Another happy landing!"
});
This error occurs because DynamicValueMarshaler
expects either a DynamicValue
or a compatible ADS struct type, not a .NET anonymous object. If writing an entire struct in one call would be useful for your application, consider contacting Beckhoff Technical Support to confirm if this behavior is a limitation or intended.
Note: If you find an alternative approach or improvement, feel free to submit a pull request to enhance this guide for other developers!
Function Blocks
Function blocks can be accessed similarly to structs, allowing access to both local variables and properties. Properties marked with the {attribute 'monitoring' := 'call'}
pragma can be accessed just like struct members. When properties contain code within their getter or setter blocks, that code will execute upon reading or writing to the property directly.
Reading Function Blocks
To retrieve the entire function block, use the ReadValue()
method:
dynamic MAIN = symbols["MAIN"];
dynamic plcFBValue = MAIN.fbValue.ReadValue();
Once you have retrieved the function block as a whole, you can access its local variables directly as shown below:
double localValue = plcFBValue._fValue;
You can also retrieve property values using this method, but be aware that doing so will NOT trigger any logic within the getter. The following code retrieves the property value without executing its getter logic:
double propValue = plcFBValue.Value;
To ensure that the getter logic is called, you need to access the property directly and use the ReadValue()
method, as shown here:
double propValue = MAIN.fbValue.Value.ReadValue();
You can also directly read the value of any function block local variable:
double localValue = MAIN.fbValue._fValue.ReadValue();
Writing Function Blocks
The only way to write to function block local variables and properties is to use WriteValue(Object)
directly on them:
MAIN.fbValue._fValue.WriteValue(2001);
MAIN.fbValue.Value.WriteValue(66);
You cannot modify PLC symbols in a function block that has been retrieved as a whole:
// Compiles but does nothing.
plcFBValue._fValue = 1999.0;
plcFBValue.Value = 300.0;
Handling Events
At times you may need to respond to changes in the ADS device’s state or track updates to specific symbol values dynamically. The TwinCAT ADS .NET library provides several built-in events that allow you to do just that. This section demonstrates how to handle connection state changes, monitor ADS state transitions, and listen for updates to symbol values.
Monitoring Connection State Changes
The ConnectionStateChanged
event allows you to track changes in the connection status of the ADS client. This is useful for managing connectivity and taking appropriate actions when the connection to the PLC is established or lost.
Note: The connection state changes only when the client actively connects to or disconnects from the PLC, or when communication is initiated by the user.
The code below demonstrates how to register an event handler to the ConnectionStateChanged
event:
var connectionStateChangedHandler = new EventHandler<ConnectionStateChangedEventArgs>
(
(sender, e) =>
{
Console.WriteLine($"Client connection state: {e.NewState}.");
}
);
client.ConnectionStateChanged += connectionStateChangedHandler;
Listening to ADS State Changes
The AdsStateChanged
event enables you to monitor when the ADS client transitions between different operational states. This event is beneficial for tracking ADS state transitions and responding to conditions where the client moves between states such as Run
, Stop
, or Error
.
The code below demonstrates how to register an event handler to the AdsStateChanged
event:
var adsStateChangedHandler = new EventHandler<AdsStateChangedEventArgs>
(
(sender, e) =>
{
Console.WriteLine
(
$"ADS state changed. New state: {e.State.AdsState}, " +
$"Device state: {e.State.DeviceState}"
);
}
);
client.AdsStateChanged += adsStateChangedHandler;
Listening for Value Changes
One of the most valuable features of the ADS client is its capability to track real-time updates to PLC symbols via the ValueChanged
event. This event notifies you whenever a subscribed symbol’s value changes, making it essential for applications that need to respond to dynamic data.
The code below demonstrates how to register an event handler to the ValueChanged
event:
var valueChangedHandler = new EventHandler<ValueChangedEventArgs>
(
(sender, e) =>
{
dynamic val = e.Value;
Console.WriteLine
(
$"Value of {e.Symbol.InstancePath} changed to " +
(e.Symbol.IsPrimitiveType ? val : JsonConvert.SerializeObject(val))
);
}
);
MAIN.fValue.ValueChanged += valueChangedHandler;
MAIN.stValue.ValueChanged += valueChangedHandler;
Best Practices
-
Minimise Processing in Handlers: Keep event handlers efficient to avoid performance bottlenecks. Offload intensive tasks to a separate thread or process if needed.
-
Unsubscribe Appropriately: Always unsubscribe from events when they are no longer needed to prevent memory leaks.
-
Ensure Thread Safety: If your application is multi-threaded, ensure that event handlers are thread-safe and can handle concurrent updates properly.
Remote Procedure Calls (RPCs)
Remote Procedure Calls (RPCs) provide a powerful way to interact with PLC function blocks and interfaces by invoking methods directly from your .NET client application. This approach is particularly useful for handling structured data and executing business logic encapsulated within the PLC, allowing for streamlined communication and control.
In the TwinCAT ADS .NET library, function blocks and interfaces are represented as dynamic types (DynamicStructInstance
for function blocks and DynamicInterfaceInstance
for interfaces). DynamicStructInstance
inherits from DynamicInterfaceInstance
, giving both classes shared access to RPC-related properties and methods.
This section will guide you through checking for RPC support, listing RPC methods, and invoking them to extend your control over PLC operations.
Checking for RPC Methods
Before attempting to invoke an RPC method, it’s helpful to confirm whether a PLC symbol supports RPC functionality. Both DynamicInterfaceInstance
and DynamicStructInstance
classes include an HasRpcMethods
property, which returns true
if RPC methods are available for the symbol. Checking this property can help you avoid errors from attempting to call unsupported methods.
Here’s how you can check if a function block or interface has RPC methods:
var formatYesNoResponse = (bool answer) => answer ? "Yes" : "No";
Console.WriteLine
(
$"Does {MAIN.fbValue.InstancePath} have RPC methods?: " +
formatYesNoResponse(MAIN.fbValue.HasRpcMethods)
);
Console.WriteLine
(
$"Does {MAIN.ipValue.InstancePath} have RPC methods?: " +
formatYesNoResponse(MAIN.ipValue.HasRpcMethods)
);
In this example, the HasRpcMethods
property provides a straightforward way to determine whether a given symbol, such as fbValue
or ipValue
, supports RPC calls. If HasRpcMethods
returns true
, you can proceed with further steps, such as listing and invoking available RPC methods.
Listing Available RPC Methods
You can retrieve a list of available methods using the RpcMethods
property. This property provides an IRpcMethodCollection
, containing instances of IRpcMethod
for each RPC method available on the symbol. Each IRpcMethod
instance provides details like the method name, parameters, return type, and additional comments, making it easy to inspect and understand the functionality of each RPC method.
The following example demonstrates how to list the available RPC methods for two PLC symbols, fbValue
and ipValue
:
Console.WriteLine("RPC Methods for fbValue:");
foreach (IRpcMethod method in MAIN.fbValue.RpcMethods)
{
Console.WriteLine(method.Name);
}
Console.WriteLine("\nRPC Methods for ipValue:");
foreach (IRpcMethod method in MAIN.ipValue.RpcMethods)
{
Console.WriteLine(method.Name);
}
Inspecting RPC Method Parameters
Each RPC method may have parameters, which you can inspect using the Parameters
property of an IRpcMethod
instance. This property provides a collection of IRpcMethodParameter
instances, where each instance represents a specific parameter with details like its name and type.
For example, to view the parameters of the Sum
method on ipValue
, use the following code:
Console.WriteLine("\nParameters for the 'Sum' method on ipValue:");
foreach (IRpcMethodParameter param in MAIN.ipValue.RpcMethods["Sum"].Parameters)
{
Console.WriteLine($"Parameter Name: {param.Name}, Type: {param.TypeName}");
}
In this example, the Parameters
property allows you to access and display each parameter’s name and type, which is useful for understanding what inputs the RPC method expects. This step ensures that you supply the correct parameter types and values when you invoke the method.
Invoking RPC Methods
The ADS .NET library provides two approaches to call an RPC method:
- Using the
InvokeRpcMethod
method — This method provides flexibility, especially when handling output parameters, and ensures compatibility with the RPC’s structure. - Direct method invocation syntax — For conciseness, if you don’t require handling output parameters.
Using InvokeRpcMethod
To call an RPC method using InvokeRpcMethod
, provide the name of the RPC method, an array containing input parameters, and an optional array for output parameters. This method is suitable when you need precise control over both input and output parameters.
object[] inParams = { 1, 2 };
object[] outParams;
double result = MAIN.ipValue.InvokeRpcMethod("Sum", inParams, out outParams);
Console.WriteLine($"Sum result: {result}");
Console.WriteLine($"Output message: {outParams[0]}");
Using Direct Method Invocation Syntax
For a more concise call, you can use direct method invocation syntax. This approach simplifies the code, although it may not populate output parameters as expected.
string message;
double result = MAIN.ipValue.Sum(1, 2, out message);
Console.WriteLine($"Sum result: {result}");
Console.WriteLine($"Output message: {message}");
Note: When using direct invocation syntax, output parameters might not be populated. This may be a limitation in the ADS .NET library, so consider reaching out to Beckhoff Technical Support for clarification. If output parameter values are essential, it’s recommended to use
InvokeRpcMethod
instead for reliable handling of output data.
Conclusion
As you continue to explore and work with TwinCAT ADS, remember that Beckhoff's extensive documentation and community resources are valuable allies in expanding your knowledge and troubleshooting challenges. The ADS ecosystem is continually evolving, with regular updates introducing new features and addressing limitations. Staying informed about these developments will ensure your applications remain at the forefront of industrial automation.
For questions, ideas, or further discussion, feel free to use the discussions section of the GitHub repository: GitHub Discussions
If you’d like to contribute improvements to this book, please submit a pull request.
If you encounter any issues, please raise a GitHub issue, and we’ll address it as soon as possible.
Here are additional resources for further assistance:
- TwinCAT Forum LinkedIn Group
- TwinControl Forum
- TwinCAT Tutorials and Articles
- CooknCode - TwinCAT Articles
- TwinCAT ADS.NET V6 documentation
I hope this guide has helped you get started with TwinCAT ADS in the .NET environment, and I wish you success in your future automation projects.
Contributors
Here is a list of the contributors who have helped improving this guide. Big shout-out to them!
If you feel you're missing from this list, feel free to add yourself in a PR.