Blog registered for “Daj się poznać” contest so I can start countdown until it begins as well as work on project.
To begin with let me explain what I’ll be working on and why I find it useful.
I won’t go into detail what ADS (Automation Device System) is, you can read that at Infosys. What matters is, that it is a protocol that allows you to communicate with TwinCAT. Note that I’ll only focus on access via symbolic variable names.
If we were to read/write, let’s say uint16 variable, it would look like
use client = new TcAdsClient() //connecto do ADS device client.Connect(amsNetId,801) //create variable handle let myuintHandle = client.CreateVariableHandle ".myuint" //read value and cast to short let myuintValue = client.ReadAny(myuintHandle, Operators.typedefof<uint16>) :?> uint16 //write incremented value client.WriteAny(myuintHandle,myuintValue + 1us) (* some other logic *) //remove handle client.DeleteVariableHandle myuintHandle //dispose client client.Dispose()
If you look at highlighted lines, you’ll notice that this code is prone to errors. First of all we pass symbolic name as a string, if we make typo, in runtime we end up with “0x710 symbol not found” exception. Sure we can make it more convenient by wrapping it into generic read methods, but still we have to pay a lot of attention to naming and types. Yep, type names in TwinCAT follow IEC61131-3 specification . Our .NET uint16 is WORD/UINT in TwinCAT, for other types refer to Infosys.
Another tricky thing we can encounter are structures. Sure we can marshal whole structures, but as always, layout is important.
STRUCT lrealVal: LREAL := 1.23; dintVal1: DINT := 120000; END_STRUCT END_TYPE TYPE TComplexStruct : STRUCT intVal : INT:=1200; dintArr: ARRAY[0..3] OF DINT:= 1,2,3,4; boolVal: BOOL := FALSE; byteVal: BYTE:=10; stringVal : STRING(5) := 'hallo'; simpleStruct1: TSimpleStruct; END_STRUCT END_TYPE
[<StructLayout(LayoutKind.Sequential, Pack=1)>] type SimpleStruct = struct val lrealVal: double val dintVal1: int end [<StructLayout(LayoutKind.Sequential, Pack=1)>] type ComplexStruct = struct val intVal: int16 [<MarshalAs(UnmanagedType.ByValArray, SizeConst=4)>] val dintArr: int array [<MarshalAs(UnmanagedType.I1)>] val boolVal: bool val byteVal: byte [<MarshalAs(UnmanagedType.ByValTStr, SizeConst=6)>] val stringVal: string val simpleStruct1: SimpleStruct end
Beside basically repeating same code on different sides, imagine situation where PLC developer slightly refactors his code and changes order of fields, no exceptions thrown but invalid values as a result.
Notice highlighted lines, when declaring .NET counterparts, you have to declare length 1 byte longer then TwinCAT due to string null terminator. That’s yet another thing you have to keep in mind when doing things manually.
I could go on with things we can mess up and end up debugging if we do things manually, but above are main problems I aim to address with this project.
First milestone – Convenient syntax and base classes
As a first milestone we should end up with set of base classes that we can extend manually to suit particular project we work on, and use it in convenient way
Second milestone – Type provider based on *.tpy file
Second milestone is to generate ADS client based on *.tpy file, it is generated when TwinCAT project is compiled, at least that’s default setting. It contains custom types definitions and variables. Format is XML based.