Reading properties from TwinCAT function blocks using c#

2.3k views Asked by At

We are using a C# application to read variables from a Beckhoff PLC through TwinCAT ADS v.3. If we attempt to utilize the same code to read properties the code fails with an exception.

    FUNCTION_BLOCK FB_Sample
    VAR
       SomeVariable : INT;
    END_VAR
    PROPERTY SomeProp : INT // declared in a separate file
    // Code used to read variable (symbol)
    var handle = client.CreateVariableHandle("sampleProgram.Source.SomeVariable");
    var result = client.ReadAny(handle, typeof(int));
    client.DeleteVariableHandle(handle);
    // Adapted code used to read property (not a symbol)
    var handle = client.CreateVariableHandle("sampleProgram.Source.SomeProp"); // This fails
    var result = client.ReadAny(handle, typeof(int));
    client.DeleteVariableHandle(handle);

When trying to create a variable handle using the above code, we receive TwinCAT.Ads.AdsErrorException: 'Ads-Error 0x710 : Symbol could not be found.'.

Since we knew that METHOD must be marked with {attribute 'TcRpcEnable'} so it can be called with this code:

client.InvokeRpcMethod("{symbolPath}", "{methodName}", {parameters} });

We attempted to use that attribute {attribute 'TcRpcEnable'} on the property as well. Using TcAdsClient.CreateSymbolLoader and looping over all available symbols we discovered that the getter/setter of the property were then marked as rpc-methods.

Console.WriteLine($"Name: {rpcMethod.Name}; Parameters.Count: {rpcMethod.Parameters.Count}; ReturnType: {rpcMethod.ReturnType};");
RpcMethods: 2
Name: __setSomeProp; Parameters.Count: 1; ReturnType: ;
Name: __getSomeProp; Parameters.Count: 0; ReturnType: INT;

But try as we might, we cannot invoke the rpc method:

var propertyResult = client.InvokeRpcMethod("sampleProgram.Source", "__getSomeProp", Array.Empty<object>());
// Throws: TwinCAT.Ads.AdsErrorException: 'Ads-Error 0x710 : Symbol could not be found.'

var propertyResult = client.InvokeRpcMethod("sampleProgram.Source", "__get{SomeProp}", Array.Empty<object>());
// Throws: TwinCAT.Ads.RpcMethodNotSupportedException: 'The RPC method '__get{SomeProp}' is not supported on symbol 'sampleProgram.Source!'

var propertyResult = client.InvokeRpcMethod("sampleProgram.Source", "get{SomeProp}", Array.Empty<object>());
// Throws: TwinCAT.Ads.RpcMethodNotSupportedException: 'The RPC method 'get{SomeProp}' is not supported on symbol 'sampleProgram.Source!'

var propertyResult = client.InvokeRpcMethod("sampleProgram.Source.SomeProp", "get", Array.Empty<object>());
// Throws: System.ArgumentNullException: 'Value cannot be null.
//         Parameter name: symbol'

Any suggestion on how we can read/write variables defined as properties on function blocks?

2

There are 2 answers

2
Filippo Boido On BEST ANSWER

When you define a new property you automatically create a get and a set for that property.

You normally use properties to read or write variables that are in the VAR section of the function block.

All variables that are in the VAR section are private thus the need of properties to access those VARs from outside the Function Block.

Properties in theory should not do any complex calculations or run any logic unlike Methods.

The point I want to make is that you do not need and should not call properties via ADS. You have access to all private VARs via ADS anyway so there is no need to call properties through ADS in the first place.

@Edit

I'm still of the opinion that properties should not contain any logic and therefor there is no need to call them via ADS.

Nevertheless there are always exceptions.

Be aware that according to the Beckhoff documentation, only simple data types and pointers will work, not structures. Moreover "Function monitoring is not possible in the compact runtime system".

Here my working example after experimenting with the {attribute 'monitoring' := 'call'} attribute

In Twincat:

{attribute 'monitoring' := 'call'}
PROPERTY RemoteCall : INT

GET:
RemoteCall := buffer;
SET:
buffer := buffer + RemoteCall;

In C#

    class Program
    {
        static TcAdsClient tcClient;
        static void Main(string[] args)
        {
            tcClient = new TcAdsClient();
            tcClient.Connect(851);
            
            AdsStream dataStream = new AdsStream(2);
            int iHandle = tcClient.CreateVariableHandle("MAIN.fbTest.RemoteCall");
            tcClient.Read(iHandle, dataStream);
            Console.WriteLine("Remote Var before property call: " + BitConverter.ToInt16(dataStream.ToArray(), 0));
            tcClient.WriteAny(iHandle,Convert.ToInt16(2));
            tcClient.Read(iHandle, dataStream);
            Console.WriteLine("Remote Var after property call: " + BitConverter.ToInt16(dataStream.ToArray(), 0));
            Console.WriteLine();
            Console.ReadLine();
        }
    }
2
froeschli On

According to Stefan Hennecken on his Blog the property has to be decorated with a pragma to enable this:

{attribute ‘monitoring’ := ‘call’}
PROPERTY PUBLIC nProp : BYTE

Then it can be read/written with this code sample:

using (AdsClient client = new AdsClient())
{
    byte valuePlc;
    client.Connect(AmsNetId.Local, 851);
    valuePlc = (byte)client.ReadValue(“MAIN.fbFoo.nProp”, typeof(byte));
    client.WriteValue(“MAIN.fbFoo.nProp”, ++valuePlc);
}