Skip to Content

author

Author: Paweł Paluch

Introduction to LIN in .NET F#

Next edition of  “Get Noticed!” started, and it’s my third attempt to write a blog in scope of one year, you know what they say, third time’s a charm. Little inspired by idea in post by fellow contender, I’m putting X amount for first post a week, and twice X for second in week, to “pleasure budget” that I can spend without remorse. We’ll see where that leads me. The goal of the project is to create “yet undefined” set of tools to make my work with LIN, a little easier and faster. The bread idea is to have tool generating .NET/F# client based on LDF (a little more on that in next post). Let’s start with short introduction to LIN.

What is LIN?

LIN (Local Interconnect Network) is a low-end and low-cost,  single wire Serial Network Protocol that is used in automotive networks to communicate between vehicle components. It consists of 1 master and up to 15 slaves.

LIN message

Each message is identified by ID. Valid IDs are within range <0,63>, where 0 to 56 are used for signal carrying, 60 and 61 for diagnostics, 62 for user defined extensions, and 64 reserved for future enhancements.  on lower 6 bits, while the two upper bits contain parity.

Parity calculation

 
let calcPID ID =
  let ID0 = ID &&& 1uy
  let ID1 = ID >>> 1 &&& 1uy 
  let ID2 = ID >>> 2 &&& 1uy 
  let ID3 = ID >>> 3 &&& 1uy 
  let ID4 = ID >>> 4 &&& 1uy 
  let ID5 = ID >>> 5 &&& 1uy 
  let P0 = ID0 ^^^ ID1 ^^^ ID2 ^^^ ID4
  let P1 = ~~~(ID1 ^^^ ID3 ^^^ ID4 ^^^ ID5) &&& 1uy
  ID ||| (P0 <<< 6) ||| (P1 <<< 7)
 

Data

Message data consists from at least 1 byte and up to 8, and checksum. LIN 1.x uses Classic Checksum, while LIN 2.x uses Enhanced Checksum with the exceptions of messages with IDs from 60 to 63 which should be calculated with Classic Checksum.

Classic and Enhanced Checksum

Classic checksum is calculated using only message data, while in Enhanced Checksum we also include Protected ID, In order to calculate checksum we sum all bytes, subtracting  255 each time sum is greater or equal to 256, and then invert resulting value.

 
let carry255 a b = 
  match a + (b |> uint16) with 
    | s when s > 255us -> s-255us 
    | s -> s
let calcEnhancedChecksum pid = List.fold carry255 (pid |> uint16) >> byte
let calcClassicChecksum = calcEnhancedChecksum 0uy 
 

Sample Device

Before we continue, let’s describe imaginary example device will be working with further. Let’s say it’s comfort system with following functions:

  • run massage, 12 air cells in 2 rows, 6 cells each. In order to run it we select:
    • one of 3 predefined programs – varying in order of cell inflating/deflating
    • one of 3 intensity levels
    • one of 5 speed levels
  • inflate, deflate:
    • 2 lumbar cells – operating in 4 modes FORWARD (inflate both lumbar cells), BACKWARD (deflate both lumbar cells), UP (inflate lumbar 1 and deflate lumbar 2), DOWN (deflate lumbar 1 and inflate lumbar 2)
    • 2 cushions bolsters – inflate/deflate both at the same time
    • 2 back bolsters – inflate/deflate both at the same time

To give a picture how such a system could look in reality, it could consist of:

  • pump
  • 2 valve blocks, each having 4 bidirectional valves inflate+deflate
  • 1 valve block, having 6 bidirectional valves inflate+deflate

where 6VB would be LIN slave, and master for both 4VB and pump, in other words, we would only communicate with 6VB, while 6VB would then delegate “some work” to two 4VB and pump, on a protocol beyond our concern.

For the convenience of our diagnostics let’s add few more functions to the system:

  • massage status
    • is massage running
    • currently inflating/deflating air cells
  • lumbar and bolster status:
    • is pump running
    • is valve working
  • direct operations on pump and valves (to give example when such would come handy, we could need to test power consumption while operating pump and valves):
    • pump speed 0-100%
    • inflate/deflate singe valve in each valve block

In love with Nancy (F# and React too)

For some time I’m switching all my development from C# to F# (because of reasons…), and next on the list was Web development. Attempts at using ASP.NET MVC combined with F# were failures, mostly because I only switch language, not a general approach and still tried to use Razor, AutoMapper, Castle.Windsor and nHibernate, and as such I was bound to fail. Recently I came across Nancy and React, and decided to give them a try. For starters I recommend F# and Nancy – beyond Hello World by Michał Franc, I’ll add few notes to that.

Static content and folders

Though you may create folders for static content in your F# project in VS with F# power tools, it’s pain in the ass to work with them, manually sorting folders contents in *.fsproj file isn’t the most productive way to start work when reopening visual studio after some minor project structure changes. What I also noticed is VS slowing down.

VS Code and symbolic link

To avoid using folders I prefer to use VS code for non F# coding (CSS, JS, so on) and creating symlinks to static content in order to avoid manually coping files after change, or creating additional build scripts.

for bootstrapper and module looking like:

 
type Bootstrapper() as __ =
  inherit DefaultNancyBootstrapper()
  do 
    #if DEBUG
    Nancy.StaticConfiguration.DisableErrorTraces <- false
    #endif

  override __.ConfigureConventions nancyConventions =
    base.ConfigureConventions(nancyConventions)
    nancyConventions.StaticContentsConventions.Add(StaticContentConventionBuilder.AddDirectory("/Scripts",null))
    nancyConventions.StaticContentsConventions.Add(StaticContentConventionBuilder.AddDirectory("/Content",null))
    nancyConventions.StaticContentsConventions.Add(StaticContentConventionBuilder.AddDirectory("/App",null))
 
 
type App() as this =
  inherit NancyModule()
  do
    this.Get.["/"] <- fun _ ->
      this.View.["Index.html"] :> obj
 

we’ll need following symlinks:

 
mklink Index.html "..\..\..\Static\Index.html"
mklink /D Content "..\..\..\Static\Content"
mklink /D App "..\..\..\Static\App"
mklink /D Scripts "..\..\..\Static\Scripts"
 

In order to create them, open Command Prompt as Administrator and use mklink command, supplying link name as first argument, and path to actual file/directory as second.

Note: I only create symlink to one view, Index.html, as in following posts I’ll be using React, and single view is sufficient.

Running our application

Self Hosted Nancy

Fastest way to run application is to install Nancy.Hosting.Self NuGet package and run in console

 
[<EntryPoint>]
let main _ = 
  let nancy = new Nancy.Hosting.Self.NancyHost(new Bootstrapper(), new Uri("http://localhost:8100"))
  nancy.Start()
 
  while Console.ReadLine() <> "x" do ()
  0 

Source code, for this and posts that will follow available at:


https://github.com/devanarch/in-love-with-nancy
0 forks.
1 stars.
0 open issues.
Recent commits:

Installing TwinCAT 3 XAE on Windows 10 x86 VM

Installing TwinCAT ain’t always issue free download->install->restart->run. Such was my case with following clean setup on Virtual Machine:

  • Windows 10 Enterprise 32bit
  • TwinCAT 3.1 Build 4020

where I received this nice error message: “the installation of microsoft visual studio 2013 integrated shell has failed

First thought, restart and try again, usually worked on another errors on different configurations, but not this time.

Visual Studio 2013 Integrated Shell

Download this small installer here and run. It’ll probably end up with “Windows Program Compatibility mode is on. Turn it off and then try Setup again.” such as in my case. To save you googleing, copy installer to another location and rename to VS_ISOSHELL, that should solve the problem.

Next time you run TwinCAT XAE installer, it should detect existing VS2013 shell, as it would with any other installed VS > 2010, and offer to integrate it.

Existing VS shell integration

When installing TwinCAT XAE on system with existing VS installation, be aware that it will most probably mess up your VS window settings.

Reboot

Some time has passed since the last post… actually a long time.

Quick DajSiePoznac summary

Let’s be honest, it was an utter failure. At some point, due to participating in too many projects I faced hard choice regarding my spare time, bloggers fame and glory or sleep, I choose the letter, yet had none of the above. Pure magic. When it comes to visitors, one is sure, bots love my writing. They visit and comment everyday, day and night (if only those comments made any sense). Beside them… well… even saying friends and family is exaggeration.

What’s next?

Since I don’t have enough time to finish library I started to write for DajSiePoznac, I’ll focus on things I use, and will use in following months. Which may be interesting for some (doubt that). By coincidence I will have to learn some new stuff, and learn it fast. Google based crash course you may say, and what’s better way to understand stuff, than explaining it to another?

Real-Time, TwinCAT 3 and LIN

So far I wrote CAN/LIN communication software in C#, and lately in F#. On a horizon, I have project which requires hard RT, and thus .NET is not an option (damn you Garbage Collector!!!).

CLR GC is a poor choice for hard RT

Before you argue, if an actual hard real-time is needed for LIN communication, or it can be worked around for performance, ask yourself, would you’d like to end up with premium car with incorrectly calibrated seat-heater, just because GC kicked-in. I thought so.

I’m kind of excited when I think of it.

  • new programming language and environment (so far I only know how to do pretty basic, PASCAL-like stuff in  TwinCAT 2)
  • new hardware (never worked with Beckhoff EL6001  Serial Interface Terminal before)
  • tight timing for operations

how bad can it get?

Alternatives

Haskell

So far I considered few alternatives, Haskell EDSLs Ivory and Atom, as funprog is jazzy, but no one I know could confirm it’s actually suited for my application, it would mean writing additional wrappers for manufacturer provided drivers, for Kvaser LIN Leaf or Vector VN1611, plus I already have to failed attempts to learn Haskell.

Java

There seems to be something called RTSJ (Real-Time Specification for Java), which is claimed to be used in South Korean T-50 jet trainer. But it’s more like curiosity for me. I just don’t trust anything that’s named Java, JVM or based on it.

C++

No introduction required, best candidate. But where’s the fun? The journey into unknown?

TL;DR

DajSiePoznac was a failure. Expect “LIN/TwinCAT for dummies by dummy”, unless again I’ll have to pick between sleep and blog.

Setting up development environment

In order to run software communicating with TwinCAT one needs actual runtime. If you can get a hold of actual PLC that’s great! In other cases you’ll need to setup it on your computer, in this post we’ll focus on setting in up on VM. VirtualBox to be precise.

Development environment

I think it’s safe to assume that today’s development machines consist mostly of x64 OS, thus our final setup will be:

  • x64 Windows host
  • x32 Windows VM
  • TwinCat 2

Note, that it’s not possible  to run TwinCAT 2 runtime on x64 host, we’ll need TwinCAT engineering for that. I assume you’re familiar with basic VirtualBox VM setup.

Downloading TwinCAT

Go to beckhoff download page and download TwinCAT 2.11 R3 and TwinCAT 2.11 x64 Engineering. After installation TwinCAT 2.11 R3 works for 30 days, but later on you can reinstall. For our purposes we’ll go with installation as Next-clicking until it’s finished. Remember TwinCAT 2.11 R3 is for VM and TwinCAT 2.11 x64 Engineering is for host.

Configuring network

For your VM select Host-only adapter.

Adding route

After installing TwinCAT and configuring network you should setup route between host and VM TwinCAT instalations. To do so:

  • click on TwinCAT tray icon
  • then “System Manager”
  • “Choose target”
  • “Search (Ethernet)…”
  • “Broadcast Search”
  • wait till search finishes, you should see 2 runtimes available, host and VM
  • Choose “IP Address” radio button for “Address info:”
  • click “Add route”
  • popup will appear, you should provide credentials for VM user with administrative privileges and confirm by clicking OK.
  • close “Add route dialog”
  • on a treeview select VM and click OK

After doing so you should be able to put VM TwinCAT in run mode, and upload program from TwinCAT PLC Control (TwinCAT tray icon –> “PLC Control”)

Adding route manually

if you’d want to add route manually, for whatever may your reason be:

  • click on TwinCAT tray icon and select properties
  • go to AMS Router tab
  • click Add
  • provide remote Machine name, Ams Net Id, and IP address  or click browse and search through your local network (note: that your probably won’t be able to read remote machine Ams Net Id via “Browse” due to 0x80070005 Access Denied exception)
  • repeat for both host and VM

Reading Ams Net Id of remote machine via Browse

In order to read remote machine AmsNetId, you’ll need to do some more configuration, at least that was my case, though you’ll probably won’t need read it anyway.

  1. make sure user accounts on VM and host have same login and password
  2. enable DCOM access rights (VM + host)
  3. enable WMI namespace access rights (VM + host)
  4. verify WMI service is running (VM + host)
  5. enable local security policy (VM + host)
DCOM access rights
  1. run dcomcnfg from Start –> Run
  2. enter “Component Services” –> “Computers”
  3. right click on “My Computer” and select “Properties”
  4. go to “Com Security” tab
  5. for both “Edit Limits” and “Edit Default” under “Access Permissions”
    1. click “Add”
    2. find user you’ll be connecting
    3. check “Local Address” and “Remote Address” boxes
    4. “Apply”
  6. click “OK”
WMI namespace access rights
  1. run wmimgmt.msc from Start –> Run
  2. right click “WMI Control” and select “Properties”
  3. go to “Security” tab
  4. click “Security” and then “Advanced”
  5. find user you’ll be connecting and lick “Edit”
  6. check “Execute Methods”, “Enable Account” and “Remote Enable”
  7. click “Apply” and then “OK”
verify WMI service is running
  1. run services.msc from Start –> Run
  2. scroll to “Windows Management Instrumentation”
  3. right click on it and verify if started and set to “Automatic”
enable local security policy
  1. run secpol.msc from Start –> Run
  2. select “Local Policies” –> “Security Options”
  3. scroll to “Network access: Sharing and security model for local accounts”
  4. right click and select “Properties”
  5. select “Classic – local users authenticate as themselves”
  6. confirm with “OK”
  7. restart

Running TwinCAT program

You can pickup sample program from #DSP project or write your own, whichever you choose, to run it:

  • Online –> Choose Run-Time System
  • Select available port from VM and click OK
  • Online –> Login (F11) to login with program
  • Online –> Run (F5) to run it

Obstacles when communicating with TwinCAT from F# (plus Workarounds)

I took a detour from main goal of project, which is type provider, in order to provide with lightweight fsharpish library for ADS communication. To my surprise a lot of drawbacks popped out, here you may find a descriptions of them, plus workarounds for some.

Marshalling structures with BOOL arrays.

On one hand, according to Infosys BOOL type has size of 1 byte (where 1 is true, 0 is false, while any other value is incorrect as it can’t be interpreted correctly), same 1 byte size for bool is in .NET. On the other hand, we have Win32 BOOL, which is 4 byte size (more detailed on SO).  Which is scenario we can easily handle in C#, but not in F#.

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct BoolArrSample
{
  [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10, ArraySubType = UnmanagedType.I1)]
  public readonly bool[] values;
}
.class public sequential ansi sealed beforefieldinit Structs.BoolArrSample
  extends [mscorlib]System.ValueType
{
  .pack 1
  .size 0

  .field public initonly marshal(fixed array[10] int8( bool[] values
}
[<Struct;StructLayout(LayoutKind.Sequential, Pack = 1)>]
type BoolArrSample =
  [<MarshalAs(UnmanagedType.ByValArray, SizeConst = 10, ArraySubType = UnmanagedType.I1)>]
  val  values: BOOL[]
.class public sequential ansi sealed serializable NuSoft.Ads.Experimental.Samples.BoolArrSample
  extends [mscorlib]System.ValueType
  implements class [mscorlib]System.IEquatable`1<valuetype NuSoft.Ads.Experimental.Samples.BoolArrSample>,
             [mscorlib]System.Collections.IStructuralEquatable,
             class [mscorlib]System.IComparable`1<valuetype NuSoft.Ads.Experimental.Samples.BoolArrSample>,
             [mscorlib]System.IComparable,
             [mscorlib]System.Collections.IStructuralComparable
{
  .custom instance void [FSharp.Core]Microsoft.FSharp.Core.StructAttribute::.ctor() = (
    01 00 00 00
  )
  .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = (
    01 00 03 00 00 00 00 00
  )
  .pack 1
  .size 0

  .field assembly marshal(fixed array[10]( bool[] values@

  .property instance bool[] values()
  {
    .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags,  int32) = (
      01 00 04 00 00 00 00 00 00 00 00 00
    )
    .get instance bool[] NuSoft.Ads.Experimental.Samples.BoolArrSample::get_values()
    {
      IL_0000: ldarg.0
      IL_0001: ldfld bool[] NuSoft.Ads.Experimental.Samples.BoolArrSample::values@
      IL_0006: ret
    }
  }
}

as you may notice, there’s no ArraySubType value in F# sample IL output. If you look at a visual fsharp sources you’ll notice that ArraySubType is disregarded. Click here for open issue.

Workaround for marhsalling byte arrays in  F#

Other than fixing the issue, one can use Struct array instead of  bool array, though it’s not much convenient.

[<AutoOpen>]
module Workarounds =
  open System.Runtime.InteropServices
  
  [<Struct;StructLayout(LayoutKind.Sequential, Pack=1)>]
  type BOOLSTRUCT = 
    val value: BOOL

    new(value) = {
      value=value
    }
    
  let inline BOOL (x: BOOL) = new BOOLSTRUCT(x)
  let inline BOOLSTRUCT (x: BOOLSTRUCT) = x.value

Marshalling TwinCAT TOD, DATETIME, TIME, DATE types

In TwinCAT time and date types are all 4 bytes, closest out-of-the-box equivalents in .Net world are DateTime for DATE_AND_TIME and DATE, and  TimeSpan for TIME_OF_DAY and TIME, problem is both of them are 8 bytes long.

Marshalling DateTime/TimeSpan values

If you read trough Infosys, you’ll find Reading and writing of TIME/DATE variables, it utilizes AdsStream and AdsBinaryReader/AdsBinaryWriter, but personally prefer to use other methods provided by TcAdsClient.dll.

[<AutoOpen>]
module Ext =
  type TcAdsClient with 
        member self.ReadDateTime(variableHandle) = 
          self.ReadAny(variableHandle, typedefof<uint32>) 
          :?> uint32
          |> int64 
          |> TwinCAT.Ads.Internal.PlcOpenDateConverterBase.ToDateTime

        member self.ReadTimeSpan(variableHandle) = 
          self.ReadAny(variableHandle, typedefof<uint32>) 
          :?> uint32
          |> int64 
          |> TwinCAT.Ads.Internal.PlcOpenTimeConverter.ToTimeSpan

        member self.WriteAny(variableHandle, value) = 
          let v = 
            value 
            |> TwinCAT.Ads.Internal.PlcOpenDateConverterBase.ToTicks
            |> uint32
          self.WriteAny(variableHandle, v) 
        
        member self.WriteAny(variableHandle, value) = 
          let v = 
            value 
            |> TwinCAT.Ads.Internal.PlcOpenTimeConverter.ToTicks
            |> uint32
          self.WriteAny(variableHandle, v) 

you can always use code from Infosys as an extension methods as well.

Marshalling DateTime/TimeSpan structure fields

When it comes to workaround for structure fields there is no workaround that I would like, at least not among those that I know to work. I had few approaches to solutions that do not work, and as I learned couldn’t even possible work. May say one learns by failing. Let’s start with one notable failure, as it also comes with F# missing functionality.

Custom marshallers

At first I thought that it would be possible to marshal one filed with usage of custom marshaller, well it doesn’t work that way.

I may be wrong at this, but Custom marshaller is not an option even if we invoked native method from tcadsdll.dll, as the signature

[DllImport("tcadsdll.dll", CharSet=CharSet.None, ExactSpelling=false)]
public static extern unsafe AdsErrorCode AdsSyncReadReq(byte* addr, uint indexGroup, uint indexOffset, int length, void* data);

expects void pointer not actual structure

Workaround

[<Struct;StructLayout(LayoutKind.Sequential, Pack = 1)>]
type DateTimeArraySample =
  [<MarshalAs(UnmanagedType.ByValArray, SizeConst = 10, ArraySubType = UnmanagedType.U4)>]
  val values: uint32[]

  member self.Values =
    self.values 
    |> Array.map (int64 >> TwinCAT.Ads.Internal.PlcOpenDateConverterBase.ToDateTime)

what I do not like about is obfuscating structure with additional properties, and even if we’d use extension properties/methods, it still involves duplicating every single DT/TOD/D/T fields.

Note on custom marshallers in F#

Putting it simply, they cannot be used ATM, SO question. Still not sure why, but actual code for read and write IL, is present in sources, yet when parsing MarshalAsAttribute exception is thrown explicitly, though it doesn’t seem that hard to fix it, I have it working on modified sources. If I only knew how to test it properly…

Extending AutoFixture with custom ISpecimenBuilder

At times AutoFixture out-of-a-box behavior and capabilities, fall to short when we require more customized test data. Such was my case. In previous post [click] I  mentioned that I prefer not to provide objects with invalid data, as a matter of fact, it’s not only a case of personal preference. If we declare array of certain length via MarshalAsAttribute, and then set different one, will end up with exception. Thus the idea of generating test data based on MarshalAsAttribute, since we already have access to it, shouldn’t be a problem. One more thing worth mentioning. At some point I’ll be mocking ADS client, since CI build server won’t have access to actual TwinCAT runtime. Default AutoFixture behavior throws exception if we try to generate structure with default constructor only, if we only go with suggested SupportMutableValueTypesCustomization we’ll end up with structure which has all fields set with default values, isn’t the thing we need, is it?

Implementing ISpecimenBuilder

First thing we ought to do is implementing ISpecimenBuilder and it’s quite simple as it only has one method.

let MarshalAsTypeBuilder =
  {
    new ISpecimenBuilder with
    member self.Create(request, context) =
      
      match request with
        | : ? Type as t when 
          t.IsValueType 
          && t.IsPrimitive |> not
          && Array.contains t 
            [|
              typedefof<string>;
              typedefof<decimal>;
              typedefof<DateTime>;
              typedefof<DateTimeOffset>;
              typedefof<TimeSpan>
            |] |> not ->
              let instance = Activator.CreateInstance(t)
              instance
        | _ -> new NoSpecimen() :> obj
  }

What we’re doing here is verifying if the current context is type of our interest. If not we fall back to default behavior on line 20. As you may noticed we still only have an instance with default values.

Providing data for struct fields

before we proceed, let’s take a look on code compiled from F#.

[<StructLayout(LayoutKind.Sequential, Pack=1)>]
type StringTestStruct =
  struct
    [<MarshalAs(UnmanagedType.ByValTStr, SizeConst=81)>]
    val string80Var:string
    [<MarshalAs(UnmanagedType.ByValTStr, SizeConst=2)>]
    val string1Var:string
    [<MarshalAs(UnmanagedType.ByValTStr, SizeConst=257)>]
    val string256Var:string
  end

[CompilationMapping(SourceConstructFlags.ObjectType)]
[Serializable]
public struct StringTestStruct : IEquatable<Tests.StringTestStruct>, IStructuralEquatable, IComparable<Tests.StringTestStruct>, IComparable, IStructuralComparable
{
    internal string string80Var@;

    internal string string1Var@;

    internal string string256Var@;

    [CompilationMapping(SourceConstructFlags.Field, 1)]
    public string string1Var
    {
        get
        {
            return this.string1Var@;
        }
    }

    [CompilationMapping(SourceConstructFlags.Field, 2)]
    public string string256Var
    {
        get
        {
            return this.string256Var@;
        }
    }

    [CompilationMapping(SourceConstructFlags.Field, 0)]
    public string string80Var
    {
        get
        {
            return this.string80Var@;
        }
    }
}

F# hides struct fields behind properties. Properties itself are of no interest for us as they do not store MarshalAsAttribute metadata, thus we’ll be iterating over non-public instance fields.

let instance = Activator.CreateInstance(t)

t.GetFields(BindingFlags.Instance ||| BindingFlags.NonPublic ||| BindingFlags.Public)
|> Array.map (fun fi -> fi,context.Resolve(fi))
|> Array.iter (fun (fi, value) -> 
  fi.SetValue(instance, value)
)
instance

At this point we have are supplied with struct instance with default AutoFixture data, which do not correspond to MarshalAsAttribute data.

Assuring valid data

As said in beginning, we have to provide data for arrays and strings accordingly to information we provided via MarshalAsAttribute.

Thus we’ll have to override default behavior for those as well.

Strings not longer that SizeConst

| : ? FieldInfo as fi when fi.FieldType = typedefof<string> ->
  let attr = fi.GetMarshalAsAttribute()
  match attr with
    | Some(maa) when maa.Value = UnmanagedType.ByValTStr && maa.SizeConst > 0 ->
        let result = context.Resolve(typedefof<string>).ToString()
        result.Substring(0,Math.Min(result.Length,maa.SizeConst-1)) :> obj
    | _ -> new NoSpecimen() :> obj

if field is string, and it has MarshalAsAttribute we use AutoFixture default string generator, only difference is, if it’s longer than expected we take substring of it, so it’s length would correspond to SizeConst

–1 is used only due to ADS string null terminator.

Arrays length equal to SizeConst

| : ? FieldInfo as fi when fi.FieldType.IsArray -> 
  let attr = fi.GetMarshalAsAttribute()
  match attr with
    | Some(maa) -> 
      let result = Array.CreateInstance(fi.FieldType.GetElementType(), maa.SizeConst)
      Array.Copy(Array.init maa.SizeConst (fun _ -> context.Resolve(fi.FieldType.GetElementType())), result,maa.SizeConst)
      result :> obj
    | _ -> new NoSpecimen() :> obj

again we override default behavior, this time first we create array with length based on SizeConst, then we fill with values generated with AutoFixture (that includes our overrides, if we have nested types)

Marshalling records – dead end

It turns out idea to use records in ADS client instead of structs wasn’t a good idea. I expected some issues, mostly with lack of parameterless constructor, but things turned out worse than expected.

let’s consider simple struct example

[<StructLayout(LayoutKind.Sequential, Pack=1)>]
type SimpleStruct =
  struct
    val byteVar: BYTE
  end
  
[<StructLayout(LayoutKind.Sequential, Pack=1)>]
type ContainingStruct =
  struct
    [<field: MarshalAs(UnmanagedType.Struct)>]
    val nested: SimpleStruct
  end
[<StructLayout(LayoutKind.Sequential, Pack=1)>]
type ContainingStructArray =
  struct
    [<field: MarshalAs(UnmanagedType.ByValArray, SizeConst=10,ArraySubType=UnmanagedType.Struct)>]
    val nested: SimpleStruct array
  end

Assert.Equal(1, typedefof<SimpleStruct> |> Marshal.SizeOf)
Assert.Equal(1, typedefof<ContainingStruct> |> Marshal.SizeOf)
Assert.Equal(10, typedefof<ContainingStructArray> |> Marshal.SizeOf)

all passes like a charm, now let’s rewrite our structs to records

[<StructLayout(LayoutKind.Sequential, Pack=1)>]
type SimpleRecord = {
  byteVar: BYTE
}
  
[<StructLayout(LayoutKind.Sequential, Pack=1)>]
type ContainingRecord = {
  [<field: MarshalAs(UnmanagedType.Struct)>]
  nested: SimpleRecord
}
[<StructLayout(LayoutKind.Sequential, Pack=1)>]
type ContainingRecordArray = {
  
  [<field: MarshalAs(UnmanagedType.ByValArray, SizeConst=10,ArraySubType=UnmanagedType.Struct)>]
  nested: SimpleRecord array
}

Assert.Equal(1, typedefof<SimpleRecord> |> Marshal.SizeOf) //success
Assert.Equal(1, typedefof<ContainingRecord> |> Marshal.SizeOf) //success
Assert.Equal(10, typedefof<ContainingRecordArray> |> Marshal.SizeOf) //failure, actual size is 40

note that it only doesn’t work on array of structs, not sure how it works under the hood, but somehow it always defaults to 4 bytes, as if we used sizeof<SimpleRecord>, thus array of 10 items, each of them 4 bytes.

Solution?

I’m aware of none yet, beside writing custom code to serialize/deserialize records to/from byte arrays. Which itself would be that bad idea (if we ignore performance) as I could handle endianness conversions if by any chance communication with PLC would occur from big endian machine. 

Accessing MarshalAsAttribute

While playing with AutoFixture to verify if F# Records can be marshalled as nicely as Structs, to my surprise I noticed MarshalAsAttribute cannot be accessed by standard GetCustomAttributes method. Turns out that it’s one of pseudo custom attributes that are treated differently by compiler.

Why would I need to access MarshalAsAttribute?

AutoFixture by default generates test data for string as memberName+GUID, which is nice for most cases, but if a filed is marked as having 10chars in string, why provide with incorrect data?

type SampleRecord = {
  [<field: MarshalAs(UnmanagedType.I1)>]boolVar:BOOL
  byteVar: BYTE
  dwordVar: DWORD
  lrealVar: LREAL
  [<field: MarshalAs(UnmanagedType.ByValTStr, SizeConst=81)>]
  string80Var: string
  [<field: MarshalAs(UnmanagedType.ByValTStr, SizeConst=11)>]
  string10Var: string
}

Possible solution would be usage of additional attribute like [<field: StringLength(10)>], but I’m not a big fan of providing same information, over and over again, if I already did it once, plus sometimes I’m stubborn, well, quite often if I’m positive something can be done the way I want it.

How to access MarshalAsAttribute?

If you look into .Net Reference Source you’ll find internal static GetCustomAttribute(RuntimeFieldInfo field) method, which returns the very same MarshalAsAttribute as we annotated our field with. Few pretty self-explanatory lines of code and we’re home.

module Extensions

open System.Reflection
open System.Runtime.InteropServices

let maa = typedefof<MarshalAsAttribute>
  
let GetCustomAttribute = 
  maa.GetMethods(BindingFlags.Static ||| BindingFlags.NonPublic) 
  |> List.ofArray 
  |> List.tryFind (fun mi -> 
    let parameters = mi.GetParameters()
    mi.Name = "GetCustomAttribute" 
    && parameters.Length = 1 
    && parameters.[0].ParameterType.Name = "RuntimeFieldInfo"
    )


type FieldInfo with
  member self.GetMarshalAsAttribute () =
    match GetCustomAttribute with
      | Some(gcaMi) ->
        let attr = gcaMi.Invoke(null, [| self |])
        Some(attr :?> MarshalAsAttribute)
      | None -> None

F# ADS Toolkit | Tools review

Why F#?

For the development of ADS Toolkit I have considered two .NET based languages, F# due to Type Providers, and Nemrele due to powerful metaprogramming capabilities. Unfortunately Nemerle, which is great language, I strongly encourage you to try it, lacks mature support in Visual Studio.

Let’s focus on few F# features that’ll be useful for this particular project.

Type aliases

As mentioned in previous post, TwinCAT implements IEC 61131-3 specification, thus data types naming is different from the one we know from .NET world.  Type aliases are a little thing, yet they make communication a lot simpler, due to allowing both parties (.NET Dev and PLC Dev) usage of same naming convention. Besides, it’s easier, and faster, to say double word instead of  32bit unsigned integer.

Units of Measure and strong typing

Yet another little thing, but it makes values less abstract, plus it makes types safe, if by any chance we’ll by trying to add, or compare,  A (current) to V (voltage) we’ll end up with compile time error. Also, typical solution involves precise measurement thus F# lack of dynamic casting between numeric is another plus.

Type providers

An F# type provider is a component that provides types, properties, and methods for use in your program. – MSDN

I want to skip as much manual work as possible when creating software, call me lazy, thus major part of this project is providing types, required to communicate with PLC, based on *.tpy file generated by TwinCAT.

F# itself

On a first few projects in area of Industrial Automation I used C# exclusively, but recently, say one year ago, I decided to take my chances and try F# for the part of solution responsible for communication with PLC, LIN (Local Interconnect Network – an Automotive network) systems and third party web service. Since that time I cannot imagine myself using non-functional language for such appliances. Time required for development was at least cut 40%, moreover I wasn’t distracted by expressive C-like syntax. As a side note, code metrics is way shorter, yet it’s not a valid criterion to judge software.

Testing

AppVeyor

I always wanted to give those fancy hosted CI build servers a shot. I picked AppVeyor as it’s free for OSS projects (same as Travis-CI), comes with nice badges (again, same as Travis-CI) and is Windows based. Later, if I get to the point at which I replace Beckhoff’s TwinCAT.Ads.dll with own, fully managed .NET client, I’ll be testing it on mono/linux.

FoQ, AutoFixture, xUnit, FsUnit

I admit, I used none of them before, only nUnit, MSTest and JustMock, no wonder I want to try something different, although JustMock has some nice features. DSP is great moment to try new tools, and I’m curious if xUnit is as much extensible as it is described, if AutoFixture saves as much time as its authors boast, and moreover, how all of them play together.

Why I’ll need mocking

As said earlier, I’ll be using AppVeyor, hosted CI build server. It means I can forget about testing against actual TwinCAT instance, whether VM or actual PLC, adding routes to running TwinCAT instance can sometimes be pain in the ass, and I don’t want to check how it can be done on AppVeyor, it probably can’t be done anyway. I’m going to wrap TcAdsClient in  my class (and mock it for test). It’s always good idea to create wrapper for TcAdsClient anyway, as AdsException does not contain information regarding on which particular symbolic/handle operation has failed.