引用:http://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  回车换行  返回:OKATD13800000000;  回车换行  建立呼叫ATA  回车换行  接听电话ATH  回车换行  挂机AT+IPR=9600  回车换行  设置模块波特率为9600AT+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();//如果当前串口数目 > 正在使用的COMif (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 解码

c# SIM 卡拨号相关推荐

  1. android指定sim卡拨号,使用双SIM卡设备中的指定SIM拨打电话

    过去几天我一直在寻找这个,我开始知道: "开箱即用的Android不支持双SIM卡.这是制造商的自定义修改,并且没有公共API来控制它." 下面的链接提供了一个解决方案,但它不能在 ...

  2. android指定sim卡拨号,android – 在双SIM卡设备中使用指定的SIM拨打电话

    过去几天我一直在寻找这个,我开始知道: "开箱即用的Android不支持双SIM卡.这是制造商的自定义修改,并且没有公共API来控制它." 下面的链接提供了一个解决方案,但它不能在 ...

  3. 使用C# 实现串口拨号器的SIM卡通信

    写此博客意为抛砖引玉,希望能和博客园的朋友们探讨一下关于.NET 在工业方面的应用,包括:物联网.无线通信.嵌入式开发.工业控制等等.欢迎探讨,多多指教!^_^ 下面是我在开发中,使用C#代码实现对安 ...

  4. PhoneAccount流程解析(SIM卡管理器)拨号过程中解析PhoneAccount

    刚开始看拨号流程是 总是出现PhoneAccount和PhoneAccountHandler,一直不知道是干嘛使的.所以今天写一篇文章来记录一下免得忘了. 在网上找了挺多资料,然后对照代码看了一遍. ...

  5. android sim卡联系人存储格式,Android获取手机通讯录、sim卡联系人及调用拨号界面方法...

    android获取手机通讯录联系人信息 private void getPhoneContacts() { ContentResolver resolver = this.getContentReso ...

  6. 小米3显示sim卡无服务器,小米手机无服务怎么解决

    原标题:小米手机无服务怎么解决 方法一: 1.第一步是检查电话卡是否正确插入卡槽中.这种情况也会发生很长时间.如果未正确插入卡插槽,则将导致无法维修. 2.要注意的第二件事是,如果所购买的小米手机是电 ...

  7. 4g通信模块怎么连接sim卡_4G通信模块在ARM平台下的应用

    4G模块是连接物与物的重要载体,是终端设备接入物联网的核心部件之一.随着4G的普及,许多新兴市场对4G通信模块的需求都在日益扩大,那么在ARM平台的嵌入式设备上如何快速的应用4G模块呢? 4G通信模块 ...

  8. 中国移动开通eSIM 一号双终端将要取代传统SIM卡?

    中国移动开通eSIM 一号双终端将要取代传统SIM卡? 近日,中国移动官方宣布,中国移动"eSIM一号双终端"业务于6月1日在天津.上海.南京.杭州.广州.深圳.成都7个城市正式启 ...

  9. 手机MODEM 开发(33)---SIM卡基础知识

    SIM卡基础知识 什么是SIM卡 SIM卡是(Subscriber Identification Module 客户识别模块)的缩写,也称为用户身份识别卡,移动无线终端必须装上此卡方能使用.SIM卡是 ...

  10. 如何正确使用SIM卡呢?

    使用智能SIM卡请注意 1.请勿将卡弯曲,卡上的金属芯片更应小心保护: 2.保持金属芯片清洁,避免沾染尘埃及化学物品: 3.为保护金属芯片,请避免经常将SIM卡从手机中抽出: 4.请勿将SIM卡置于超 ...

最新文章

  1. 逻辑心理测试题:三囚分汤
  2. 头发一周洗几次才适宜? - 生活至上,美容至尚!
  3. R语言tseries包
  4. Jsp、Servlet
  5. java虚拟机中xms_java JVM虚拟机选项: Xms Xmx PermSize MaxPermSize 区别
  6. java字符串笔试题_五道Java常见笔试题及答案汇总
  7. 抖音去水印解析网址入口_抖音去水印 视频去水印 小工具
  8. finereport 格式化金额函数_帆软报表常用函数总结
  9. 华为主题锁屏壁纸换不掉_华为手机总出现一些不明照片咋回事,原来你没关闭这个默认设置...
  10. box-sizing:boder-box
  11. 操作系统学习笔记(五)---进程同步
  12. I came, I saw, I hacked Automated Generation of Process-independent Attacks for ICS
  13. 《Vue.js实战》第七章.组件
  14. 地理商业智能发展浅析
  15. JS中children与childNodes的区别
  16. 犯罪分子检测--Eye in the Sky: Real-time Drone Surveillance System for Violent Individuals
  17. 易信公众平台开发(ASP.NET WebForm) Using Senparc.Weixin.MP SDK
  18. 刘轩-心理学-十点课-如何克服拖延症
  19. matlab 三维时频幅值图
  20. 试题 F: 统计子矩阵

热门文章

  1. 申请AppleID的界面,连继续按钮都看不到
  2. 百度手机输入法中的五笔9键盘有问题?
  3. 计算机应用水平excel考什么,全国专业技术人员计算机应用能力考试EXCEL2003中文字处理全真模拟试卷(三)...
  4. C# dataGridView控件 获取整行宽度 整列高度 设置某列宽度 设置某行高度
  5. C# richTextBox滚动到最后一行 显示最后一行 自动跳转最后一行
  6. 理论知识内容有_育婴师培训课程包含哪些内容?
  7. ios添加全局悬浮按钮_MIUI10快报:全局透明壁纸上线,本地视频支持设为锁屏壁纸...
  8. linux web接口返回乱码,【bug】测试环境的API接口,返回内容为乱码
  9. python显示1000以内的斐波拉契数列_Python 实现斐波那契数列方法及其优化总结
  10. 性能服务器可以同时标注吗,服务器上有内存标注