Introduction
This is a simple example of a C# Application interfacing with the Null Modem Emulator (com0com) driver to allow run time creation and configuration of Virtual Serial Ports
Background
This originally started as a hard coded solution in our environment. We use a lot of serial com port interfaces to connect to various modems and radio transceivers. As part of the testing environment, a physical device had to be connected to the test machine and the serial communications were split to a second machine to monitor and interpret the resulting data.
During a recent environment change, this hardware based split has become unfeasible and too costly to maintain. We then decided to implement a virtual serial ports via Null Modem Emulator. The current configuration works perfectly, but maintaining and modifying the environment has become more difficult for non-IT staff.
Thus, the need was born to develop a unified solution that will present an all inclusive, user friendly platform to initialize and test various hardware and software.
This brings us to the purpose of the tip, a simple way to interface with null modem, issue commands and read feedback.
Using the Code
Null Modem can be controlled via the executable by issuing a command flag to the application with any required flags. We can launch external applications directly from C# and through that mechanism, we can control Null Modem. After execution, Null Modem provides the relevant feedback on the task executed.
Sending Commands to Null Modem
private static string execute_command(string command)
{
Process p = new Process();
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.WorkingDirectory = "c:\\NullModem\\";
p.StartInfo.FileName = "c:\\NullModem\\setupc.exe";
p.StartInfo.Arguments = " " + command;
p.Start();
string output = "";
output += p.StandardOutput.ReadToEnd() + "\r\n";
p.WaitForExit();
return output
}
Example
List all of the available null modem ports
execute_command("list");
Output from Null Modem
CNCA0 PortName=COM25
CNCB0 PortName=COM26
Install a new port pair
execute_command("install PortName=COM26,EmuBR=yes,EmuOverrun=yes,
cts=ropen PortName=COM25,EmuBR=yes,EmuOverrun=yes,cts=ropen");
Output from Null Modem
CNCA2 PortName=COM100,EmuBR=yes,EmuOverrun=yes,cts=ropen
CNCB2 PortName=COM101,EmuBR=yes,EmuOverrun=yes,cts=ropen
ComDB: COM100 - logged as "in use"
ComDB: COM101 - logged as "in use"
Interpreting the Results
The following method groups the received lines from Null Modem into a logical port pair. Each line is parsed and a NullModemPort
object is generated.
private static List<NullModemPair> Parse_NME_Data(string data)
{
var lstNMP = new List<NullModemPair>();
var nmp = new NullModemPair();
foreach(var x in data.Split('\n'))
{
var p = Parse_NME_Line(x);
if (!string.IsNullOrEmpty(p.nme_Name))
{
if (p.is_A)
{
nmp.Index = p.Index;
nmp.A = p;
}else if (nmp.Index == p.Index)
{
nmp.B = p;
lstNMP.Add(new NullModemPair(nmp.Index, nmp.A, nmp.B));
nmp = new NullModemPair();
}
}
}
return lstNMP;
}
To parse the individual lines from Null Modem, we use Regex to look for familiar patterns in the results.
private static NullModemPort Parse_NME_Line(string line)
{
var p = new NullModemPort();
Regex rgx = new Regex("(CNC[AB][0-9]*)", RegexOptions.IgnoreCase);
MatchCollection matches = rgx.Matches(line);
foreach (Match match in matches)
p.nme_Name = match.Groups[1].Value;
rgx = new Regex("CNC([AB])[0-9]*. ", RegexOptions.IgnoreCase);
matches = rgx.Matches(line);
foreach (Match match in matches)
{
if (match.Groups[1].Value.Contains("A"))
p.is_A = true;
else
p.is_A = false;
}
rgx = new Regex("CNC[AB]([0-9]*)", RegexOptions.IgnoreCase);
matches = rgx.Matches(line);
foreach (Match match in matches)
int.TryParse(match.Groups[1].Value, out p.Index);
rgx = new Regex("PortName=(.*?),|\n", RegexOptions.IgnoreCase);
matches = rgx.Matches(line);
foreach (Match match in matches)
p.PortName = match.Groups[1].Value;
string pars = "";
rgx = new Regex("[A-Z][0-9]?.*?,(.*)", RegexOptions.IgnoreCase);
matches = rgx.Matches(line);
foreach (Match match in matches)
pars = match.Groups[1].Value;
p.EmulateBaudRate = false;
p.BufferOverrun = false;
p.CTS_Open = false;
if (pars.Contains("EmuBR=yes"))
p.EmulateBaudRate = true;
if (pars.Contains("EmuOverrun=yes"))
p.BufferOverrun = true;
if (pars.Contains("cts=ropen"))
p.CTS_Open = true;
return p;
}
The port object is returned to the grouping method. The method then pairs and stores the information.
This will allow us to have easy access to the information throughout the rest of the program.
Points of Interest
The sample application as a whole is very loosely constructed and only meant to demonstrate the mechanisms through which we can access Null Modem and issue commands to it.
A lot of optimization can be done to how the process is called and results are retrieved.
Additionally, the structure of the NullModemInterface
and the resulting hierarchy should be redone to fit in with the requirements of the application.
Error handling has been omitted for the purposes of the demonstration. Some thought and care should be put into adding proper exception handling into the application.