Looking at how to transmit the UECP Stream

Getting the UECP stream sent

A standard C# SerialPort component is used. Care must be taken that a an USB to serial port adapter can be used since many PC of toaday do not implement the native serial port.

Send the UECP Stream

As stated the transmission is done asynchronously via a thread seperate from the form. First this thread is created with three buffers:

public void Create(VirtualConcentratorCallback _vcc, Int16 size, ref RDSComm _Trunk)
{
    //  size of command queue to create
    Trunk = _Trunk;
    virtualconcentrator_callback = (Object)_vcc;  //  Information callback for Concentrator from the virtual concentrator
    QueueSize = size;
    bPending = new Boolean[QueueSize];
    CommandPending = new Int16[QueueSize];
    CommandDataPending = new Object[QueueSize];
    if (QueueSize >= 8) queue_lower = (Int16)(QueueSize - (QueueSize / 4));
    else queue_lower = QueueSize;
    for (Int16 i = 0; i < QueueSize; i++)
    {
        bPending[i] = false;
        CommandPending[i] = 0x00;
        CommandDataPending[i] = null;
    }
}
_vcc                the callback to be used to handle the commands
size                the size of the command pile eg 16
_Trunk              a reference to the RDS serial port in use
bPending            to flag if the command is pending execution or not.
CommandPending      the commands that are pending execution or have been executed.
CommandDataPending  data associated with the command pending if applicable.     
        

Then comes the actual working part of the thread:

public void Run()
{
    Int16 Keep_Alive_Counter = 0;
    //  The real thread that runs the virtual concentrator
    //  Inform that thread has started
    if (virtualconcentrator_callback != null)
    {
        cd.type = VirtualConcentrator_Callback;
        cd.reason = VirtualConcentrator_Started;
        ((VirtualConcentratorCallback)virtualconcentrator_callback)(ref cd);
    }
    else return;    //  vc is null!!!
    //  Set up a timer and application event handler
    try
    {
        while (cd.run)
        {
            Application.DoEvents();
            Thread.Sleep(2);    //  2msec is the time slice
            Application.DoEvents();
            if (cd.run)
            {
                //  Look up the next command if any
                Keep_Alive_Counter++;
                if (Keep_Alive_Counter >= 50)
                {
                    cd.reason = VirtualConcentrator_Check_And_Kick;
                    Keep_Alive_Counter = 0;
                }
                else
                {
                    cd.type = VirtualConcentrator_Callback;
                    cd.reason = VirtualConcentrator_Check;
                    ((VirtualConcentratorCallback)virtualconcentrator_callback)(ref cd);
                }
                //  Check TrunkFailureCounter has not reached the limit TrunkFailureCount
                if (TrunkFailureCounter > TrunkFailureCount)
                {
                    cd.type = VirtualConcentrator_Callback;
                    cd.reason = VC.VirtualConcentratorTrunkFailure;
                    ((VirtualConcentratorCallback)virtualconcentrator_callback)(ref cd);
                }
            }
        }
        //  The thread exits
        if (virtualconcentrator_callback != null)
        {
            cd.type = VirtualConcentrator_Callback;
            cd.reason = VirtualConcentrator_Stop;
            ((VirtualConcentratorCallback)virtualconcentrator_callback)(ref cd);
        }
    }
    catch
    {
        if (virtualconcentrator_callback != null)
        {
            cd.type = VirtualConcentrator_Callback;
            cd.reason = VirtualConcentrator_Stop;
            ((VirtualConcentratorCallback)virtualconcentrator_callback)(ref cd);
        }
    }
}

The startup is synchronised with the opening of the serial port. Every 2mSec the thread kicks a keep alive counter and checks for any commands pending by using callbacks to the main form. If there is a command pending the serial port thread is commanded to execute the command, wait for a reply by executing a callback. On the reply reception the reply is posted back to update the form.

cd      the object used to hold the callback data.

In addition this thread needs functions to Add, Remove, GetNext commands from the buffers. For example:

public Boolean Add(Int16 code)
{
    //  The next free space is in next_command
    if ((code == VC.VirtualEquipmentCommandStart) ||
        (code == VC.VirtualEquipmentCommandStop) ||
        (code == VC.VirtualEquipmentCommandReset)) CommandPending[next_pending] = code;
    else CommandPending[next_command] = code;
    bCommandPending = true;
    //  Increment next_command upto the end
    next_command++;
    queue_length++;
    if (next_command >= QueueSize) next_command = 0;
    return true;
}

This function accepts a simple single command, others are required to accept command + data and command + object. An object would be used when multiple data items have to be sent.

Finally the callback. Here is the action on the PI parameter to retrieve that parameter from the RDS and place it on the form. Since the call to this function can come from both the same and different threads the possibilty of an invoke is required.

protected Boolean ConcentratorCallback(ref ConcentratorData cd)
{
    Boolean bReply = true;
    switch (cd.reason)
    {
        case VC.VirtualConcentratorUserCommand:
            switch (cd.type)
            {
                case VC.VirtualEquipmentCommandGetRDS_PI:
                    //  Get the PI value
                    UInt16 pi = 0;
                    bReply = Get_RDS_PI(ref pi, 0, 0);
                    if (bReply)
                    {
                        //  Display PI
                        try
                        {
                            if (PIBox.InvokeRequired)
                            {
                                UpdatePICallback dd = new UpdatePICallback(UpdatePI);
                                this.Invoke(dd, new object[] { pi });
                            }
                            else
                            {
                                UpdatePI(pi);
                            }
                        }
                        catch { bProblem = true; }
                    }
                    break;
cd.reason                               a code to indicates a command is to be executed
cd.type                                 has the command to be executed
VC.VirtualEquipmentCommandGetRDS_PI     the flag for the command
Get_RDS_PI                              the function to set up the frame to get PI from the encoder

cd.reason can be used to seperate standard UECP commands, manufacture specific command and maintenance information.