Skip to content
< All Topics

Custom connection

It is possible to extend the functionality of Experior with custom communication protocols. This is done by creating your own plugin and extending Experior.Core.Communication.PLC.Connection.

In case your connection is TCP/IP based you can extend from Experior.Core.Communication.PLC.TCPIP.Connection.

Experior.Core.Communication.PLC.Connection

In attached memcom communication plugin a custom connection is made that doesn’t use the network to communicate but instead uses a memory stream. This plugin can be used to quickly test your Input/Output driven components. You do this by connecting an input and an output through the same address of the memcom connection.

For example link the Pushed PLC Input symbol of a button to Byte 1 Bit 0 of the memcom connection and also connect the Lighting PLC output to Byte 1 Bit 0 of the memcom connection. When pressing the button the lamp will light up:

The custom connection uses a memory stream to achieve this because a memory stream has similar behavior as a regular network stream.

The source code contains some notes explaining how to achieve the same functionality without the memory stream (and just share the data buffer).

Whenever the user assigns an input/output from a component to a PLC connection then this input/output is assigned to a PLC Buffer (Experior.Core.Communication.PLC.Buffers.Input/Experior.Core.Communication.PLC.Buffers.Output). These buffers maintain an array of bytes and grows/shrinks whenever new inputs/outputs are added/removed.

So in the constructor of the connection we create the buffers and subscribe to the events that are triggered when their size should change or when new inputs/outputs are assigned to them.

It is possible to have multiple buffers in a connection. Therefore we distinguish between them in a connection using a unique key (string) the Source.

In the example plugin only 1 Input buffer and 1 Output buffer is used, each with a default Source name equal to “0”.

In the constructor we also restore the saved buffer in case it exists.

public MemConnection(MemConnectionInfo info)
: base(info)
{
    input = new Core.Communication.PLC.Buffers.Input();

    // first restore the saved outputbuffer if it exists
    if (info.outputs != null && info.outputs.Count > 0)
    {
        output = info.outputs[outslot];
    }
    else output = new Core.Communication.PLC.Buffers.Output();

    //give the input and output buffer a unique slot name
    input.Source = inpslot;
    output.Source = outslot;

    // subscribe to the different events to react upon data changes or to properly allocate the buffers
    output.BufferChanged += new Core.Communication.PLC.Buffers.Output.BufferChangedEvent(OutputDataUpdated);
    input.BufferChanged += new Core.Communication.PLC.Buffers.Input.BufferChangedEvent(InputDataUpdated);
    Experior.Core.Communication.PLC.Connection.Inputs.LengthChanged += new Inputs.LengthChangedEvent(Inputs_LengthChanged);
    Experior.Core.Communication.PLC.Connection.Outputs.LengthChanged += new Outputs.LengthChangedEvent(Outputs_LengthChanged);
    Experior.Core.Communication.PLC.Connection.Outputs.ConnectionedAssigned += new Outputs.OutputEvent(Outputs_ConnectionedAssigned);
    Experior.Core.Communication.PLC.Connection.Outputs.ConnectionedUnAssigned += new Outputs.OutputEvent(Outputs_ConnectionedUnAssigned);
    Experior.Core.Communication.PLC.Connection.Inputs.ConnectionedAssigned += new Inputs.InputEvent(Inputs_ConnectionedAssigned);
    Experior.Core.Communication.PLC.Connection.Inputs.ConnectionedUnAssigned += new Inputs.InputEvent(Inputs_ConnectionedUnAssigned);

    // add sources to the connection
    AddSource(input);
    AddSource(output);

    Experior.Core.Environment.Scene.Loaded += new Core.Environment.Scene.Event(Scene_Loaded);
}

When the user assigns an Input/Output to the connection it triggers the Experior.Core.Communication.PLC.Connection.Inputs.ConnectionedAssigned
and  Experior.Core.Communication.PLC.Connection.Outputs.ConnectionedAssigned events.

In the example we only use this to set the Source property of the Input/Output

private void Inputs_ConnectionedAssigned(Input sender, Core.Communication.PLC.Connection connection)
{
    if (this != connection)
        return;
        
    // here you can react upon the sender being added to this connection
    // in our case we just want to ensure the proper Source is set
    sender.Source = inpslot;
}

Due to the nature of the example connection it is crucial that the Input and Output buffer have the same size so a big part of the source code is related to this.

When assigning Inputs/Outputs to a connection Experior verifies whether the buffer size should change and in those cases triggers the Experior.Core.Communication.PLC.Connection.Inputs.LengthChanged
or Experior.Core.Communication.PLC.Connection.Outputs.LengthChanged events.

At all times you ask the minimum and maximum used byte of a input/output buffer by providing the connection and the Source of the buffer:

int max = Experior.Core.Communication.PLC.Connection.Inputs.MaxSize(this, inpslot);
int offset = Experior.Core.Communication.PLC.Connection.Inputs.MinSize(this, inpslot);

If the buffer is not large enough anymore you can allocate sufficient bytes:

// allocate (max-offset) bytes for the input buffer starting from offset
input.Allocate(max - offset, offset, Experior.Core.Communication.PLC.Buffers.Buffer.Units.SINT);

Note: Experior.Core.Communication.PLC.Buffers.Buffer.Units.INT is used to indicate that the given size should be measured in bytes.
In case Experior.Core.Communication.PLC.Buffers.Buffer.Units.SINT is used the size will be measured in words of 16 bits.

After the allocation the input.Data property will be an array of bytes with the proper size.
When the user tries to connect the connection the EstablishConnection() method is called.
When he tries to disconnect the Disconnect() method is called.
In our example we override both methods and set the proper state of the connection (Core.Communication.State).
When establishing the connection we make sure that the Input/Output buffers are allocated with the same size.
In case we use a memorystream we also start listening on the stream (similar as we would be on a networkstream).

When the states of the Input/Outputs are changed this is immediately reflected in changes of the corresponding Input/Output buffers of the associated connection and the BufferChanged events are triggered.
In our example we write the changed buffer to the memorystream. A different thread reads the memory stream and when the byte array is received it calls the byte[] DataReceived(string source, byte[] data) method of the connection to indicate that the given source has received the given data.

int l = memStream.Read(incoming, 0, incoming.Length);

if (l > 0)
{
    DataReceived(inpslot, incoming);
}

This triggers the proper events on the changed Inputs/Outputs.
In the example without the memorystream we simply share the Data of the buffer and directly call the DataReceived method.

Experior.Core.Communication.PLC.TCPIP.Connection

This connection class inherits from Experior.Core.Communication.PLC.Connection and mainly adds some TCPIP specific properties like Port, IP address and distinction between server and client connection:
string IP: This getter property is used to get / or set the IPv4 address used by this connection. For a server connection this is 127.0.0.1 (localhost).

int Port: This property is used to get or set the port number for the TCPIP connection.

bool Server: Getter property to indicate whether this connection is used as server or client.

Was this article helpful?
How can we improve this article?
Please submit the reason for your vote so that we can improve the article.