using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO.Ports;
using System.Runtime.CompilerServices;
using System.Diagnostics;
using System.Threading;
using Util.Log;
namespace SerialLibrary
{
public class SerialLib
{
#region Custom
private bool running; //当前运行状态
private SerialPort sp; //串口控件
private long received_count, send_count; //发送总字节,接收总字节
private byte[] Received; //所有接收数据的缓存
private int received_offset; //当前缓冲存在空闲的位置
private int cmd_offset; //已解出的命令的位置,下一个可用的开始位置
const int BUF_SIZE = 512;
const byte STX = 0x02; //STX
const byte ETX = 0x03; //ETX
private Dictionary<byte, WaitEvent> waitList;
#endregion
public SerialLib()
{
send_count = 0;
received_count = 0;
received_offset = 0;
cmd_offset = 0;
waitList = new Dictionary<byte, WaitEvent>();
running = false;
sp = new SerialPort();
sp.ReadBufferSize = 512;
sp.WriteBufferSize = 512;
sp.BaudRate = 9600;
sp.StopBits = StopBits.One;
sp.DataBits = 8;
sp.Parity = Parity.None;
Received = new byte[BUF_SIZE];
sp.DataReceived += new SerialDataReceivedEventHandler(sp_DataReceived);
}
///
/// 命令等待回应
///
class WaitEvent : IDisposable
{
public ManualResetEvent Event;
public byte[] recv;
public WaitEvent()
{
recv = null;
Event = new ManualResetEvent(false);
}
public void Dispose()
{
recv = null;
Event.Dispose();
Event = null;
}
}
/// 接收串口数据,取出有效命令,发到线程池待处理
/// 整个方法加锁,防止多次进入接收影响全局变量的值。必须完成一次后才能再次进入接收
[MethodImpl(MethodImplOptions.Synchronized)]
private void sp_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
if (!running) return;
SerialPort sp = (SerialPort)sender;
int n = sp.BytesToRead;
if (received_offset + n > BUF_SIZE) //数据包超大,放不下,只接收能放下的字节数
{
pack_received_buffer(); //压缩
if (received_offset + n > BUF_SIZE) //压缩后仍不能接收的,减少接收空间
{
n = BUF_SIZE - received_offset;
}
}
received_count += n; //接收的总字节数
byte[] buf = new byte[n];
sp.Read(buf, 0, n);
Array.Copy(buf, 0, Received, received_offset, n);
received_offset += n; //移入后偏移
Debug.WriteLine("接收位置-{0},命令位置-{1},接收总字节-{2},发送总字节-{3}", received_offset, cmd_offset, received_count, send_count);
Debug.WriteLine("接收数据:" + dump_buffer(buf));
//按协议中长度找协议末尾
int datalen = get_int(Received, cmd_offset + 1, 2);
int end_offset = cmd_offset + 3 + datalen; //结束符的位置
end_offset = end_offset < received_offset ? end_offset : cmd_offset + 1; //防止超过当前已读字节,读到垃圾字节
if (Received[end_offset] != ETX) return; //接收数据不完整,等待下次接收
while (Received[end_offset] != ETX && end_offset < received_offset) end_offset++;
if (end_offset == received_offset)
{
//如果找了超过256字节仍无有效内容,直接清空全部。单包数据应该不超过128
if (end_offset > cmd_offset + 256)
{
received_offset = 0;
cmd_offset = 0;
}
return; //没有找到结束符
}
end_offset++; //后移一字节,包插进 LRC校验值
byte[] cmd_buf = new byte[end_offset - cmd_offset + 1];
Array.Copy(Received, cmd_offset, cmd_buf, 0, cmd_buf.Length); //复制出一条完整的命令
ThreadPool.QueueUserWorkItem(receive_serial_data, cmd_buf);
cmd_offset = end_offset + 1; //跳过LRC,到下一条
if (received_offset > BUF_SIZE * 0.85) //缓存位置超过85%。实际不太可能,除非受干扰全乱码
{
pack_received_buffer();
}
}
private void receive_serial_data(object data)
{
byte[] cmd_buf = (byte[])data;
Debug.WriteLine("处理数据:" + dump_buffer(cmd_buf));
Log.Info("处理数据:" + dump_buffer(cmd_buf));
byte lrc = cmd_buf[cmd_buf.Length - 1]; //取最后一字节,lrc
if (lrc != calc_lrc(cmd_buf, 3, cmd_buf.Length - 2))
{
Debug.WriteLine("数据LRC校验失败,不处理");
}
byte cmdtype = cmd_buf[3];
lock (waitList)
{
if (waitList.ContainsKey(cmdtype))
{
int dlength = get_int(cmd_buf, 1, 2);
WaitEvent we = waitList[cmdtype];
if (dlength > 0)
{
we.recv = new byte[dlength + 5];
Array.Copy(cmd_buf, 0, we.recv, 0, dlength + 5); //如果有数据返回,包括命令类型一起返回
}
we.Event.Set();
}
}
}
///
/// 启动串口
///
///通卡模块所在的串口号
///
public int Start(int port)
{
if (running) return 0; //已经打开过的,直接返回成功
sp.PortName = string.Format("COM{0}", port);
try
{
sp.Open();
sp.DiscardInBuffer();
sp.DiscardOutBuffer();
running = true;
Log.Info("通卡模块串口COM" + port.ToString() + "启动成功");
return 0;
}
catch (Exception e)
{
Log.Error("启动串口失败: ", e);
return 1;
}
}
///
/// 关闭串口
///
public void Stop()
{
if (running)
{
try
{
if (sp.IsOpen) sp.Close();
Log.Info("通卡模块串口关闭成功");
}
catch (Exception e)
{
Log.Error("串口关闭异常: ", e);
}
running = false;
}
}
///
/// 发送串口命令
///
///命令类型
///发送数据
///接收数据。可能为 null
///0为处理成功, 3为无响应,其他为失败
public int process(byte cmdType, byte[] data, out byte[] recvdata, int Wait)
{
recvdata = null;
cmd_offset = 0;
int dlength = data.Length;
byte[] cmd = new byte[dlength + 6]; //1头长+2长度(bcd)+1命令长度(bcd)+4长度终端编号(bcd)+4长度表文流水号(bcd)+N长度报文+1尾长+1校验长
int datalen = data.Length + 1;
bcdstr_to_byte_array(datalen.ToString(), ref cmd, 1, 2);//设置数据长度两个字节
cmd[0] = STX;
cmdType = int2bcd(cmdType);
cmd[3] = cmdType;
Array.Copy(data, 0, cmd, 4, dlength);
cmd[4 + dlength] = ETX;
cmd[5 + dlength] = calc_lrc(cmd, 3, cmd.Length - 2);
send_count += cmd.Length;
Debug.WriteLine("发送命令:" + dump_buffer(cmd));
Log.Info("发送命令:" + dump_buffer(cmd));
try
{
sp.Write(cmd, 0, cmd.Length);
if (Wait == 0) return 0; //不需要接收回复
//等待回复,超时 Wait 秒
WaitEvent we = new WaitEvent();
lock (waitList)
{
waitList[cmdType] = we;
}
DateTime dt = DateTime.Now;
dt = dt.AddSeconds(Wait);
while (!we.Event.WaitOne(50) && DateTime.Now < dt)
{
Thread.Sleep(50);
}
if (!we.Event.WaitOne(1))
{
lock (waitList)
{
waitList.Remove(cmdType);
}
Debug.WriteLine("命令超时,没收到回应");
return 3; //未收到响应
}
if (we.recv != null)
{
recvdata = new byte[we.recv.Length];
Array.Copy(we.recv, recvdata, we.recv.Length);
Log.Info("接受命令:" + dump_buffer(recvdata));
}
lock (waitList)
{
waitList.Remove(cmdType);
}
we.Dispose();
we = null;
return 0;
}
catch (Exception e)
{
Log.Error("串口处理异常: ", e);
return 1;
}
}
///
/// 整数转为bcd码, 12(dec) --> 0x12
///
///
///
private byte int2bcd(int x)
{
x = (byte)x;
return (byte)((x / 10 * 16) + x % 10);
}
///
/// bcd转为整数, 0x12 --> 12(dec)
///
///
///
private int bcd2int(byte x)
{
return (x / 16 * 10) + (x % 16);
}
///
/// 将bcd字符串转换成字节数据,填充到指定位置的指定长度
///
///
///
///
///
private void bcdstr_to_byte_array(string bcdstr, ref byte[] array, int Start, int Length)
{
int i = bcdstr.Length;
int k = 0;
byte x;
string st;
while (i > 0)
{
if (i > 1) st = bcdstr.Substring(i - 2, 2);
else st = bcdstr.Substring(0, 1); //不是双字符对齐时,会剩下一个字符要转换
if (!byte.TryParse(st, out x)) x = 0;
array[Start + Length - k - 1] = int2bcd(x);
k++;
i -= 2;
}
while (Start + Length - k - 1 >= Start)
{
array[Start + Length - k - 1] = 0;
k++;
}
}
///
/// 压缩接收缓冲,使有效数据前移,空间后方空间
///
private void pack_received_buffer()
{
//后续缓存空间不够,向前移动,覆盖前面已用过的空间
byte[] swap = new byte[BUF_SIZE];
Array.Copy(Received, cmd_offset, swap, 0, received_offset - cmd_offset);
Array.Copy(swap, 0, Received, 0, received_offset - cmd_offset);
received_offset = received_offset - cmd_offset;
cmd_offset = 0;
}
private string dump_buffer(byte[] buffer)
{
StringBuilder sp = new StringBuilder(1024);
int n = 0;
foreach (byte x in buffer)
{
sp.AppendFormat("{0:X2}", x);
n++;
if (n > 512) break; //缓存有限,只处理最长512字节
}
return sp.ToString();
}
private int get_int(byte[] buffer, int Start, int Length)
{
int n = 0;
StringBuilder strBuilder = new StringBuilder();
for (int i = Start; i < Start + Length && i < buffer.Length; i++)
{
strBuilder.AppendFormat("{0:X2}",buffer[i]);
}
n = Convert.ToInt32(strBuilder.ToString());
return n;
}
///
/// 计算LRC校验, xor异或
///
///
///
///
///
private byte calc_lrc(byte[] data, int Start, int End)
{
byte lrc = 0;
int dlength = data.Length;
for (int i = Start; i < End && i < dlength; i++)
{
lrc ^= data[i];
}
return lrc;
}
}
}