使用C# 实现串口拨号器的SIM卡通信
阅读原文时间:2023年07月11日阅读:2

参考网址: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\\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 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 _portBaudRate = new Dictionary();
_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();  
}  

}

测试结果:

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章