参考网址: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<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# 实现串口拨号器的SIM卡通信的更多相关文章

  1. 02_电话拨号器intent说明

    怎么在第一个Activity打开第二个Activity?在一个Activity中打开另外一个Activity,实际上之前已经做过,就是电话拨号器. package com.itheima.callne ...

  2. Mono for Android—初体验之“电话拨号器”

    1.Main.axml文件: <?xml version="1.0" encoding="utf-8"?><LinearLayout xmln ...

  3. Android 笔记 day2 拨号器

  4. [Android]电话拨号器开发

    继续今天的Android,经过昨天大体了解了Android开发的一些基本文件结构,今天来做一个电话拨号器! 预期达到的效果 实现过程 首先还是按照昨天第一篇教程,新建一个项目叫PhoneCall的An ...

  5. Android-简单拨号器案例

    Android [19]简单电话拨号器 @方法步骤 1.新建一个android程序,项目名设置为 phone  ,然后打开  phone->res->layout->activity ...

  6. android案例一 电话拨号器

    效果图:   电话拨号器的核心原理:  意图   MainActivity代码:     private EditTest et_number; //加载一个布局 setContentView(R.l ...

  7. 无废话Android之常见adb指令、电话拨号器、点击事件的4种写法、短信发送器、Android 中各种布局(1)

    1.Android是什么 手机设备的软件栈,包括一个完整的操作系统.中间件.关键的应用程序,底层是linux内核,安全管理.内存管理.进程管理.电源管理.硬件驱动 2.Dalvik VM 和 JVM ...

  8. android之电话拨号器

    在android入门的案例中,除了HelloWorld这个经典案例,还有一个电话拨号器需要掌握,现在我就来个电话拨号器的示范,毕竟大牛也是从菜鸟进化而来的. 首先你应该知道自己要设置怎样的UI,然后创 ...

  9. Android实战--电话拨号器

    今天跟着黑马视频建立一个android app--电话拨号器 首先新建一个android项目 activity_main_xml中的代码如下: <RelativeLayout xmlns:and ...

随机推荐

  1. C语言:整数保存 原码 反码 补码

    #include <stdio.h> /* 本题结果为:-4 short类型占据2字节 ;赋值后实际占据了3个字节,所以有溢出警告提示,结果只保留0xfffc 保存二进制:1111 111 ...

  2. Spring RestTemplate 之post请求

    ●post请求:在RestTemplate中,POST请求可以通过如下三个方法来发起,但post提交方式又有两种 formData 和 payLoad,而且接口设计与传统的浏览器使用的提交方式又有差异 ...

  3. springboot-3-web开发

    一.视图层技术thymeleaf 我们一般都是基于3.x版本 1.流程: 导入依赖 <!--整合thymeleaf技术--> <dependency> <groupId& ...

  4. 微信小程序云开发-云存储的应用-识别行驶证

    一.准备工作 1.创建云函数identify 2.云函数identify中index.js代码 1 // 云函数入口文件 2 const cloud = require('wx-server-sdk' ...

  5. Java-数组有关

    1.复制数组 复制数组主要有三类方法: 1.使用循环语句逐个赋值数组元素 2.使用System类中的静态方法arraycopy 3.使用clone方法复制数组 对于2,详述如下: arraycopy( ...

  6. 网络损伤仪WANsim中的时延的不同模型

    网络损伤仪WANsim中的3种时延模型 时延指的是报文从网络的一端到达另一端所花费的时间. 网络损伤仪WANsim中为用户提供了3种时延损伤的模型.常量模型.均匀分布.正态分布. 这3种模型按照各自的 ...

  7. 数据结构与算法-排序(二)选择排序(Selection Sort)

    摘要 选择排序的逻辑是先遍历比较出序列中最大的,然后把最大的放在最后位置. 遵循这个逻辑,用代码实现时,做到1.减少比较次数之外,这里引入一个新的指标 - 稳定性,2.保证排序过程中的稳定性也是一个优 ...

  8. 使用JavaMailSender 发送邮件

    使用JavaMailSender 发送邮件 package com.juvenxu.mvnbook.account.email; import javax.mail.MessagingExceptio ...

  9. 自学linux——15.云主机的购买流程及域名的购买备案解析

    项目上线流程 一.服务器选配购买 项目上线的服务器必须是外网服务器 1.服务器购买情况 真实服务器(成本过高,购买内部自用) 云服务器(上线首选):阿里云,腾讯云,华为云 2.购买阿里云服务器:htt ...

  10. Java时间类从此变得清晰明了

    Java时间类 Java时间类分为Date 日期类和Calendar 日历类,相信很多小伙伴在初学时会对这个两个类的用法.区别以及有什么联系会感到疑惑,似乎懂了,但又不能具体说清,今天再带你来清晰的再 ...