一个C#写的接收电子称数据并解析出重量数据的类。
之前单位购买了两架电子称,同时单独购买了配套的软件。该软件是用Delphi 7写就的,界面老旧就不说了,关键是功能太弱了,基本只能简单地记录一下称重的数据,打印的标签效果亦只能是简单的文字打印,基本上无法使用。想着之前曾经有用POS指令控制串口打印机的经验,应该不是很难,就自己写了一个,以解决零散分包标签打印和装箱标签打印的需要。
共接触到三种电子称数据格式,如数据格式不在此列,则使用例如串口调试助手之类的工具接收电子称数据自行分析并作相应调整即可。
电子称状态变更通告事件类,注意此辅助助类原处于不同的命名空间,请自行修改或合并到相同的命名空间。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace CommonLib
{
///
/// 电子称状态变更通告事件类定义
///
public class ScaleStateChangeEventArgs:EventArgs
{
public int ScaleID {
get; private set; }
public float? Weight {
get; private set; }
public bool Disconnected {
get; private set; }
public bool PortOpenError {
get; private set; }
///
/// 电子称状态变更通告事件
///
/// 电子称标号,对应数据库中的ID
/// 实时屏显重量
/// 连接已丢失
/// 端口(串口)打开错误
public ScaleStateChangeEventArgs(int scaleID, float? weight, bool disconnected, bool portOpenError)
{
this.ScaleID = scaleID;
this.Weight = weight;
this.Disconnected = disconnected;
this.PortOpenError = portOpenError;
}
}
}
电子称参数类,作为主类构造函数的参数,用于封装电子称的主要参数:串口及数据格式的参数。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace DataReceiver
{
///
/// 电子称参数类
///
public class ScaleParam
{
public int ScaleID {
get; set; }
public string Name {
get; set; }
public string COMPortName {
get; set; }
public int BaudRate {
get; set; }
public int DataBits {
get; set; }
public string StopBits {
get; set; }
public string Parity {
get; set; }
public int CheckingInterval {
get; set; }
public int DataLength {
get; set; }
public int BeginByte {
get; set; }
public int EndByte {
get; set; }
public int ScaleDataBeginLoc {
get; set; }
public int ScaleDataLength {
get; set; }
///
/// 有些电子称发送的数据格式是没有小数点分隔符例如:耀华 Xk3190
///
public bool HasDecimalSeparatorFlag {
get; set; }
///
/// 没有小数点的数中整数部分长度
///
public short IntegerPortionLength {
get; set; }
}
}
比较简单,请参考注释即可。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.IO;
using System.IO.Ports;
using System.Runtime.InteropServices;
using CommonLib;
using DataReceiver;
namespace DataReceiver
{
///
/// 电子称数据接收类 Receiver
///
public class Receiver
{
///
/// 电子称状态变更通告事件
///
public event EventHandler<ScaleStateChangeEventArgs> ScaleStateChange;
///
/// 电子称参数
///
private ScaleParam scale;
///
/// 连接类型:串口
///
private SerialPort comport;
///
/// (从电子称)最后一次收到的正确的字节数据
///
private byte[] lastReceivedBytes = new byte[1];
///
/// (从电子称)最后一次收到的正确的重量
///
private float? lastReceivedWeight;
///
/// 电子称发送的有效数据长度
///
int DataLength = -1;
///
/// 从串口读取的数据长度,为保证接到的数据包含有效数据,
/// 读取的数据长度为:DataLength * 2
/// 此处或可再优化,实际使用正常,故未作深究
///
int bytesToRead = -1;
///
/// 电子称连接丢失标志
///
private bool lostConnectionFlag = false;
///
/// 接收到错误的数据标志
/// 通常,电子称开机时、波特率不匹配时数据格式会有问题
///
private bool errorDataReceivedFlag = false;
///
/// 定时读取串口数据的定时器
///
private Timer DataChecker;
///
///
///
//private GlobalData globalData = GlobalData.Instance;
public Receiver() {
}
///
/// 自定义构造器
///
/// 电子称参数
public Receiver(ScaleParam targetScale)
:base()
{
scale = targetScale;
DataLength = scale.DataLength;
bytesToRead = DataLength * 2;
Initializer();
DataChecker = new Timer(DataProcessor, null, scale.CheckingInterval, scale.CheckingInterval);
}
public void Reset()
{
DataChecker.Change(Timeout.Infinite, Timeout.Infinite); // Stop
comport.Close();
Initializer();
DataChecker.Change(scale.CheckingInterval, scale.CheckingInterval); // Start
}
public void ClosePort()
{
comport.Close();
}
///
/// 初始化招收器
/// 设置标志、初始化接收数组、打开串口等
///
public void Initializer()
{
lastReceivedBytes = new byte[DataLength];
lostConnectionFlag = false;
errorDataReceivedFlag = false;
bool error = false;
comport = new SerialPort();
if (comport.IsOpen) comport.Close();
else
{
comport.PortName = scale.COMPortName;
comport.BaudRate = scale.BaudRate;
comport.DataBits = 8;
comport.StopBits = StopBits.One;
comport.Parity = Parity.None;
comport.DtrEnable = true;
comport.RtsEnable = true;
try
{
comport.Open();
}
catch (UnauthorizedAccessException) {
error = true; }
catch (IOException) {
error = true; }
catch (ArgumentException) {
error = true; }
if (error)
{
ScaleStateChange?.Invoke(this, new ScaleStateChangeEventArgs(scale.ScaleID, null, true, true));
}
else
{
}
}
}
///
/// 电子称数据处理例程
///
///
private void DataProcessor(object state)
{
// 如果串口未打开或缓冲区接收到的数据长度少于 bytesToRead 的定义值
if (!comport.IsOpen || comport.BytesToRead < bytesToRead)
{
if (!lostConnectionFlag)
{
lostConnectionFlag = true;
ScaleStateChange?.Invoke(this, new ScaleStateChangeEventArgs(scale.ScaleID, null, true, false));
}
// 直接返回以直至收到足够的数据
return;
}
// 已接收到足够的数据,无须继续接收(因为电子称的数据是连继重复发送的)
DataChecker.Change(Timeout.Infinite, Timeout.Infinite); // Stop
byte[] buffer = new byte[bytesToRead];
// 从缓冲区读长度为 bytesToRead 的数据
comport.Read(buffer, 0, bytesToRead);
// 定位有效数据首字节出现的位置
int begingOffset = -1;
for (int i= 0; i < buffer.Count(); i++)
{
if (buffer[i] == scale.BeginByte) //
{
begingOffset = i;
break;
}
}
try
{
byte[] data = new byte[DataLength];
// 复制完整数据至字节缓存数组
Buffer.BlockCopy(buffer, begingOffset, data, 0, DataLength);
// 调用数据处理例程
ProcessingScaleData(data);
}
catch (Exception ex)
{
//Console.WriteLine(">>>>>>>>>>>>>>>>>>>>>> Exception: " + ex.Message);
}
// 清空串口接收缓冲区以保证数据实时性
comport.DiscardInBuffer(); //Start over
// 重新启动数据接收定时器
DataChecker.Change(scale.CheckingInterval, scale.CheckingInterval); // Start
}
///
/// 电子称数据处理例程
///
/// 电子称发送的字节数据
private void ProcessingScaleData(byte[] receivedBytes)
{
// 只有和最后一次收到的数据不同时才需要处理
if (!ByteArrayCompare(lastReceivedBytes, receivedBytes))
{
ProcessingData(receivedBytes);
}
// 收到相同数据时检查电子称的串口连接是否断开
// 如果曾经断开过连接,屏幕会显示 ERROR
// 此时需重置标志并更新显示
else if (lostConnectionFlag)
{
lostConnectionFlag = false;
ProcessingData(receivedBytes);
}
}
///
/// 更新 lastReceivedBytes 变更数据
/// 并调用数据解析例程
///
/// 电子称发送的字节数据
private void ProcessingData(byte[] receivedBytes)
{
lastReceivedBytes = receivedBytes;
// 字节数据转换为字符串数据并传给解析例程
ParseData(Encoding.ASCII.GetString(receivedBytes));
}
string tmpWeightData = string.Empty;
string weightData = string.Empty;
float weightResult;
///
/// 电子称数据解析例程
/// 支持3种电子称数据格式,请参考图示
/// 可根据需要扩充
///
/// 电子称发送过来的字符串数据
private void ParseData(string data)
{
try
{
weightData = data.Substring(scale.ScaleDataBeginLoc, scale.ScaleDataLength);
if (!scale.HasDecimalSeparatorFlag)
{
tmpWeightData = weightData;
weightData = tmpWeightData.Substring(0, scale.IntegerPortionLength) + "." +
tmpWeightData.Substring(scale.IntegerPortionLength, scale.ScaleDataLength-scale.IntegerPortionLength);
}
if (float.TryParse(weightData, out weightResult))
{
errorDataReceivedFlag = false;
lastReceivedWeight = weightResult;
ScaleStateChange?.Invoke(this, new ScaleStateChangeEventArgs(scale.ScaleID, weightResult, false, false));
}
else if (!errorDataReceivedFlag)
{
errorDataReceivedFlag = true;
ScaleStateChange?.Invoke(this, new ScaleStateChangeEventArgs(scale.ScaleID, lastReceivedWeight, false, false));
lastReceivedBytes = new byte[1];
}
}
catch (Exception ex)
{
ScaleStateChange?.Invoke(this, new ScaleStateChangeEventArgs(scale.ScaleID, null, false, false));
lastReceivedBytes = new byte[1];
}
}
[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
static extern int memcmp(byte[] b1, byte[] b2, long count);
///
/// 字节数组比较例程
///
/// 字节数组1
/// 字节数组2
/// 相同则返回True;否则False
static bool ByteArrayCompare(byte[] b1, byte[] b2)
{
// Validate buffers are the same length.
// This also ensures that the count does not exceed the length of either buffer.
return b1.Length == b2.Length && memcmp(b1, b2, b1.Length) == 0;
}
///
/// TXT日志记录例程
///
/// 待写入日志文件中的字符串数据
private void WriteFile(string data)
{
FileStream fs = new FileStream(AppDomain.CurrentDomain.BaseDirectory + "Logger.txt", FileMode.Append);
StreamWriter sw = new StreamWriter(fs);
sw.Write(data);
sw.Flush();
sw.Close();
fs.Close();
}
} // class ends
}
程序界面使用了MahApps.Metro.Controls,具体请参考 https://mahapps.com/docs/controls/metrowindow