使用C# 實現串口撥號器的SIM卡通信


 

寫此博客意為拋磚引玉,希望能和博客園的朋友們探討一下關於.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 解碼

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM