用C#实现通过串口对设备的数据采集--Server层

时间:2021-12-28 15:24:19

今天中午没睡午觉,头昏眼花的,实在写不了代码,把这几天写的Server层数据采集的程序整理了一下。

WatrLevelDataCollectServer.cs

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO.Ports;
using SMOS.Model.Device;
using System.Text.RegularExpressions; namespace SMOS.Server.Impl.Collection
{
public class WaterLevelDataCollectServer : DataCollectServer
{
private SerialPort comm = new SerialPort();
private bool Closing = false;//是否正在关闭串口,执行Application.DoEvents,并阻止再次invoke
private bool Listening = false;//是否没有执行完invoke相关操作
private List<byte> buffer = new List<byte>();//默认分配1页内存,并始终限制不允许超过
private byte[] binary_data = new byte[];//FF FF 01 03 1B 1D
private decimal dataValue = ;
private bool dataCatched;
private bool workManner = true;//工作方式,true--使用设备默认的自动报告方式,false--使用查询方式
/// <summary>
/// 采集水位计数据(默认方式,自动报告方式)
/// </summary>
/// <param name="waterLevelSetInfo"></param>
/// <returns></returns>
public override Model.Device.DeviceRecordInfo GetData(Model.Device.DeviceSetInfo deviceSet)
{
WaterLevelSetInfo entity = deviceSet as WaterLevelSetInfo;
//根据当前串口对象,来判断操作
if (comm.IsOpen)
{
comm.Close();
}
try
{
comm.PortName = entity.Port;
comm.BaudRate = entity.BaudRate;
comm.Open();
}
catch (Exception ex)
{
throw ex;
}
if(workManner == false )//采集水位计数据(查询方式,需要对设备进行手动设置)
{
//发送采集指令 ,16进制发送
byte extensionNo = 0x01;//分机编号默认为"01"
byte checkData = 0x6B;//在分机编号默认时,校验位为“6B”
checkData =(byte)(0xFC + 0x6E + (int)extensionNo);
MatchCollection mc = Regex.Matches("FC 6E "+""+extensionNo+"00 00 "+ checkData.ToString("X"), @"(?i)[\da-f]{2}");
List<byte> buf = new List<byte>();//填充到临时列表中
//依次添加到列表中
foreach (Match m in mc)
{
buf.Add(byte.Parse(m.Value, System.Globalization.NumberStyles.HexNumber));
}
//转换列表为数组后发送
comm.Write(buf.ToArray(), , buf.Count);
}
WaterLevelRecordInfo waterLevelRecordInfo = new WaterLevelRecordInfo();
//添加事件注册
comm.DataReceived += comm_DataReceived;
DateTime dtOld = DateTime.Now;
while (true)
{
if (DateTime.Compare(dtOld.AddSeconds(Convert.ToDouble(entity.AcquisitionInterval)), DateTime.Now) > )
{
System.Threading.Thread.Sleep();
if (dataCatched)
{
waterLevelRecordInfo.MeasuredLevel = dataValue;
waterLevelRecordInfo.RecordTime = DateTime.Now;
waterLevelRecordInfo.DeviceID = entity.DeviceID;
//end operation //解绑事件
comm.DataReceived -= comm_DataReceived;
comm.Close();
return waterLevelRecordInfo as DeviceRecordInfo;
}
}
else
{
comm.DataReceived -= comm_DataReceived;
comm.Close();
return null;
}
}
}
/// <summary>
/// 串口数据接收
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void comm_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
if (Closing) return;//如果正在关闭,忽略操作,直接返回,尽快的完成串口监听线程的一次循环
try
{
Listening = true;//设置标记
int n = comm.BytesToRead;//记录缓存
byte[] buf = new byte[n];//声明一个临时数组存储串口数据
comm.Read(buf, , n);//读取缓冲数据
dataCatched = false;//缓存记录数据是否捕获到
//缓存数据
buffer.AddRange(buf);
//完整性判断
while (buffer.Count >= )//至少要包含头(2字节)+分机编号(1字节)+测量数据(2字节)+校验(1字节)
{
//查找数据头
if (buffer[] == 0xFF && buffer[] == 0xFF)
{
int len = ;//数据长度
//数据完整判断第一步,长度是否足够
if (buffer.Count < len) break;
buffer.CopyTo(, binary_data, , len);//复制一条完整数据到数据缓存
//分析数据
dataValue = (decimal)((binary_data[] * + binary_data[]) / 1000.000);
dataCatched = true;
buffer.RemoveRange(, len);//正确分析一条数据,从缓存中移除数据
}
else
{
//如果数据开始不是头,则删除数据
buffer.RemoveAt();
}
}
}
catch (Exception ex)
{
dataCatched = false;
}
finally
{
Listening = false;//可以关闭串口
}
}
}
}

在WaterLevelDataCollectServer这个类里主要通过GetData这个方法来实现的水位计数据的采集,使用SerialPort控件,当串口接收导数据的时候自动触发comm_DataReceived事件,但是由于这个事件被触发的时刻不确定,所以在GetData方法中使用循环等待,当接收到数据时跳出循环,并将采集结果返回。

  在没有实际设备的时候可以用串口调试助手并通过虚拟串口给程序发送数据,注意COM口,波特率等通信参数的设置应该一一对应。