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.
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.