寫此博客意為拋磚引玉,希望能和博客園的朋友們探討一下關於.NET 在工業方面的應用,包括:物聯網、無線通信、嵌入式開發、工業控制等等。歡迎探討,多多指教!^_^
下面是我在開發中,使用C#代碼實現對安裝在COM 串行端口上的SIM卡撥號器的撥號調度程序。
應用場景:
在使用新能源的風光互補路燈遠程管理系統中,通信服務器需要通過無線通信方式喚醒上位機。
> 上位機中內置GPRS 無線上網卡,被安裝在風光互補路燈中。
> 通信服務器上擴展出4個COM 串行端口,分別安裝有:西門子C35TS 撥號器和西門子MC52I 撥號器。
使用需求:
> 監控中心跟上位機進行通信前,對沒有連接上的上位機先使用撥號器喚醒;
> 由於長時間連續使用撥號器進行撥號,將導致撥號器的宕機情況,所以采用輪番調用的線性方式使用4個撥號器;
> 實現自動檢測服務器上的COM 串行端口,並自動識別可使用的撥號器;
> 增加撥號器后,程序能自動識別並添加使用;
> 拔出撥號器后,程序能自動識別並停止使用;
> 能克服撥號器的宕機、假死等異常情況,並在指定的間隔時間重新檢測撥號器,並添加到服務器中使用;
> 讓撥號器通過SIM卡,實現對上位機的撥號,掛機等功能;
程序實現:
程序中應用到AT 指令集,詳細介紹請看百度百科。這里附上一些簡單的AT 指令集:
AT 回車換行 返回:OK ATD13800000000; 回車換行 建立呼叫 ATA 回車換行 接聽電話 ATH 回車換行 掛機 AT+IPR=9600 回車換行 設置模塊波特率為9600 AT+CSCA=13800000000 回車換行 設置短信中心號碼 AT+CMGF=1 回車換行 設置短信格式為文本方式(=0為PDU方式,用於發送數據和中文) AT+CMGS 發送文本短信,具體如下: AT+CMGS=13800000000 >0000123456789
在程序項目中,需要引用如下程序集:
using System.IO.Ports; using System.Threading; using System.Collections;
並使用到了.NET 的串行端口資源 SerialPort 類。
MySerialPort 類
對每一個連接到COM 串行端口的撥號器實例化 MySerialPort 對象,代碼如下:
public class MySerialPort { private SerialPort com; public MySerialPort(string _portName) { this.com = new SerialPort(); //接收數據事件 this.com.DataReceived += new SerialDataReceivedEventHandler(com_DataReceived); //串口名 com.PortName = _portName; this.PortName = _portName; // BaudRate 串行波特率 com.BaudRate = 9600; //默認值 // 是否啟用請求發送 (RTS) 信號。 com.RtsEnable = true; //由計算機發送 Request To Send 信號到聯接的調制解調器,以請示允許發送數據。 // 是否使Data Terminal Ready (DTR)線有效。 xugang 2012.8.20 添加 com.DtrEnable = true; //Data Terminal Ready 是計算機發送到調制解調器的信號,指示計算機在等待接受傳輸。 try { com.Open(); } catch //(Exception) { Close(); } } public MySerialPort(string _portName, int _baudRate):this(_portName) { if (_baudRate != 0) { // BaudRate 串行波特率 com.BaudRate = _baudRate; } } private string portName; //串口名稱 public string PortName { get { return portName; } set { portName = value; } } // BaudRate 串行波特率 public int BaudRate { get { return com.BaudRate; } set { com.BaudRate = value; } } private bool isWorking; //設置是否正在使用 public bool IsWorking { get { return isWorking; } set { isWorking = value; } } // 檢測當前端口是否安裝有撥號器 public bool HasModem() { read = ""; //清空返回緩沖區 WriteSerial("AT\r\n"); Thread.Sleep(100); Console.WriteLine(read); if (read.Contains("ATOK")) { Console.WriteLine(this.com.PortName + "端口能使用!"); return true; } else return false; } //進行撥號,喚醒上位機 public void Dialing(string _SIM) { IsWorking = true; //正在撥號 read = ""; //清空返回緩沖區 WriteSerial(string.Format("ATD{0};\r\n", _SIM)); System.Threading.Thread.Sleep(20 * 1000); //Console.WriteLine(" {0} ATH TO:{1}", DateTime.Now, _SIM); WriteSerial("ATH\r\n"); Thread.Sleep(500); Console.WriteLine(read); if (read.Contains("ATHOK")) { Console.WriteLine(this.com.PortName + "端口撥號已完成!"); } else { //System.Threading.Thread.Sleep(1000); WriteSerial("ATH\r\n"); Thread.Sleep(500); Console.WriteLine(read); if (read.Contains("ATHOK")) { Console.WriteLine(this.com.PortName + "端口撥號已完成!"); } else { IsWorking = false; //撥號完成 throw new Exception(this.com.PortName + "撥號異常!"); } } IsWorking = false; //撥號完成 } /// <summary> /// 向串口端發送命令! /// </summary> /// <param name="s">命令字符串</param> private void WriteSerial(string s) { //mLogger.Info(s); byte[] buff = Encoding.ASCII.GetBytes(s); try { this.com.Write(buff, 0, buff.Length); } catch (Exception ex) { //WriteExecLog.Writing(ex); Console.WriteLine(ex.Message); } } //int n = 0; string read = ""; //接收數據事件方法 void com_DataReceived(object sender, SerialDataReceivedEventArgs e) { if (sender is SerialPort) { try { SerialPort mySerial = sender as SerialPort; read += mySerial.ReadLine().Trim(); //Console.WriteLine(mySerial.PortName + " 第" + n.ToString() + "接收數據:" + read); //n++; } catch (TimeoutException) { return; //xg備忘:可以將異常寫入日志! } catch (Exception) { return; //xg備忘:可以將異常寫入日志! } } } //關閉 public void Close() { if (com != null) { com.Close(); com.Dispose(); } } //private string ReadSerial() //{ // while (_keepReading) // { // if (com.IsOpen) // { // //byte[] readBuffer = new byte[com.ReadBufferSize + 1]; // byte[] readBuffer = new byte[10]; // try // { // //int count = com.Read(readBuffer, 0, com.ReadBufferSize); // int count = com.Read(readBuffer, 0, 9); // String SerialIn = System.Text.Encoding.ASCII.GetString(readBuffer, 0, count); // if (count != 0) // { // return SerialIn; // } // } // catch (TimeoutException) // { // return ""; // } // } // else // { // TimeSpan waitTime = new TimeSpan(0, 0, 0, 0, 50); // Thread.Sleep(waitTime); // } // } // return ""; //} }
SerialPortList 類
定義一個 SerialPortList 類,實現對所有連接上的撥號器 MySerialPort 對象進行管理和調度使用。代碼如下:
public class SerialPortList { //已經安裝了撥號器的串口對象 private List<MySerialPort> al = null; private Dictionary<string, int> portBaudRate = null; //波特率配置列表 public Dictionary<string, int> PortBaudRate { get { return portBaudRate; } set { portBaudRate = value; } } private bool hasPort = false; //當前有無可使用的串口撥號器 public bool HasPort { get { return hasPort; } //set { hasPort = value; } } private int reCheckMinutes = 30; //默認30分鍾 //串口撥號器的重新檢測間隔分鍾 public int ReCheckMinutes { get { return reCheckMinutes; } set { reCheckMinutes = value; } } #region 構造方法重載 public SerialPortList() { } public SerialPortList(Dictionary<string, int> _portBaudRate) { this.portBaudRate = _portBaudRate; } public SerialPortList(int _reCheckMinutes) { this.reCheckMinutes = _reCheckMinutes; } public SerialPortList(Dictionary<string, int> _portBaudRate,int _reCheckMinutes) { this.portBaudRate = _portBaudRate; this.reCheckMinutes = _reCheckMinutes; } #endregion 構造方法重載 /// <summary> /// 獲得串口上已經安裝了撥號器的對象 /// </summary> public void GetSerialPortList() { al = new List<MySerialPort>(); //步驟一: 獲得所有的串口名稱(列表) string[] ports = SerialPort.GetPortNames(); foreach (string port in ports) { MySerialPort mySerialPort = null; Console.WriteLine("正在檢測:" + port ); //測試使用 //是否設置波特率? if (portBaudRate != null && portBaudRate.ContainsKey(port) && portBaudRate[port] != 0) { mySerialPort = new MySerialPort(port, portBaudRate[port]); } else mySerialPort = new MySerialPort(port); bool ok = mySerialPort.HasModem(); if (ok) { al.Add(mySerialPort); } else { mySerialPort.Close(); mySerialPort = null; } } // 判斷當前計算機有無可使用串口端 hasPort = al.Count <= 0 ? false : true; } /// <summary> /// 重新獲得串口上已經安裝了撥號器的對象 /// </summary> public void ReGetSerialPortList() { if (al == null) GetSerialPortList(); else { //步驟一: 重新獲得所有的串口名稱(列表) string[] portsName_2 = SerialPort.GetPortNames(); //如果當前串口數目 > 正在使用的COM if (portsName_2.Length > al.Count) { Console.WriteLine("正在重新檢測可以使用的撥號器!"); //測試使用 foreach (string pName_2 in portsName_2) { //當前串口名是否存在撥號列表中 bool hasAt = al.Exists(delegate(MySerialPort port_1){ return pName_2 == port_1.PortName; }); //如果當前串口名不存在撥號列表中,則重新檢測! if (!hasAt) { Console.WriteLine("正在重新檢測:" + pName_2); //測試使用 MySerialPort mySerialPort = null; //是否設置波特率? if (portBaudRate != null && portBaudRate.ContainsKey(pName_2) && portBaudRate[pName_2] != 0) { mySerialPort = new MySerialPort(pName_2, portBaudRate[pName_2]); } else mySerialPort = new MySerialPort(pName_2); bool ok = mySerialPort.HasModem(); if (ok) { al.Add(mySerialPort); } else { mySerialPort.Close(); mySerialPort = null; } } } } } // 判斷當前計算機有無可使用串口端 hasPort = al.Count <= 0 ? false : true; } /// <summary> /// 重新獲得串口上已經安裝了撥號器的對象 (波特率使用默認值) /// </summary> public void ReGetSerialPortList(int _reCheckMinutes) { //串口撥號器的重新檢測間隔分鍾 reCheckMinutes = _reCheckMinutes; ReGetSerialPortList();//波特率全部使用默認值 } /// <summary> /// 釋放所有串口資源組件 /// </summary> public void ClearAllSerialPort() { if (al != null) { for (int i = 0; i < al.Count; i++) { al[i].Close(); al[i] = null; } al = null; } if (portBaudRate != null) { portBaudRate = null; } } private int index_Number = -1; //串口的調度號 private int IndexNumber() { lock (this) { if (index_Number + 1 >= al.Count) { if (al.Count == 0) index_Number = -1; else index_Number = 0; } else { index_Number++; } return index_Number; } } /// <summary> /// 對已經安裝了撥號器的串口調度使用 /// </summary> private void UseingSerialPort(string _SIM) { if (al == null) return; // 等待線程進入 Monitor.Enter(al); MySerialPort getPort = null; try { //獲得當前調用的串口對象的索引號 int num = IndexNumber(); if (num >= 0) //判斷是否存在撥號器 { getPort = al[num]; if (getPort != null && !getPort.IsWorking) { getPort.Dialing(_SIM); //對 SIM 進行撥號,喚醒上位機 } } } catch { //再一次檢查該 COM 能否使用! (范工提議) if (getPort != null) { string re_PortName = getPort.PortName; al.Remove(getPort); //從可用列表去除 getPort.Close(); MySerialPort mySerialPort = new MySerialPort(re_PortName); bool ok = mySerialPort.HasModem(); if (ok) { al.Add(mySerialPort); //重新添加到列表 } else { mySerialPort.Close(); mySerialPort = null; } } } finally { // 通知其它對象 Monitor.Pulse(al); // 釋放對象鎖 Monitor.Exit(al); } } //重新檢測端口時間 private DateTime dtCheck = DateTime.Now; /// <summary> /// 調用撥號器 /// </summary> /// <param name="_SIM"></param> public void InvokingSerialPort(string _SIM) { if (hasPort == false) { // 獲得串口上已經安裝了撥號器的對象 this.GetSerialPortList(); } else { this.UseingSerialPort(_SIM); //Thread.Sleep(5000); //定期檢測串口列表 if (dtCheck.AddMinutes(reCheckMinutes) <= DateTime.Now) { // 重新獲得串口上已經安裝了撥號器的對象 this.ReGetSerialPortList(); dtCheck = DateTime.Now; } } } }
測試代碼如下:
class Program { static void Main(string[] args) { // 獲得串口上已經安裝了撥號器的對象 (自定義波特率) Dictionary<string, int> _portBaudRate = new Dictionary<string, int>(); _portBaudRate["COM5"] = 9600; _portBaudRate["COM6"] = 9600; _portBaudRate["COM7"] = 9600; SerialPortList list = new SerialPortList(_portBaudRate,5); try { // 獲得串口上已經安裝了撥號器的對象 list.GetSerialPortList(); if (list.HasPort == false) { Console.WriteLine("當前計算機無可使用的串口撥號器!"); } while (list.HasPort) { // 調用撥號器 list.InvokingSerialPort("13500000000"); // 實際SIM卡號 Thread.Sleep(5000); } } finally { // 釋放所有串口資源組件 list.ClearAllSerialPort(); } Console.ReadLine(); } }
測試結果:
參考資源:
在C# 中使用SerialPort 類實現串口通信(陸續更新)
AT 指令發送短信流程
短信 PDU 解碼