RS232 Modem Wrapper






3.73/5 (9 votes)
An article about creating a class which wraps the serial port and modem communication logic.
Introduction
After a bit of search on CodeProject.com, I still could not find any article on wrapping the SerialPort
class and modem functionality. So, I decided to write a base class which wraps RS232 communications with a modem. Also, I've written two subclasses for demonstration purposes - to simulate different behaviors of different modem models. In general, I think this article should be useful for somebody who is trying to implement classes which hold communication logic with any RS232 device.
Background
I have used some general AT modem commands in this sample. A good compilation of the general AT commands is available in this site. Also, some knowledge about thread-safe calls is required, because the SerialPort
class operates in a different thread than the GUI.
Using the code
There are three classes implemented here. Below is the UML class diagram, drawn with a freeware tool bouml.
The basic class for communication with the modem is modem
. The other two classes, ModemModel1
and ModemModel2
, are inherited from the base class for demonstrating the idea that different types of modems can support different command sets and/or have different implementations of the same AT command (in theory). For example, a GSM modem can receive and send SMS; non-GSM compatible modems can't do this. Because I use only one modem, I just simulated different modems by implementing different modem behavior in subclasses by overriding the base class methods. Let's assume that the property DefaultBoudrate
and the methods InitializeModem()
, GetManufacturer()
, and GetProductId()
behave differently for different modem models. For this reason, they should be overridden in the sublases of modem
. Like this:
public class ModemModel1 : Modem
{
#region constructors
public ModemModel1() : base() { }
public ModemModel1(SerialPort prt, SetCallback c) : base(prt,c) { }
public ModemModel1(SerialPort prt, string prtnum, SetCallback c) :
base(prt, prtnum, c) { }
#endregion
#region overriden properties
// Will be other DefaultBoudrate than in base class
public override int DefaultBoudrate
{
get {return 19200;}
}
#endregion
#region overriden methods
// Other modem initialization than in base class
protected override void InitializeModem()
{
base.SendCommandToModem("ATE1L2S3?\r");
}
// Other command for geting manufacturer. Actually command is the
// same - I4, but for simulating other behavior, just added additional
// command I1.
public override void GetManufacturer()
{
base.SendCommandToModem("ATI4I1\r");
}
// Also overriden command for getting modem product Id.
// Added aditional command I3.
public override void GetProductId()
{
base.SendCommandToModem("ATI0I3\r");
}
#endregion
}
One interesting note. I wanted the modem to be initialized in the object construction phase. That is, in the modem
class constructor. And when the modem initializes, it sends a response back. I wanted to show that response in a form in the initialization textbox. At first, I thought that the initialization event will do the trick. But... it doesn't. Because the event handler can be attached to an already constructed object, and we need to fire the initialization event before the object is finally constructed. The solution was to use a delegate. That is, to pass a delegate into the modem
class constructor. Like this:
public ModemModel1(SerialPort prt, string prtnum, SetCallback c) :
base(prt, prtnum, c) { }
In general, using the created classes is simple, like this:
mod = new Modem(new SerialPort(), this.cmbPortas.Text, this.call);
After that, we can query the modem manufacturer with mod.GetManufacturer()
. Also, we have the modem
class static method FirstAttachedModem()
, which returns the COM port on which the modem is connected and also the modem manufacturer and the chipset version. That is why on the form show event, the modem is automatically detected (if it exists, of course :-)).
Finally, about thread-safe calls. This is achieved by the Modem.SetCallback
delegate. The approach is more general - not to pass the modem answer / text to the GUI thread, but instead to pass the whole Modem
object like this:
public void TakeControl(Modem modcall).
{
if (this.InvokeRequired)
{
Modem.SetCallback d = new Modem.SetCallback(TakeControl);
this.Invoke(d,new object[] {modcall});
}
else
{
if (this.init)
this.txtInicializavimas.Text = modcall.ModemAnswer;
else
this.txtAtsakymas.Text = modcall.ModemAnswer;
}
}