最近写C#串口通信程序,系统是B/S架构。SerialPort类有一个DataReceived事件,用来接收串口返回的数据,但这种方式在C/S架构下很好用,但B/S就不好处理了。所以写了一个同步模式接收返回数据的方法,不使用DataReceived事件。经过测试,可以正常使用。
一、MachineFactory类
为什么使用工厂类:售货机由不止一个厂家提供,接口协议都不一样。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Reflection; using System.IO; using System.IO.Ports; namespace IMachineDll { /// <summary> /// 售货机工厂类 /// </summary> public class MachineFactory { /// <summary> /// 货机接口缓存 /// </summary> private static Dictionary<string, IMachine> dicMachine = new Dictionary<string, IMachine>(); /// <summary> /// 锁变量 /// </summary> public static object _lock = new object(); /// <summary> /// 创建售货机类 /// </summary> /// <param name="path">DLL物理路径</param> /// <param name="dllName">DLL名称(不含扩展名),命名空间必须为DLL名称加“Dll”后缀,类名必须和DLL名称相同</param> /// <param name="com">串口名称,如:COM1</param> public static IMachine Create(string path, string dllName, string com) { if (!dicMachine.ContainsKey(dllName) || dicMachine[dllName] == null) { using (FileStream fs = new FileStream(path + dllName + ".dll", FileMode.Open, FileAccess.Read)) { using (MemoryStream ms = new MemoryStream()) { byte[] byteArray = new byte[4096]; while (fs.Read(byteArray, 0, byteArray.Length) > 0) { ms.Write(byteArray, 0, byteArray.Length); } Assembly assembly = Assembly.Load(ms.ToArray()); dicMachine[dllName] = (IMachine)assembly.CreateInstance(dllName + "Dll." + dllName, false, BindingFlags.Default, null, new object[] { com }, null, null); } } } return dicMachine[dllName]; } } }
二、Machine类
1、变量与构造函数
/// <summary> /// 串口资源 /// </summary> private SerialPort serialPort = null; public Machine(string com) { serialPort = new SerialPort(com, 9600, Parity.None, 8, StopBits.One); serialPort.ReadBufferSize = 1024; serialPort.WriteBufferSize = 1024; }
2、向串口发送数据,同步接收返回数据的方法:
/// <summary> /// 向串口发送数据,读取返回数据 /// </summary> /// <param name="sendData">发送的数据</param> /// <returns>返回的数据</returns> private byte[] ReadPort(byte[] sendData) { lock (MachineFactory._lock) { //打开连接 if (!serialPort.IsOpen) serialPort.Open(); //发送数据 serialPort.Write(sendData, 0, sendData.Length); //读取返回数据 DateTime dt = DateTime.Now; while (serialPort.BytesToRead == 0) { Thread.Sleep(1); if (DateTime.Now.Subtract(dt).TotalMilliseconds > 5000) //如果5秒后仍然无数据返回,则视为超时 { throw new Exception("主版无响应"); } } Thread.Sleep(50); byte[] recData = new byte[serialPort.BytesToRead]; serialPort.Read(recData, 0, recData.Length); //关闭连接 if (serialPort.IsOpen) serialPort.Close(); return recData; } }
优化版:
/// <summary> /// 向串口发送数据,读取返回数据 /// </summary> /// <param name="sendData">发送的数据</param> /// <returns>返回的数据</returns> private byte[] ReadPort(byte[] sendData) { lock (MachineFactory._lock) { //打开连接 if (!serialPort.IsOpen) serialPort.Open(); //发送数据 serialPort.Write(sendData, 0, sendData.Length); //读取返回数据 DateTime dt = DateTime.Now; while (serialPort.BytesToRead < 2) { Thread.Sleep(1); if (DateTime.Now.Subtract(dt).TotalMilliseconds > 5000) //如果5秒后仍然无数据返回,则视为超时 { throw new Exception("主版无响应"); } } List<byte> recList = new List<byte>(); byte[] recData = new byte[serialPort.BytesToRead]; serialPort.Read(recData, 0, recData.Length); recList.AddRange(recData); int length = recData[1] + 3; //报文数据总长度 while (recList.Count < length) { if (serialPort.BytesToRead > 0) { recData = new byte[serialPort.BytesToRead]; serialPort.Read(recData, 0, recData.Length); recList.AddRange(recData); } Thread.Sleep(1); } //关闭连接 if (serialPort.IsOpen) serialPort.Close(); return recList.ToArray(); } }
3、发送联机指令:
/// <summary> /// 联机 /// </summary> /// <param name="msg">传出错误信息</param> /// <returns>联机是否成功</returns> public bool Connect(out string msg) { byte[] sendData = new byte[] { 0x01, 0x01, 0x00, 0x00 }; CommonUtil.CalCheckCode(sendData); byte[] recData = ReadPort(sendData); if (recData.Length >= 4 && recData[0] == 0x01 && recData[1] == 0x02 && recData[2] == 0x00 && CommonUtil.ValidCheckCode(recData)) { switch (recData[3]) { case 0x00: msg = "控制主板正在重启"; return false; case 0x01: msg = "联机成功"; return true; case 0x02: msg = "控制主板正在维护"; return false; case 0x03: msg = "控制主板收到的数据格式不正确"; return false; default: msg = "未知状态"; return false; } } else if (IsRunning(recData, out msg) || !IsConnected(recData, out msg)) { return false; } else { throw new Exception("货机返回的数据格式不正确"); } }
三、如何使用
1、Controller层代码(还不完善,仅测试,真实情况是根据硬件信息,确定调用哪个Dll使用哪个串口):
#region 创建售货机接口 /// <summary> /// 创建售货机接口 /// </summary> private IMachine CreateMachine() { //return MachineFactory.Create(Request.PhysicalApplicationPath + @"in", "Machine", "COM1"); return MachineFactory.Create(@"D:售药机代码ReceptionMachineinDebug", "Machine", "COM1"); } #endregion #region 联机 /// <summary> /// 联机 /// </summary> /// <param name="msg">错误信息</param> /// <returns>联机是否成功</returns> private bool Connect(out string msg) { try { IMachine machine = CreateMachine(); DateTime dt1 = DateTime.Now; while (!machine.Connect(out msg)) //联机 { if (DateTime.Now.Subtract(dt1).TotalMilliseconds > 20000) { msg = "联机超时"; return false; } Thread.Sleep(50); } return true; } catch (Exception ex) { msg = ex.Message; return false; } } #endregion #region 单次联机 /// <summary> /// 单次联机 /// </summary> public ActionResult Conn() { string msg = null; Dictionary<string, object> dic = null; try { IMachine machine = CreateMachine(); if (machine.Connect(out msg)) //联机成功 { dic = new Dictionary<string, object>(); dic["ok"] = true; dic["msg"] = "成功"; return Content(JsonConvert.SerializeObject(dic)); } else { dic = new Dictionary<string, object>(); dic["ok"] = false; dic["msg"] = "联机失败:" + msg; return Content(JsonConvert.SerializeObject(dic)); } } catch (Exception ex) { dic = new Dictionary<string, object>(); dic["ok"] = false; dic["msg"] = "错误:" + ex.Message; return Content(JsonConvert.SerializeObject(dic)); } } #endregion #region 联机并使能硬纸币器 /// <summary> /// 联机并使能硬纸币器 /// </summary> public ActionResult ConnectEnable() { string msg = null; Dictionary<string, object> dic = null; try { IMachine machine = CreateMachine(); if (Connect(out msg) && machine.CoinEnable(out msg) && machine.PaperMoneyEnable(out msg)) //联机并使能硬纸币器成功 { dic = new Dictionary<string, object>(); dic["ok"] = true; dic["msg"] = "成功"; return Content(JsonConvert.SerializeObject(dic)); } else { dic = new Dictionary<string, object>(); dic["ok"] = false; dic["msg"] = "硬币器使能失败:" + msg; return Content(JsonConvert.SerializeObject(dic)); } } catch (Exception ex) { dic = new Dictionary<string, object>(); dic["ok"] = false; dic["msg"] = "错误:" + ex.Message; return Content(JsonConvert.SerializeObject(dic)); } } #endregion
2、前台代码:
@{ ViewBag.Title = "货机接口测试"; Layout = null; } <!DOCTYPE html> <html> <head> <title>@ViewBag.Title</title> <script type="text/javascript" src="~/Scripts/jquery-1.8.2.min.js"></script> <script type="text/javascript" src="~/Scripts/LongPolling.js"></script> </head> <body> <div style="padding: 20px;"> <input type="button" value="联机" onclick="connect()" /> <div style="font-size: 20px; line-height: 30px;"> <div style="padding: 20px;"> <span id="msg"> </span> </div> </div> </div> </body> </html> <script type="text/javascript"> //联机 function connect() { commonAjax({ url: "@Url.Content("/MachineInterface/Conn")", callback: function (data) { if (data.ok) { var html = "联机成功"; $("#msg").html(html); } else { alert(data.msg); } } }); } </script>