zoukankan      html  css  js  c++  java
  • 使用C# 实现串口拨号器的SIM卡通信

     参考网址:https://www.cnblogs.com/xugang/archive/2012/08/23/2652671.html

    写此博客意为抛砖引玉,希望能和博客园的朋友们探讨一下关于.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
    ");
             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};
    ", _SIM));
    
            System.Threading.Thread.Sleep(20 * 1000);
    
            //Console.WriteLine(" {0}  ATH TO:{1}", DateTime.Now, _SIM);
    
            WriteSerial("ATH
    ");
    
            Thread.Sleep(500);
            Console.WriteLine(read);
            if (read.Contains("ATHOK"))
            {
                Console.WriteLine(this.com.PortName + "端口拨号已完成!");
            }
            else
            {
                //System.Threading.Thread.Sleep(1000);
                WriteSerial("ATH
    ");
                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();
        }
    }
    复制代码

    测试结果:

  • 相关阅读:
    重新想象 Windows 8 Store Apps (15) 控件 UI: 字体继承, Style, ControlTemplate, SystemResource, VisualState, VisualStateManager
    重新想象 Windows 8 Store Apps (12) 控件之 GridView 特性: 拖动项, 项尺寸可变, 分组显示
    返璞归真 asp.net mvc (10) asp.net mvc 4.0 新特性之 Web API
    与众不同 windows phone (29) Communication(通信)之与 OData 服务通信
    与众不同 windows phone (33) Communication(通信)之源特定组播 SSM(Source Specific Multicast)
    与众不同 windows phone (27) Feature(特性)之搜索的可扩展性, 程序的生命周期和页面的生命周期, 页面导航, 系统状态栏
    与众不同 windows phone (30) Communication(通信)之基于 Socket TCP 开发一个多人聊天室
    返璞归真 asp.net mvc (12) asp.net mvc 4.0 新特性之移动特性
    重新想象 Windows 8 Store Apps (2) 控件之按钮控件: Button, HyperlinkButton, RepeatButton, ToggleButton, RadioButton, CheckBox, ToggleSwitch
    重新想象 Windows 8 Store Apps (10) 控件之 ScrollViewer 特性: Chaining, Rail, Inertia, Snap, Zoom
  • 原文地址:https://www.cnblogs.com/bruce1992/p/14836809.html
Copyright © 2011-2022 走看看