65.9K
CodeProject 正在变化。 阅读更多。
Home

使用物联网设备(Arduino 和 GSM 模块)发送、接收和删除短信的方法

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.84/5 (28投票s)

2016 年 3 月 2 日

CPOL

9分钟阅读

viewsIcon

160764

downloadIcon

7297

让我们来看看如何使用 Arduino 和 GSM 模块发送、接收和删除短信

引言

在本文中,我将指导您如何使用 Arduino 和 GSM SIM 900 模块发送和接收短信。

请注意,我使用的是 2G 模块;这意味着它只能与兼容 2G 的 SIM 卡配合使用。因此,它不能与 3G 或 4G LTE SIM 卡配合使用。如果您对我的测试 SIM 卡有疑问,使用的是 T-Mobile 标准预付费 SIM 卡。 

https://codeproject.org.cn/Articles/38705/Send-and-Read-SMS-through-a-GSM-Modem-using-AT-Com

我将涵盖以下主题。

先决条件 
背景 
使用代码

下面是 Arduino Uno 上 GSM SIM 900 模块的快照

GSM_Modem

必备组件

1) Arduino,最常用的是 Arduino Uno。

2) GSM SIM 900 – 这是一个便宜且易于使用的模块。您可以在 eBay 上购买。请注意,Arduino 的 GSM 模块有 2G、3G 等版本。我使用的是四频模块;这意味着它只能与兼容 2G 的 GSM SIM 卡配合使用。

3) SIM 900 Arduino 库 – 您可以从 http://www.gsmlib.org/download.html 下载。
下载 BETA_GSM_GPRS_GPS_IDE100_v307_1.zip 或最新版本。

背景

如果您是 Arduino 初学者,请参阅官方 Arduino 网站

http://arduino.cc/en/Guide/HomePage

AT 命令简介

http://www.developershome.com/sms/atCommandsIntro.asp

https://codeproject.org.cn/Articles/85636/Introduction-to-AT-commands-and-its-uses

SIM900 文档

http://www.seeedstudio.com/wiki/GPRS_Shield_V1.0

Arduino 串行通信基础知识

http://arduinobasics.blogspot.com/2012/07/arduino-basics-simple-arduino-serial.html

使用代码

我们将以非常通用的方式对 Arduino 进行编程,以接收在 PC 上运行的程序发送的 AT 命令。

您可以使用以下工具,它基本上通过串行通信连接 Arduino。连接后,您应该能够发送“AT 命令”并从 Arduino 接收响应。

Sscom32E 串行工具

http://www.seeedstudio.com/wiki/images/b/b2/Sscom32E.zip

下面是我们用于接收 AT 命令并通过串行通信(波特率为 9600)发送响应的代码片段。 

下面的 Arduino GSM/GPRS 模块代码片段是从 http://www.seeedstudio.com/wiki/GPRS_Shield_V1.0 重用的

我们这样做。

  1. 首先,我们应该包含 SoftwareSerial 库。
     
  2. 让我们打开串口并将其波特率设置为 9600。我们将从串行通信的每秒 9600 位开始。有关详细信息,请参阅 http://arduino.cc/en/Serial/Begin
     
  3. 在 loop 方法中,我们需要编写代码来接收 PC 上运行的应用程序发送的 AT 命令。我们还编写代码将 GSM 模块的响应发送回 PC。请注意,在将响应发送回 PC 时,我们一次读取一个字符,并将其保存在大小为 64 的缓冲区中,然后通过串口写入。最后,我们将清除缓冲区并将计数重置为零。
//Serial Relay - Arduino will patch a 
//serial link between the computer and the GPRS Shields
//at 9600 bps 8-N-1
//Computer is connected to Hardware UART
//GPRS Shield is connected to the Software UART 
 
#include <SoftwareSerial.h>
 
SoftwareSerial GPRS(7, 8);
unsigned char buffer[64]; // buffer array for data recieve over serial port
int count=0;              // counter for buffer array 

void setup()
{
  GPRS.begin(9600);     // the GPRS baud rate   
  Serial.begin(9600);   // the Serial port of Arduino baud rate. 
}
 
void loop()
{
  if (GPRS.available())              // if date is comming from softwareserial port ==> data is comming from gprs shield
  {
    while(GPRS.available())          // reading data into char array 
    {
      buffer[count++]=GPRS.read();   // writing data into array
      if(count == 64)break;
  }
    Serial.write(buffer,count);   // if no data transmission ends, write buffer to hardware serial port
    clearBufferArray();           // call clearBufferArray function to clear the storaged data from the array
    count = 0;                    // set counter of while loop to zero
  }
  if (Serial.available())         // if data is available on hardwareserial port ==> data is comming from PC or notebook
    GPRS.write(Serial.read());    // write it to the GPRS shield
}
void clearBufferArray()           // function to clear buffer array
{
  for (int i=0; i<count;i++)
    { buffer[i]=NULL;}            // clear all index of array with command NULL
}

在 Arduino IDE 中使用上面的代码,让我们编译并将其上传到连接了 GSM 模块的 Arduino,接收 AT 命令并从模块返回响应应该都没有问题。

ArduinoGSMCode

让我们使用“Sscom32E”工具来实际操作 AT 命令。首先,您需要选择正确的串行 com 端口,保留默认的数据位、停止位等,然后点击“OpenCom”按钮,这样就可以与 Arduino 打开串行通信了。

下面是代码片段,我们使用 AT 命令 `AT+CMGL=”ALL”` 来读取所有短信。

ReadAll-Messages

更多 AT 命令

检查 SIM 卡是否就绪

AT+CPIN?
+CPIN: READY
好的

获取网络信息

AT+COPS?
+COPS: 0,0,”T-Mobile”
好的

语音通话 

ATD1224XXX31XX;

挂断

ATH

测试信号强度

AT+CSQ
+CSQ: 11,0
好的

读取未读消息

AT+CMGL="REC UNREAD"

读取所有消息

AT+CMGL="ALL"

短信应用程序 (.NET WinForm)

让我们深入了解 .NET WinForm 应用程序,并尝试理解如何通过串行通信将 AT 命令发送到 Arduino。

下面是获取所有“COM”端口并将其添加到组合框中的代码片段,以便您可以选择用于与 Arduino 通信的特定端口。

注意 – 当您将 Arduino 连接到 PC 时,您应该能够看到它正在使用的 COM 端口。这就是您需要选择用于发送 AT 命令的端口。

string[] ports = SerialPort.GetPortNames();
// Add all port names to the combo box:
foreach (string port in ports)
{
    this.cboPortName.Items.Add(port);
}

接下来,我们看如何打开串行连接。在 UI 中,您会看到一个“Connect”按钮,单击它会触发下面的代码。您可以看到,我们正在使用 SMSHelper 来以指定的 COM 端口名称、波特率、数据位等打开 COM 端口。

SMS-App-Port-Settings

private void btnOK_Click(object sender, EventArgs e)
{
            try
            {
                //Open communication port 
                this.port = smsHelper.OpenPort(this.cboPortName.Text, 
                           Convert.ToInt32(this.cboBaudRate.Text),
                           Convert.ToInt32(this.cboDataBits.Text),
                           Convert.ToInt32(this.txtReadTimeOut.Text),
                           Convert.ToInt32(this.txtWriteTimeOut.Text));

                if (this.port != null)
                {
                    this.gboPortSettings.Enabled = false;
                    this.statusBar1.Text = "Modem is connected at PORT " + this.cboPortName.Text;

                    // Add tab pages
                    // Code for adding tabs goes here
                }
                else
                {
                    //MessageBox.Show("Invalid port settings");
                    this.statusBar1.Text = "Invalid port settings";
                }
            }
            catch (Exception ex)
            {
                ErrorLog(ex.Message);
            }
}

现在,我们来看一下如何使用 `System.IO.Ports.SerialPort` 类来打开和关闭串行 com 端口。下面是相应的代码片段。 

我们将创建一个 `SerialPort` 实例并设置所有必需的属性,如端口名称、波特率、数据位和停止位等。然后,我们打开一个串行端口连接,以便我们可以发送 AT 命令并接收响应。另请注意,`DataReceived` 事件正在被处理,当 Arduino 通过指定的串行端口发送数据进行通信时,该事件会被触发。

您会看到一件有趣的事情,那就是我们创建了一个 `AutoResetEvent` 实例。它代表一个等待句柄事件。在下面的代码片段中,在数据接收事件中,您会看到当收到一些数据时,我们可以设置等待句柄事件,发出数据可用的信号,以便进行读取。

接下来,您将看到如何从指定的串行端口读取 AT 命令和响应,以实现 Arduino 和 PC 之间的通信。 

public SerialPort OpenPort(string portName, int baudRate,
                            int dataBits, int readTimeout, int writeTimeout)
{
            receiveNow = new AutoResetEvent(false);
            SerialPort port = new SerialPort();

            try
            {           
                port.PortName = portName;                 //COM1
                port.BaudRate = baudRate;                 //9600
                port.DataBits = dataBits;                 //8
                port.StopBits = StopBits.One;             //1
                port.Parity = Parity.None;                //None
                port.ReadTimeout = readTimeout;           //300
                port.WriteTimeout = writeTimeout;         //300
                port.Encoding = Encoding.GetEncoding("iso-8859-1");
                port.DataReceived += new SerialDataReceivedEventHandler(port_DataReceived);
                port.Open();
                port.DtrEnable = true;
                port.RtsEnable = true;
            }
            catch (Exception ex)
            {
                throw ex;
            }
            return port;
}

public void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
            try
            {
                if (e.EventType == SerialData.Chars)
                {
                    receiveNow.Set();
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
}

//Close Port
public void ClosePort(SerialPort port)
{
            try
            {
                port.Close();
                port.DataReceived -= new SerialDataReceivedEventHandler(port_DataReceived);
                port = null;
            }
            catch (Exception ex)
            {
                throw ex;
            }
}

发送/执行 AT 命令 

下面是通过已打开的 COM 端口发送 AT 命令的代码片段。

我们调用 `SerialPort` 实例的“Write”方法,并传递要执行的命令。读取响应以确保命令已成功执行,否则我们可以抛出通用错误消息。

public string SendATCommand(SerialPort port,string command, 
                                                   int responseTimeout, string errorMessage)
{
            try
            {               
                port.DiscardOutBuffer();
                port.DiscardInBuffer();
                receiveNow.Reset();
                port.Write(command + "\r");
           
                string input = ReadResponse(port, responseTimeout);
                if ((input.Length == 0) || ((!input.EndsWith("\r\n> "))
                            && (!input.EndsWith("\r\nOK\r\n"))))
                    throw new ApplicationException("No success message was received.");
                return input;
            }
            catch (Exception ex)
            {
                throw ex;
            }
}   

读取 AT 命令响应

下面是读取 AT 命令响应的代码片段。 

我们将通过调用 `SerialPort` 实例的 `ReadExisting` 方法来读取串行数据;该方法返回一个部分响应,因此我们需要循环并附加数据,直到接收到的串行数据包含子字符串“OK”或“\r\n>”,这意味着我们已完全读取 AT 命令响应。

public string ReadResponse(SerialPort port, int timeout)
{
            string serialPortData = string.Empty;
            try
            {    
                do
                {
                    if (receiveNow.WaitOne(timeout, false))
                    {
                        string data = port.ReadExisting();
                        serialPortData += data;
                    }
                    else
                    {
                        if (serialPortData.Length > 0)
                            throw new ApplicationException("Response received is incomplete.");
                        else
                            throw new ApplicationException("No data received from phone.");
                    }
                }
                while (!serialPortData.EndsWith("\r\nOK\r\n") &&
                 !serialPortData.EndsWith("\r\n> ") && !serialPortData.EndsWith("\r\nERROR\r\n"));
            }
            catch (Exception ex)
            {
                throw ex;
            }
            return serialPortData;
}

发送短信

让我们深入了解如何触发短信。下面是应用程序 UI 的快照。

SMS-App-SendMessage

这是使用 AT 命令发送短信的代码片段。发送短信的语法如下。

AT+CMGF=1 <ENTER> - 表示我们有兴趣发送文本消息。请注意,使用此命令无法发送 Unicode 消息。

AT+CMGS="+1224XXXXXX" <ENTER>

来自 CodeProject 发送和接收物联网设备(Arduino 和 GSM 模块)短信的测试消息 <CTRL-Z>

这是我们发送短信的操作。

  1. 发送“AT”命令以检查电话是否已连接。
  2. 发送命令 `AT+CMGF=1`,表示我们将发送文本消息。
  3. 发送命令 `AT+CMGS="+1224XXXXXX"` <ENTER>

现在发送一条包含您想要发送的文本消息并以 <CTRL-Z> 结尾的命令。

public bool SendMessage(SerialPort port, string phoneNo, string message)
{
            bool isSend = false;
            try
            {                
                string recievedData = SendATCommand(port,"AT", 300, "No phone connected");
                string command = "AT+CMGF=1" + char.ConvertFromUtf32(13);
                recievedData = SendATCommand(port,command, 300, "Failed to set message format.");

                // AT Command Syntax - http://www.smssolutions.net/tutorials/gsm/sendsmsat/
                command = "AT+CMGS=\"" + phoneNo + "\"" + char.ConvertFromUtf32(13);
                recievedData = SendATCommand(port, command, 300,
                    "Failed to accept phoneNo");

                command = message + char.ConvertFromUtf32(26);
                recievedData = SendATCommand(port, command, 3000,
                    "Failed to send message"); //3 seconds

                if (recievedData.EndsWith("\r\nOK\r\n"))
                    isSend = true;
                else if (recievedData.Contains("ERROR"))
                    isSend = false;
        
                return isSend;
            }
            catch (Exception ex)
            {
                throw ex; 
            }          
 }    

下面是调试代码快照,您可以看到发送短信以触发短信时,GSM 模块会返回响应。如果一切顺利,消息将在大约一秒钟内发送,您应该能够收到。

SendMessage-Debug

读取短信

现在是时候看看如何从 SIM 内存中读取短信了。 

SMS-App-ReadAll-Messages_1

让我们尝试理解用于读取消息的 AT 命令。

1)    读取所有消息  - "AT+CMGL=\"ALL\""
2)    读取未读消息  -  "AT+CMGL=\"REC UNREAD\""
3)    读取已发送的存储消息 - "AT+CMGL=\"STO SENT\""
4)    读取未发送的存储消息 - AT+CMGL=\"STO UNSENT\""

我们将通过串行通信将上述 AT 命令发送到 GSM 模块。收到响应后,我们将对其进行解析并返回给调用者。

public ShortMessageCollection ReadSMS(SerialPort port, string atCommand)
{
            // Set up the phone and read the messages
            ShortMessageCollection messages = null;
            try
            {

                #region Execute Command
                // Check connection
                SendATCommand(port,"AT", 300, "No phone connected");
                // Use message format "Text mode"
                SendATCommand(port,"AT+CMGF=1", 300, "Failed to set message format.");
                // Read the messages
                string input = SendATCommand(port, atCommand, 5000, "Failed to read the messages.");
                #endregion

                #region Parse messages
                messages = ParseMessages(input);
                #endregion

            }
            catch (Exception ex)
            {
                throw ex;
            }

            if (messages != null)
                return messages;
            else
                return null;        
}

下面是解析短信的代码片段。响应包含带有 CMGL 的字符串,我们将使用正则表达式来匹配并获取格式化的短信。 
请注意,我们正在构建一个短信集合,并将其返回给调用者。 

public ShortMessageCollection ParseMessages(string input)
{
            ShortMessageCollection messages = new ShortMessageCollection();
            try
            {     
                Regex r = new Regex(@"\+CMGL: (\d+),""(.+)"",""(.+)"",(.*),""(.+)""\r\n(.+)\r\n");
                Match m = r.Match(input);
                while (m.Success)
                {
                    ShortMessage msg = new ShortMessage();
                    msg.Index = m.Groups[1].Value;
                    msg.Status = m.Groups[2].Value;
                    msg.Sender = m.Groups[3].Value;
                    msg.Alphabet = m.Groups[4].Value;
                    msg.Sent = m.Groups[5].Value;
                    msg.Message = m.Groups[6].Value;
                    messages.Add(msg);
                    m = m.NextMatch();
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
            return messages;
}

删除短信

让我们看看如何根据索引删除消息或删除所有消息(全部或已读)。 下面是代码片段,您可以看到如何根据通过已打开的串行 com 端口发送的指定 AT 命令来删除短信。

在我们深入研究删除短信的代码之前,让我们先了解 AT 命令的用法。 

AT+CMGD=<index><CR> - 根据指定的索引删除消息。

以下是删除短信的高级语法。有关详细信息,请参阅 http://www.developershome.com/sms/cmgdCommand.asp

+CMGD=index[,flag]

为了删除所有短信,会发送以下 AT 命令。其中值“4”用于简单地忽略索引并从存储区域删除所有短信。

AT+CMGD=1,4 

public bool DeleteMessage(SerialPort port, string atCommand)
{
            bool isDeleted = false;
            try
            {
                #region Execute Command
                string recievedData = SendATCommand(port,"AT", 300, "No phone connected");
                recievedData = SendATCommand(port,"AT+CMGF=1", 300, "Failed to set message format.");
                String command = atCommand;
                recievedData = SendATCommand(port,command, 300, "Failed to delete message");
                #endregion

                if (recievedData.EndsWith("\r\nOK\r\n"))
                {
                    isDeleted = true;
                }
                if (recievedData.Contains("ERROR"))
                {
                    isDeleted = false;
                }
                return isDeleted;
            }
            catch (Exception ex)
            {
                throw ex; 
            }            
} 

下面是删除短信选项卡的快照

SMS-App-DeleteMessage

关注点

当我研究和处理物联网设备时,我意识到短信集成对于几乎所有应用程序都是必需且有用的。有无数的应用程序可以想到使用短信功能。当然,这不是唯一的解决方案,但它是一种经济高效的解决方案,因为您真的不必担心第三方服务来发送短信。有时物联网设备可以根据接收到的某些特定消息来运行,在这种情况下,您可以绝对利用这些解决方案。 

参考

注意 – 前端 .NET WinForm 应用程序最初由 Syeda Anila Nusrat 编写。本文重用了大部分内容,并进行了一些小的增强和代码重构。

历史

版本 1.0 - 最初发布关于如何使用 Arduino 和 GSM 模块发送、接收和删除短信 - 2015 年 3 月 15 日。

© . All rights reserved.