Matteo Matteo - 4 years ago 166
C# Question

Serial Port AT Commands for GSM Modem Device not working on loop

I've been currently working on a .net project that sends SMS messages through a GSM Modem Device. I can manage to send a single SMS message just fine, but when I start to loop the AT Commands to send SMS to multiple recipients, the behaviour of the application gets very clunky. Sometimes it could only send an SMS to the first recipient it could find, most of the times none at all.

Below is the source code for my method on sending SMS to multiple recipients:

String MessageBody = String.Format("Everyone,\n\nEquipment Current Reading: {0} tph\nCurrent Status: {1}\n\n".Replace("\n",Environment.NewLine), CurrentValue, EqValue);

SerialPort SP = new SerialPort();
SP.PortName = "COM3";



using (TestEntities TE = new TestEntities())
{
List<vwRecipient_MasterList> RecipientList = TE.vwRecipient_MasterList.Where(r => r.Group_id == R_Group).Select(r => r).ToList();
foreach (vwRecipient_MasterList Recipient in RecipientList)
{
SP.Open();
String formattedRecipientNo = Char.ConvertFromUtf32(34) + Recipient.MobileNo + Char.ConvertFromUtf32(34);

SP.Write("AT+CMGF=1" + Char.ConvertFromUtf32(13));
SP.Write("AT+CMGS=" + formattedRecipientNo + Char.ConvertFromUtf32(13));
SP.Write(MessageBody + Char.ConvertFromUtf32(26) + Char.ConvertFromUtf32(13));
SP.Close();
}
}

Answer Source

So I did a bit more research on how to utilize the serial port class based on the MSDN reference (for serial ports) and a few articles around here at SO, and I've come up with this unorthodox solution that involves using the SerialDataReceivedEventHandler provisioned by the SerialPort class and an infinite while loop.

First of all, I created two properties in the class scope that is visible for both methods of the SendSMS(WebMethod) and the DataRecieved(Event):

// Holds the text output from the SerialPort 
public string spReadMsg { get; set; }

// Used as a fail-safe terminator of the infinite loop used in the Web Method. 
public DateTime executionTime { get; set; }

The following is the DataRecievedHandler event. Basically, all this event does is store the text response from the SerialPort in the spReadMsg property for the SendSMS method

private void DataRecievedHandler(object sender, SerialDataReceivedEventArgs e)
{
    try
    {
        SerialPort sp = (SerialPort)sender;
        string indata = sp.ReadExisting();
        Debug.Print("Data Received:");
        Debug.Print(indata);

        // Store to class scope property spReadMsg for the sendSMS method to read.
        spReadMsg = indata;
    }
    catch (Exception ex)
    {
        Debug.Print(ex.Message);          
    }
}

Finally, I added a few lines on my Web Method to listen to the DataRecieved event whenever the desired response for SMS Message has been sent successfully.

According to this article on using AT commands for Modem Devices: http://www.smssolutions.net/tutorials/gsm/sendsmsat. The SerialPort should return a +CMGS: # response to identify that message sending has been completed.

So All I have to do is wait for a +CMGS: response which will let the program know that the message has been sent and is ready to send the next message to the next recipient. I made a makeshift listener for the web method using an infinite while loop that terminates once the response +CMGS: has been read from the serial port or when the it takes longer than 30 seconds to get the desired response.

[WebMethod]
public void sendSMSMessage_inHouse(String Recipients, String MessageBody)
{

    String sanitizedRecipient = Recipients.Replace(" ", "");
    var RecipientList = Recipients.Split(',').ToList();

    String sanitizedMessage = MessageBody.Replace(@"\n", Environment.NewLine);


    SerialPort SP = new SerialPort();
    SP.PortName = "COM3";
    SP.DataReceived += new SerialDataReceivedEventHandler(DataRecievedHandler);

    SP.Open();

    // Initally set property to the "condtion" value to allow first message to be run without the datarecieved response from the serial port
    spReadMsg = "+CMGS:";

    // Set executionTime inital value for comparison
    executionTime = DateTime.Now;

    foreach (String Recipient in RecipientList)
    {

        // Infinite Loop listens to the response from the serial port
        while (true)
        {
            // If the +CMGS: response was recieved continue with the next message

            // Use Contains comparison for substring check since some of the +CMGS: responses 
            // contain carriage return texts along with the repsonse

            // Then empty the spReadMsg property to avoid the loop from prematurely 
            //sending the next message when the latest serial port response has not yet been 
            //updated from the '+CMGS:' response
            if (!string.IsNullOrEmpty(spReadMsg) && spReadMsg.Contains("+CMGS:"))
            {
                spReadMsg = string.Empty;
                break;
            }

            // If takes longer than 30 seconds to get the response since the last sent 
            //message - break.
            if (DateTime.Now > executionTime.AddSeconds(30))
            {
                break;
            }
        }

        // Once the while loop breaks proceed with sending the next message.            

        String formattedRecipientNo = Char.ConvertFromUtf32(34) + Recipient + Char.ConvertFromUtf32(34);

        SP.Write("AT+CMGF=1" + Char.ConvertFromUtf32(13));
            //Thread.Sleep(800);
        SP.Write("AT+CMGS=" + formattedRecipientNo + Char.ConvertFromUtf32(13));
            //Thread.Sleep(800);
        SP.Write(sanitizedMessage + Char.ConvertFromUtf32(26) + Char.ConvertFromUtf32(13));
            //Thread.Sleep(1000);
            //Thread.Sleep(2000);


        // Get the Datetime when the current message was sent for comparison in
        // the next while loop
        executionTime = DateTime.Now;

    }

    SP.Close();
}
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download