使用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 解码




本文转自钢钢博客园博客,原文链接:http://www.cnblogs.com/xugang/archive/2012/08/23/2652671.html,如需转载请自行联系原作者

相关文章
|
移动开发 调度 C#
c# SIM 卡拨号
引用:http://www.cnblogs.com/xugang/archive/2012/08/23/2652671.html 写此博客意为抛砖引玉,希望能和博客园的朋友们探讨一下关于.NET 在工业方面的应用,包括:物联网、无线通信、嵌入式开发、工业控制等等。
1067 0
|
C# 数据安全/隐私保护
C#实现ADSL拨号功能及扩展
今天在园子里的博问中,看到一个关于打开和断开网络链接的提问。正好自己以前写过一个类似的程序,现在分享给大家。 这个简单的程序可以实现使用河南网通ADSL的帐户进行拨号、断开的功能,在Vista和XP系统中可以正常使用,其他系统未测试。
1036 0
|
6月前
|
开发框架 前端开发 .NET
C#编程与Web开发
【4月更文挑战第21天】本文探讨了C#在Web开发中的应用,包括使用ASP.NET框架、MVC模式、Web API和Entity Framework。C#作为.NET框架的主要语言,结合这些工具,能创建动态、高效的Web应用。实际案例涉及企业级应用、电子商务和社交媒体平台。尽管面临竞争和挑战,但C#在Web开发领域的前景将持续拓展。
188 3
|
6月前
|
SQL 开发框架 安全
C#编程与多线程处理
【4月更文挑战第21天】探索C#多线程处理,提升程序性能与响应性。了解C#中的Thread、Task类及Async/Await关键字,掌握线程同步与安全,实践并发计算、网络服务及UI优化。跟随未来发展趋势,利用C#打造高效应用。
196 3
|
7天前
|
C# 开发者
C# 一分钟浅谈:Code Contracts 与契约编程
【10月更文挑战第26天】本文介绍了 C# 中的 Code Contracts,这是一个强大的工具,用于通过契约编程增强代码的健壮性和可维护性。文章从基本概念入手,详细讲解了前置条件、后置条件和对象不变量的使用方法,并通过具体代码示例进行了说明。同时,文章还探讨了常见的问题和易错点,如忘记启用静态检查、过度依赖契约和性能影响,并提供了相应的解决建议。希望读者能通过本文更好地理解和应用 Code Contracts。
19 3
|
28天前
|
安全 C# 数据安全/隐私保护
实现C#编程文件夹加锁保护
【10月更文挑战第16天】本文介绍了两种用 C# 实现文件夹保护的方法:一是通过设置文件系统权限,阻止普通用户访问;二是使用加密技术,对文件夹中的文件进行加密,防止未授权访问。提供了示例代码和使用方法,适用于不同安全需求的场景。
|
2月前
|
API C#
C# 一分钟浅谈:文件系统编程
在软件开发中,文件系统操作至关重要。本文将带你快速掌握C#中文件系统编程的基础知识,涵盖基本概念、常见问题及解决方法。文章详细介绍了`System.IO`命名空间下的关键类库,并通过示例代码展示了路径处理、异常处理、并发访问等技巧,还提供了异步API和流压缩等高级技巧,帮助你写出更健壮的代码。
39 2
|
2月前
|
安全 程序员 编译器
C#一分钟浅谈:泛型编程基础
在现代软件开发中,泛型编程是一项关键技能,它使开发者能够编写类型安全且可重用的代码。C# 自 2.0 版本起支持泛型编程,本文将从基础概念入手,逐步深入探讨 C# 中的泛型,并通过具体实例帮助理解常见问题及其解决方法。泛型通过类型参数替代具体类型,提高了代码复用性和类型安全性,减少了运行时性能开销。文章详细介绍了如何定义泛型类和方法,并讨论了常见的易错点及解决方案,帮助读者更好地掌握这一技术。
71 11
|
2月前
|
SQL 开发框架 安全
并发集合与任务并行库:C#中的高效编程实践
在现代软件开发中,多核处理器普及使多线程编程成为提升性能的关键。然而,传统同步模型在高并发下易引发死锁等问题。为此,.NET Framework引入了任务并行库(TPL)和并发集合,简化并发编程并增强代码可维护性。并发集合允许多线程安全访问,如`ConcurrentQueue&lt;T&gt;`和`ConcurrentDictionary&lt;TKey, TValue&gt;`,有效避免数据不一致。TPL则通过`Task`类实现异步操作,提高开发效率。正确使用这些工具可显著提升程序性能,但也需注意任务取消和异常处理等常见问题。
46 1