资讯专栏INFORMATION COLUMN

一个简单的电子称数据接收/解析类(C#)

isaced / 3567人阅读

摘要:一个写的接收电子称数据并解析出重量数据的类。之前单位购买了两架电子称,同时多带带购买了配套的软件。电子称参数类有些电子称发送的数据格式是没有小数点分隔符例如耀华没有小数点的数中整数部分长度数据接收处理类比较简单,请参考注释即可。

一个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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/121828.html

相关文章

  • 【转】php命名空间

    摘要:命名空间可以解决以下两类问题用户编写的代码与内部的类函数常量或第三方类函数常量之间的名字冲突。在命名空间内部访问全局类函数和常量调用全局函数访问全局常量实例化全局类命名空间和动态语言特征命名空间的实现受到其语言自身的动态特征的影响。 PHP 命名空间(namespace)是在PHP 5.3中加入的,如果你学过C#和Java,那命名空间就不算什么新事物。 不过在PHP当中还是有着相当重要...

    Jrain 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<