使用物联网设备(Arduino 和 GSM 模块)发送、接收和删除短信的方法
让我们来看看如何使用 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 模块的快照
必备组件
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 重用的
我们这样做。
- 首先,我们应该包含 SoftwareSerial 库。
- 让我们打开串口并将其波特率设置为 9600。我们将从串行通信的每秒 9600 位开始。有关详细信息,请参阅 http://arduino.cc/en/Serial/Begin
- 在 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 命令并从模块返回响应应该都没有问题。
让我们使用“Sscom32E”工具来实际操作 AT 命令。首先,您需要选择正确的串行 com 端口,保留默认的数据位、停止位等,然后点击“OpenCom”按钮,这样就可以与 Arduino 打开串行通信了。
下面是代码片段,我们使用 AT 命令 `AT+CMGL=”ALL”` 来读取所有短信。
更多 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 端口。
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 的快照。
这是使用 AT 命令发送短信的代码片段。发送短信的语法如下。
AT+CMGF=1 <ENTER> - 表示我们有兴趣发送文本消息。请注意,使用此命令无法发送 Unicode 消息。
AT+CMGS="+1224XXXXXX" <ENTER>
来自 CodeProject 发送和接收物联网设备(Arduino 和 GSM 模块)短信的测试消息 <CTRL-Z>
这是我们发送短信的操作。
- 发送“AT”命令以检查电话是否已连接。
- 发送命令 `AT+CMGF=1`,表示我们将发送文本消息。
- 发送命令 `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 模块会返回响应。如果一切顺利,消息将在大约一秒钟内发送,您应该能够收到。
读取短信
现在是时候看看如何从 SIM 内存中读取短信了。
让我们尝试理解用于读取消息的 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; } }
下面是删除短信选项卡的快照
关注点
当我研究和处理物联网设备时,我意识到短信集成对于几乎所有应用程序都是必需且有用的。有无数的应用程序可以想到使用短信功能。当然,这不是唯一的解决方案,但它是一种经济高效的解决方案,因为您真的不必担心第三方服务来发送短信。有时物联网设备可以根据接收到的某些特定消息来运行,在这种情况下,您可以绝对利用这些解决方案。
参考
历史
版本 1.0 - 最初发布关于如何使用 Arduino 和 GSM 模块发送、接收和删除短信 - 2015 年 3 月 15 日。